So far one of the more difficult topics I have encountered in Rails is nested forms and how it produces HTML and interacts with objects. A first glance it seems simple, it’s like a nested array…sort of…right? Well sort of…also not sort of. First off when white-listing attributes and nested attribute it comes out a hash. Second off, when you add the nested attributes (ie categories_attributes in the following code) it gets confusing. There are very specific naming conventions that must be followed.
def post_params params.require(:post).permit(:name, category_ids:, categories_attributes: [:name, :some_attribute]) end
At first glance just whitelisting the params should work since rails is full of magic. However, in addition, you need to do is add a line in the MODEL that adds methods. Just like
has_many: adds methods so does
class Post < ActiveRecord::Base has_many :categories accepts_nested_attributes_for :categories end
Here is an example with a
belongs_to relationship notice it’s singular and not plural.
class Comment < ActiveRecord::Base belongs_to :user accepts_nested_attributes_for :user end
def comment_params params.require(:comment).permit(:content,, :user_id, user_attributes:[:username]) end
However, everything else a bit less intuitive.
accepts_nested_attributes_for is added it creates a method which is named in this situation
categories_attributes or user_attributes. It is named after whatever model you happen to be using.
Now that we have gone over how to setup this association we can talk about the method.
def categories__attributes=(category_attributes) code and probably more code end
Seems simple. However, it doesn’t do what it looks like it does. It intercepts from a nested form an appropriately placed :category and runs the data through categories_attributes=. In fact, if you placed :category_attributes in place of :category it wouldn’t work.
categories_attributes= intercepts the expected setter method in a nested form. So you just have to remember that when using a nested form that
<%= f.fields_for :categories do |category| %> <%= category.label :name %> <%= category.text_field :name %> <% end %>
<%= f.fields_for :categories_attributes do |category| %> <%= category.label :name %> <%= category.text_field :name %> <% end %>