Rails has_and_belongs_to_many & has_many :through scaffolding

Perry Horwich picture Perry Horwich · Oct 10, 2011 · Viewed 7.3k times · Source

Is there a way to generate a scaffold for Rails models that have either a has_and_belongs_to_many or has_many :through relationship? If not, is there a developmental reason why some basic form of this functionality has not been included? Rails requires developers to generate and edit a "custom" "join table migration." Is the necessary attention to detail a way of reminding Rails developers of some important fact or is this simply an example of how Rails is a 'work in progress?'

A quick Stackoverflow search for:

"has_and_belongs_to_many" yields 821 questions  
"has_many :through" yields 933 questions  

... and many of these start with "How do I..." So it seems the topic is not trivial and relevant to SO users. I Guess I am wondering why it has not received attention. Is the matter more complex than it seems? The feature set for Rails seems so extensive. I keep wondering why scaffolding for associations with join tables has been omitted.

Please focus your answer toward the assertions above or "include statement like, "A scaffolding would have to include..." or "A rails generate script would need..."

Thanks!

Answer

mliebelt picture mliebelt · Oct 10, 2011

I like your question, I have wondered for myself why this is not included in Rails (and as I suspect not available as a Gem). Here are some of the reasons why that could be a problem. I will them explain at the typical example with assemblies and parts.

  1. Scaffolding works by using generators to create files. If you migrate to a newer version, there is sometimes the option to update existing files by doing the following steps:

    • Copy existing ones to a new name.
    • Overwrite existing ones by new ones.

    That implies that there is no notion of adding to files or changing files. EDIT: This is not true, there are mechanisms to add to files and even change in files.

  2. If you look at the generation of e.g. has_many :through (should be similar to has_and_belongs_to_many) you have to do the following things:

    • Add a migration that creates the join table.

      ==> Should be possible for scaffolding by doing: rails g scaffold_hmt Assembly Part

    • Add a model for that join model.

      ==> Should be possible for scaffolding by the previous scaffold.

    • Change existing models to include the lines:

      assembly.rb: has_many 'assemblies_parts'; has_many :parts, :through => 'assemblies_parts'

      part.rb: has_many 'assemblies_parts'; has_many :assemblies, :through => 'assemblies_parts'

      ==> So no scaffolding possible

    • What to do with views is wide open. There are examples at RailsCast how to do it, but it is not at all trivial, and there is no one single technique that works well in all circumstances. I do think that the different patterns could be implemented as templates for scaffolding (e.g. to use checkboxes or multi-select lists or text entry with completion). The problem is the same as in has_many views, however.

So as a summary, a scaffold could be worth a try (see the following paragraph). The RailsGuides to Creating and Customizing Rails Generators & Templates seems plausible at least. And a solution that generates parts of files, names them accordingly and helps in the output of the scaffold on the console to do the rest by hand could be worth a try.


I have tried yesterday to come up with a partial solution, you may have a look at it at GitHub. It works like that:

  1. Copy the contents of the directory scaffold_hmt (stands for has_many :through) to your rails application directory lib/generators.
  2. You may call then the generator with: rails g scaffold_hmt Assembly Part.
  3. It will then generate:
    • Migration for the join table
    • Model for the join table
  4. It fails to change the files:

    • Model assembly.rb
    • Model part.rb

    The reason for that is that the finding of the right place is not trivial. As a workaround, it prints out what should have inserted into the files.

    c:\apps\ruby\rails3\minds>rails generate scaffold_hmt Assembly Part
          create  db/migrate/20111011135944_create_assemblies_parts.rb
          create  app/models/assemblies_part.rb
    Try to insert into file: app/models/assembly.rb the following statements:
    
    has_many :assemblies_parts
    has_many :parts, :through => :assemblies_parts
          insert  app/models/assembly.rb
    Try to insert into file: app/models/part.rb the following statements:
    
    has_many :assemblies_parts
    has_many :assemblies, :through => :assemblies_parts
          insert  app/models/part.rb
    

Give it a try and see if that will help you.