Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct implementation of many-to-many in Ruby on Rails?

Newbie question, beware! I'd like to implement a basic many-to-many relationship in Rails and I'm trying to find out which approach is considered the most "rails way" of them. In a traditional non-ActiveRecord DB I'd just created two tables and a junction table and written a bunch of logic to make sure all three tables are taken in consideration when operations are performed on any of them.

This is my first time using an ORM and so I'm trying to find out if perhaps ActiveRecord somehow simplifies the process for you, perhaps by not requiring a junction table to be manually created.

Railscasts seems like a reputable source of Rails wisdom, are the two ways in this cast truly "Rails way" or can I do better? - http://railscasts.com/episodes/47-two-many-to-many

like image 809
Alexandr Kurilin Avatar asked Dec 21 '22 09:12

Alexandr Kurilin


1 Answers

There's basically two ways: has_and_belongs_to_many (habtm) and has_many with a :through option that points to another association. Both require join tables; the latter is what we call a join model, because you typically add more information to the join.

For example, consider an application with a User model who bookmarks Sites. One way would be to implement it as a habtm relationship

class User < ActiveRecord::Base
  has_and_belongs_to_many :sites
end
class Site < ActiveRecord::Base
  has_and_belongs_to_many :users
end

user.sites << Site.find(...)

This modeling will also require creating the sites_users table, which necessarily will lack a primary key.

The problem with this is you're likely to want to store additional information on it, so you might as well go with a join model, in this case Bookmark:

class User < ActiveRecord::Base
  has_many :bookmarks
  has_many :sites, :through => :bookmarks
end
class Site < ActiveRecord::Base
  has_many :bookmarks
  has_many :users, :through => :bookmarks
  #edit: adding validation for requiring at least one bookmark
  validate_before_create :at_least_one_bookmark
  private
  def at_least_one_bookmark
    errors.add_to_base("requires at least one bookmark") unless bookmarks.count > 0
  end
end
class Bookmark < ActiveRecord::Base
  belongs_to :user
  belongs_to :site
end

user.bookmarks.create(:site => Site.find(...) )

The more common pattern is the join model approach for its versatility and better modelling, though habtms are still used somewhat. They're just so two-dimensional that you really need to examine what you're doing and make sure there isn't some richer behavior that needs to be modelled as well.

like image 81
Keith Gaddis Avatar answered Feb 11 '23 08:02

Keith Gaddis