9

Say I have two models, Book and Author with a has_and_belongs_to_many relationship between them.

What I want to do is to be able to add author names in the book form, and on submit to either link the authors with the book if they already exist, or create them if they don't.

I also want to do the same with the author form: add book names and on submit either link them if they exist, or create them if they don't.

On edit, however, I want to neither be able to edit nor delete the nested objects, only remove the associations.

Is accepts_nested_attributes_for suitable for this, or is there another way?

I managed to accomplish this by following the Complex Forms railscasts on Rails 2, but I'm looking for a more elegant solution for Rails 3.

Marjan
  • 1,378
  • 1
  • 14
  • 21

1 Answers1

12

I'm not sure why so many people use has_and_belongs_to_many, which is a relic from Rails 1, instead of using has_many ..., :through except that it's probably in a lot of old reference books and tutorials. The big difference between the two approaches is the first uses a compound key to identify them, the second a first-class model.

If you redefine your relationship, you can manage on the intermediate model level. For instance, you can add and remove BookAuthor records instead of has_and_belongs_to_many links which are notoriously difficult to tweak on an individual basis.

You can create a simple model:

class BookAuthor < ActiveRecord::Base
  belongs_to :book
  belongs_to :author
end

Each of your other models is now more easily linked:

class Book < ActiveRecord::Base
  has_many :book_authors
  has_many :authors, :through => :book_authors
end

class Author < ActiveRecord::Base
  has_many :book_authors
  has_many :books, :through => :book_authors
end

On your nested form, manage the book_authors relationship directly, adding and removing those as required.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • Thank you. I took this approach and it's much easier to implement than anything else. – Marjan Feb 09 '11 at 13:45
  • 37
    The reason people use HABTM over has_many :through is because not every many-to-many join table requires a model to manage it. It's not uncommon for a complex data model to have many join tables that do nothing but express the relationship between two other tables. – Aaron Rustad Apr 12 '12 at 20:00
  • The name alone is reason enough for HABTM to die, but there are others. For one, you can't use `nested_attributes_for` with HABTM, so adding or removing things with checkboxes is an exercise in frustration with the old method. Even if you don't need all the features that the `:through` approach has to offer, if there's even a slight chance that you *will*, it's worth using. I can't think of any reasons to use HABTM specifically and haven't used it since Rails 1.x. – tadman Apr 13 '12 at 04:54
  • 9
    The name of the method is irrelevant as to whether it should exist or not. Polluting a code base with any number of models/classes that do nothing is a code smell and should be avoided at all costs, even if there is a "slight chance that you will" need them. If you need them, simply make the change when needed. – Aaron Rustad Apr 15 '12 at 00:27
  • 2
    I honestly couldn't disagree more. `has_and_belongs_to_many` is a clunky antiquity that has no place in a modern Rails application. A join model, even if simple, serves a specific purpose: Defining the relationship between two entities. Having tables without models in ActiveRecord is not something to be done casually. – tadman Apr 15 '12 at 07:05
  • 9
    Clearly, there is a place HABTM. From the 3.2.3 documentation: "Choosing which way to build a many-to-many relationship is not always simple. If you need to work with the relationship model as its own entity, use has_many :through. Use has_and_belongs_to_many when working with legacy schemas or when you never work directly with the relationship". Seems reasonable enough to me. – Aaron Rustad Apr 15 '12 at 16:08
  • 1
    Looks like this was discussed on SO before: http://stackoverflow.com/questions/658206/has-and-belongs-to-many-in-rails – Aaron Rustad Apr 15 '12 at 16:25
  • I would certainly opt for HABTM over :through if possible. – Kris Sep 04 '12 at 15:48
  • 1
    The ActiveRecord `has_and_belongs_to_many` implementation lacks a lot of the features put into the `:through` version, plus the join records themselves don't really exist as models but some magical glue that holds them together. Any attributes on the join model will appear within the context of a model fetched with them, creating confusion. The overhead of `:through` is minimal and the benefits are significant. Legacy applications may be using `has_and_belongs_to_many` because that was the only facility available at the time, but new applications should use `:through` whenever possible. – tadman Sep 04 '12 at 16:13