Rails 4 НЕ обновляет вложенные атрибуты

Проблема: Вместо обновления вложенных атрибутов они создаются поверх существующих вложенных атрибутов, когда я нажимаю на действие #update связанного features_controller.rb

Вероятная причина: Я думаю, что проблема заключается в моем непонимании в Rails 'form_for. Я думаю, что разбивка в моих взглядах, как я отображаю сохраняющиеся вложенные атрибуты и/или , как я не могу указать вложенный атрибут id, заставляя его просто создать новый

feature.rb

class Feature < ActiveRecord::Base
  ...
  has_many :scenarios
  accepts_nested_attributes_for :scenarios,
    allow_destroy: true,
    reject_if: :all_blank
  ...
end

features_controller.rb

def update
  ...
  project = Project.find(params[:project_id])
  @feature = Feature.find(params[:id])

  if @feature.update_attributes(feature_params)
    # checking feature_params looks good...
    # feature_params['scenarios'] => { <correct object hash> }

    redirect_to project
  else
    render :edit
  end
end

...

private
def feature_params
  params.require(:feature).permit(:title, :narrative, :price, :eta, scenarios_attributes[:description, :_destroy])
end

_form.html.haml(упрощенный)

= form_for [@project, @feature] do |f|
  ...
  - if @feature.new_record? -# if we are creating new feature
    = f.fields_for :scenarios, @feature.scenarios.build do |builder|
      = builder.label :description, "Scenario"
      = builder.text_area :description, rows: "3", autocomplete: "off"

  - else -# if we are editing an existing feature
    = f.fields_for :scenarios do |builder|
      = builder.label :description, "Scenario"
      = builder.text_area :description, rows: "3", autocomplete: "off"

Я уверен, что есть лучший способ достичь проверки if @feature.new_record?. Я также использую несколько крючков Javascript для создания динамических вложенных форм атрибутов (которые я забыл), сильно зависящих от Railscast # 196 Вложенная модель формы (пересмотренная )

Мне бы очень понравилась реализация Rails-y с этими типами вложенных форм.

Ответы

Ответ 1

Попробуйте добавить :id к части :scenario_attributes вашего метода feature_params. У вас есть только поле описания и возможность разрешить уничтожение.

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

Как предположил @vinodadhikary, вам больше не нужно проверять, является ли функция новой записью, так как Rails, особенно используя метод form_for, сделает это за вас.

Обновление:

Вам не нужно определять if @feature.new_record? ... else в вашей форме. При использовании form_for он будет обработан Rails. Rails проверяет, будет ли действие create или update на основе object.persisted?, поэтому вы можете обновить форму:

= 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"

Ответ 2

Как @Philip7899 упоминается как комментарий в принятом ответе, позволяя пользователю установить id означает, что они могут "украсть" записи для детей, принадлежащие другому пользователю.

Однако Rails accepts_nested_attributes_for фактически проверяет id и повышает:

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

В основном идентификаторы ищутся в дочерней ассоциации (опять же, как сказано в @glampr). Поэтому дочерняя запись, принадлежащая другому пользователю, не найдена.

В конечном счете 401 - это статус ответа (в отличие от обычного 404 из ActiveRecord::RecordNotFound)

Выполняет некоторый код, который я использовал для проверки поведения.

let :params do
  {
    id: questionnaire.id,
    questionnaire: {
      participation_id: participation.id,
      answers_attributes: answers_attributes
    }
  }
end

let :evil_params do
  params.tap do |params|
    params[:questionnaire][:answers_attributes]['0']['id'] = another_participant_s_answer.id.to_s
  end
end

it "doesn't mess with other people 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) # pass
end

В заключение добавление id к разрешенным параметрам, как указано выше, является правильным и безопасным.

Увлекательные рельсы.