Rails 4 has_one association form not building

Wasabi Developer picture Wasabi Developer · Nov 6, 2013 · Viewed 12.1k times · Source

I need some pointers on how Rails 4 works with has_one and belongs_to association.

My form doesn't save the has_one relationship

Post Model

class Post < ActiveRecord::Base
  validates: :body, presence: true

  has_one :category, dependent: :destroy
  accepts_nested_attributes_for :category
end

class Category < ActiveRecord::Base
  validates :title, presence: true
  belongs_to :post
end

Post Controller

class PostController < ApplicationController
  def new
    @post = Post.new
    @post.build_category
  end

  def create
    @post = Post.new(post_params)
  end

  private

  def post_params
    params.require(:post).permit(:body)
  end
end

Form in the Post#new action

<%= form_for @post do |form| %>

  <%= form.label :body %>
  <%= form.text_area :body %>

  <%= fields_for :category do |category_fields| %>
    <%= category_fields.label :title %>
    <%= category_fields.text_field :title %>
  <% end %>

  <%= form.button "Add Post" %>

<% end %>

It's not saving the category title when the Post form is submitted.

Debug params

utf8: ✓
authenticity_token: 08/I6MsYjNUhzg4W+9SWuvXbSdN7WX2x6l2TmNwRl40=
post: !ruby/hash:ActionController::Parameters
  body: 'The best ice cream sandwich ever'
category: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  title: 'Cold Treats'
button: ''
action: create
controller: posts

App log

Processing by BusinessesController#create as HTML
  Parameters: {"utf8"=>"✓",
               "authenticity_token"=>"08/I6MsYjNUhzg4W+9SWuvXbSdN7WX2x6l2TmNwRl40=",
               "post"=>{"body"=>"The best ice cream sandwich ever"},
               "category"=>{"title"=>"Cold Treats", "button"=>""}

In the Rails console.. I can run the following succesfully

> a = Post.new
=> #<Post id: nil, body: "">
> a.category
=> nil

> b = Post.new
=> #<Post id: nil, body: "">
> b.build_category
=> #<Post id: nil, title: nil>
> b.body = "The best ice cream sandwich ever"
=> "The best ice cream sandwich ever"
> b.category.title = "Cold Treats"
=> "Cold Treats"

The questions I have the relates to how to tackle this problem:

  1. I'm not sure if I have to add :category_attributes in the post_params strong parameter method?
  2. Should the logs and debug params show that the Category attributes are nested inside the Post parameter?
  3. In the Category hash parameter there is a blank button key that isn't in my fields_for am I missing something when using the form helpers?
  4. Is the reason because the create action doesn't take the build_category method and I will need to add this to the create action?
  5. Will validations on the Category model (presence: true) be automatically used on the Post form?

Thanks in advance.

Update: missing category_fields inside fields_for block.

Answer

sled picture sled · Nov 6, 2013

Question #1: Yes, you need to add the :category_attributes in the post_params strong parameter method like this:

def post_params
  params.require(:post).permit(:body, category_attributes: [:title])
end

Question #2: Yes, the parameters should be nested, this is a typo in your view because you are not applying the fields_for (plural by the way) in the scope of the parent form builder, also you are not using the category_fields form builder inside the fields_for block!

The view should look like this:

<%= form_for @post do |form| %>

  <%= form.label :body %>
  <%= form.text_area :body %>

  <%= form.fields_for :category do |category_fields| %>
    <%= category_fields.label :title %>
    <%= category_fields.text_field :title %>
  <% end %>

  <%= form.button "Add Post" %>

<% end %>

Question #3: The button parameter might be in the wrong place due to the mixed up form building in your view.

Question #4: You don't need to build the child model in your create action if you accept nested attributes

Question #5: Yes, the validation of the child model is also run and if the validation of the child fails, the parent will also have an error and is not saved to the database.