Here’s a little trick I found when handling multiple nested models in a form that require validations for each model.
Spec time. For example, we have a model, Author, which hasmany Books and hasmany Magazines. Book and Magazine has validations. On a single page, we create a three forms for Book, Magazine, and Author. There is also an option select that a user can choose if the Author has a Book or a Magazine.
Pretty straight forward. Let’s do some code.
#models/author.rb class Author < ActiveRecord::Base has_many :books has_many :magazines accepts_nested_attributes_for :books accepts_nested_attributes_for :magazines validates_presence_of :name end
#models/book.rb class Book < ActiveRecord::Base belongs_to :author validates_presence_of :title, :genre end
#models/magazine.rb class Magazine < ActiveRecord::Base belongs_to :author validates_presence_of :title, :genre end
Basic model setup with validations and acceptsnestedattributes_for. More info on that can be found in the Ruby on Rails API - Nested Attributes.
Moving on to the form setup..
#views/authors/new.html.erb <% form_for @author do |f| %> <%= f.error_messages %> <p> <%= f.label :name %><br /> <%= f.text_field :name %> </p> <p> <%= f.radio_button("media_type", "book" %><%= f.label :media_type_book, 'Book' %> [?] <%= f.radio_button("media_type", "magazine" %><%= f.label :media_type_magazine, 'Magazine' %> [?] </p> <% f.fields_for :books do |book| %> <p> <%= book.label :title %><br /> <%= book.text_field :title %> </p> <p> <%= book.label :genre %><br /> <%= book.text_field :genre %> </p> <% end %> <% f.fields_for :magazines do |magazine| %> <p> <%= magazine.label :title %><br /> <%= magazine.text_field :title %> </p> <p> <%= magazine.label :genre %><br /> <%= magazine.text_field :genre %> </p> <% end %> <%= f.submit 'Submit' %> <% end %>
That sets up the form with the nested model, now for the controller code.
#controllers/authors_controller.rb class AuthorsController < ApplicationController def new @author = Author.new books = @author.books.build #this builds the nested form in the view magazines = @author.magazines.build #this builds the nested form in the view end def create param_hash = params[:author] if params[:author][:media_type] == "book" param_hash.delete("books_attributes") elsif params[:author][:media_type] == "magazine" param_hash.delete("magazines_attributes") end @author = Author.new(param_hash) @author.save end end
Instead of passing the params straight to Author.new, it’s put into a hash variable. Then the params get checked for a title and genre, if empty delete the key from the hash and pass to Author.new. Activerecord wont see the book param and will skip the validations for it.