web-dev-qa-db-ja.com

Rails 4ネストされた属性を更新しない

問題:ネストされた属性を更新する代わりに、作成されています関連付けられた#updatefeatures_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実装が大好きです。

20
pruett

: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になるかどうかを確認します。 updateobject.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"
41
jason328

受け入れられた回答のコメントとして@ Philip7899が述べたように、ユーザーがidを設定できるようにすることは、別のユーザーに属する子レコードをcould「盗む」ことを意味します。

ただし、Rails accepts_nested_attributes_forは実際にidをチェックし、以下を発生させます。

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

基本的に、IDは子協会で検索されます(@glamprが言ったように)。そのため、他のユーザーに属する子レコードが見つかりません。

最終的に、401は応答ステータスです(ActiveRecord::RecordNotFoundからの通常の404とは異なります)

動作をテストするために使用したいくつかのコードに従います。

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'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) # pass
end

結論、上記のように許可されたパラメータにidを追加することは正しく、safeです。

魅力的なレール。

4
ecoologic