user1801879
user1801879

Reputation: 822

Why is this rails nested attributes assignment not working

I've a Rails model called Biography, and biography has one lifestyle.

class Lifestyle < ActiveRecord::Base
    belongs_to :biography
end

class Biography < ActiveRecord::Base
  has_one :lifestyle, dependent: :destroy
  accepts_nested_attributes_for :lifestyle
end

And In my BiographyController I've this:

def update_biography
  biography = current_user.biography
  logger.debug("params are: #{params}")
  logger.debug("biography_params are: #{biography_params}")
  if biography.update(biography_params)
    render :json => biography
  else
    render :json => { :errors => biography.errors.full_messages }, :status => 400
  end
end

def biography_params
            params.require(:biography).permit(
                                      :disability, :hiv_positive, :blood_type,
                                      lifestyle_attributes: [:id, :diet, :smoke, :drink])
end

And this is what I get from my two logger.debug statements above:

params are: {"lifestyle_attributes"=>{"diet"=>"2", "smoke"=>"false", "drink"=>"2"}, "disability"=>"false", "hiv_positive"=>"false", "blood_type"=>"3", "controller"=>"biographies", "action"=>"update_biography", "id"=>"4", "biography"=>{"disability"=>"false", "hiv_positive"=>"false", "blood_type"=>"3"}}

biography_params are: {"disability"=>"false", "hiv_positive"=>"false", "blood_type"=>"3"}

Why is that my biography_params do not contain lifestyle_attributes even though I've accepts_nested_attributes_for statment in the Biography model, and also defining association between Biography and Lifestyle in the models? I've also added lifestyle_attributes in the strong parameters permit list.

However, if I run this in rails console the assignment does work:

b = Biography.first
b.update("lifestyle_attributes"=>{"diet"=>"2", "smoke"=>"false", "drink"=>"2"})

Upvotes: 2

Views: 1706

Answers (3)

Richard Peck
Richard Peck

Reputation: 76784

The problem is that lifestyle_attributes are not a part of the biography params hash. You should have:

params: {
   biography: {
      lifestyle_attributes: {
         ...
      }
   }
}

This will allow the params method to access the data properly.

To explain how it works, you need to look at how the ActionController::Parameters class works:

Returns a new ActionController::Parameters instance that includes only the given filters and sets the permitted attribute for the object to true.

Each time you use params.require(:x).permit(:y), it will return a new hash with only the permitted params. These permitted params have to be nested within the required param.

As you've demonstrated, this works well...

biography_params are: {"disability"=>"false", "hiv_positive"=>"false", "blood_type"=>"3"}

The problem is that because lifestyle_attributes is not nested under biography, its parameters are not returned after you call the params method.


The fix for this will be in your form:

#app/views/biographies/new.html.erb
<%= form_for @biography do |f| %>
   <%= ... biography attributes %>
   <%= f.fields_for :lifestyle do |l| %>
      <%= lifestyle fields %>
   <% end %>
   <%= f.submit %>
<% end %>

I don't know how you've done it currently, but somehow, you've attached lifestyle attributes outside of the biography hash.

Upvotes: 1

mhaseeb
mhaseeb

Reputation: 1779

require and permit are actually the method of ActionController::Parameters. The require which in this case is the :biography needs to be present in the hash you are sending from your backbone views.

The require method ensures that a specific parameter is present, and if it's not provided, the require method throws an error. It returns an instance of ActionController::Parameters for the key passed into require i.e :biography.

Upvotes: 2

Rajarshi Das
Rajarshi Das

Reputation: 12340

You can try

params = {biography: {first_name: "new", last_name: "user", disability: false, hiv_positive: false, blood_type: 3,  "lifestyle_attributes: {diet: "2", smoke: "false", drink: "2"}}

If you do not want biography: on your params you can ignore require(:biography) on params.require(:biography) to just params.permit(...)

Hope now it will work

You get more info on Nested Attributes

Upvotes: 1

Related Questions