views:

825

answers:

5

Hey guys,

how can I achieve the following? I have two models (blogs and readers) and a JOIN table that will allow me to have an N:M relationship between them:

class Blog < ActiveRecord::Base
 has_many :blogs_readers, :dependent => :destroy
 has_many :readers, :through => :blogs_readers
end

class Reader < ActiveRecord::Base
 has_many :blogs_readers, :dependent => :destroy
 has_many :blogs, :through => :blogs_readers
end

class BlogsReaders < ActiveRecord::Base
  belongs_to :blog
  belongs_to :reader
end

What I want to do now, is add readers to different blogs. The condition, though, is that I can only add a reader to a blog ONCE. So there mustn't be any duplicates (same readerID, same blogID) in the BlogsReaders table. How can I achieve this?

The second question is, how do I get a list of blog that the readers isn't subscribed to already (e.g. to fill a drop-down select list, which can then be used to add the reader to another blog)?

Sebastian

+5  A: 

This should take care of your first question:

class BlogsReaders < ActiveRecord::Base
  belongs_to :blog
  belongs_to :reader

  validates_uniqueness_of :reader_id, :scope => :blog_id
end
Mike Breen
A: 

Thanks Mike! That works perfectly and fails to save it! Do you have any idea how to solve the second issue? That's kinda the more important one for me, since I'd like to give the user valid options only :-)

Sebastian
+1  A: 

I'm thinking someone will come along with a better answer than this.

the_reader = Reader.find(:first, :include => :blogs)

Blog.find(:all, 
          :conditions => ['id NOT IN (?)', the_reader.blogs.map(&:id)])

[edit]

Please see Josh's answer below. It's the way to go. (I knew there was a better way out there ;)

Mike Breen
you could also do this in one statement using find_by_sql.
Mike Breen
Awesome! This works perfectly! Thanks a lot!!
Sebastian
+3  A: 

What about:

Blog.find(:all,
          :conditions => ['id NOT IN (?)', the_reader.blog_ids])

Rails takes care of the collection of ids for us with association methods! :)

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

Josh
Also, I wanted to mention that this is probably the better method, as the accepted answer selects ALL data from the row(s) (e.g., the_reader.blogs) whereas my answer selects only the ids from the rows (e.g., the_reader.blog_ids). This is a big performance hit!
Josh
this is a better solution and should be the right answer. Thanks Josh.
Mike Breen
thx Josh! Looks slimmer indeed!
Sebastian
+1  A: 

Simpler solution that's built into Rails:

class Blog < ActiveRecord::Base
 has_many :blogs_readers, :dependent => :destroy
 has_many :readers, :through => :blogs_readers, :uniq => true
end

class Reader < ActiveRecord::Base
 has_many :blogs_readers, :dependent => :destroy
 has_many :blogs, :through => :blogs_readers, :uniq => true
end

class BlogsReaders < ActiveRecord::Base
  belongs_to :blog
  belongs_to :reader
end

Note adding the :uniq => true option to the has_many call.

Also you might want to consider has_and_belongs_to_many between Blog and Reader, unless you have some other attributes you'd like to have on the join model (which you don't, currently). That method also has a :uniq opiton.

Note that this doesn't prevent you from creating the entries in the table, but it does ensure that when you query the collection you get only one of each object.

Otto