Rails 4 NOT updating nested attributes Rails 4 NOT updating nested attributes ruby-on-rails ruby-on-rails

Rails 4 NOT updating nested attributes


Try adding :id to the :scenario_attributes portion of your feature_params method. You only have the description field and the ability to allow a destroy.

def feature_params  # added => before nested attributes  params.require(:feature).permit(:id, :title, :narrative, :price, :eta, scenarios_attributes => [:id, :description, :_destroy])end

As @vinodadhikary suggested, you no longer need to check if feature is a new record, since Rails, specifically using the form_for method, will do that for you.

Update:

You don't need to define if @feature.new_record? ... else in your form. It will be taken care by Rails when you use form_for. Rails checks if the action is going to be create or update based on object.persisted?, so, you can update your form to:

= form_for [@project, @feature] do |f|  ...  = f.fields_for :scenarios, @feature.scenarios.build do |builder|    = builder.label :description, "Scenario"    = builder.text_area :description, rows: "3", autocomplete: "off"


As @Philip7899 mentioned as a comment in the accepted answer, allowing the user to set the id means that they could "steal" children records belonging to another user.

However, Rails accepts_nested_attributes_for actually checks the id and raises:

ActiveRecord::RecordNotFound:  Couldn't find Answer with ID=5 for Questionnaire with ID=5

Basically the ids are looked for in the children association (again, as said by @glampr). Therefor, the child record belonging to another user is not found.

Ultimately, 401 is the response status (unlike the usual 404 from ActiveRecord::RecordNotFound)

Follows some code I used to test the behaviour.

let :params do  {    id: questionnaire.id,    questionnaire: {      participation_id: participation.id,      answers_attributes: answers_attributes    }  }endlet :evil_params do  params.tap do |params|    params[:questionnaire][:answers_attributes]['0']['id'] = another_participant_s_answer.id.to_s  endendit "doesn't mess with other people's answers" do  old_value = another_participant_s_answer.value  put :update, evil_params  expect(another_participant_s_answer.reload.value).to eq(old_value) # pass  expect(response.status).to eq(401) # passend

In conclusion, adding the id to the permitted params as stated above is correct and safe.

Fascinating Rails.