How would you set up an activerecord/datamapper association for the following scenario:
A user creates a "bookshelf" which has many books(a book object just has an isbn that is used to query an api, and has_many review objects associated with it). Let's say Jack creates a "bookshelf" with a book object. Then, lets say that Jill creates a "bookshelf" with the same book object(it has the same id and the same reviews). The book object has the following code as of now:
class Book < ActiveRecord::Base
has_many :reviews
end
Then, when you view the page for a book (you click the link to it from the "bookshelf" created by Jack) you should see the same book object when you clicked the link to it from Jill's "bookshelf" (e.g. both "bookshelves" have a link to /books/23 because they have the same book object).
I have not been able to figure this out with the has_many association because that requires me to make a new book each time a user adds a book to their "bookshelf." I have trouble understanding the has_and_belongs_to_many relationship, is that what should be used here? I was not able to find any similar questions on SO, so any help is greatly appreciated.
I am using Rails 4 with Ruby 2.1.
Here is a drawing of what I would like to accomplish: Drawing
Yes, you would have to define
many-to-many relationshipbetween a Bookshelf and a Book. There are two ways to achieve this in Rails:Option 1) Use
has_and_belongs_to_manySee guide
According to official documentation
has_and_belongs_to_manyassociation:So, your classes should look like this:
Add a join table generation to your migrations:
This will create a
books_bookshelvestable in your database. The table will have no primary key. There would be two foreign keys to your modelsBookandBookshelf.So, if you call
self.booksin the context of an user's bookshelf, you will get a list of books in the bookshelf. Vice versa, callingself.bookshelvesin the context of a book will return a set of bookshelves the book belongs to.The problem with this approach is that every time you add a new book to the bookshelf a new record is created in the database. If you are okay with that, there is no easier option than using
has_and_belongs_to_many association. Otherwise, I recommend you to go with the Option #2.Option 2) Use
has_many :throughAnother option is to use
has_many, :through association(see guide). You would have to define one more model to do that, but it might come handy in some use cases (see below for an example).Your classes should look like this:
Probably the best thing about using
has_many :through associationis that it allows you to add custom columns to the join table (e.g. add columncountto keep track how many books of the same type are there in the bookshelf).The migration would look pretty much the same as the one we used in Option 1, except for the fact we are adding an unique constraint on the foreign keys (please note that adding the constraint is optional):
By going with this approach, adding a new would be a bit more complicated as you would have to make sure you are not inserting duplicate records in the join table. (However, you may remove the unique constraint from the migration, to achieve exactly the same kind of behavior as you would get with
has_and_belongs_to_many.)