16

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

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

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 (упр lified)

= 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 Nested Model Form (revised)

Я хотел бы действительно хороший Rails-у реализацию решения такого рода вложенные формы.

+2

Добавил бы ': id' в часть': scene_attributes' вашего метода 'feature_params' исправить это? У вас есть только поле описания и возможность разрешить уничтожение. – jason328

+0

да, это точно! – pruett

ответ

39

Попробуйте добавить :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, сделает это за вас.

Update:

Вам не нужно определить if @feature.new_record? ... else в вашей форме. При использовании form_for он позаботится о 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" 
+0

Ahh хорошая точка. Моя ошибка для этого. – jason328

+1

Я бы также предложил обновить код формы, проверяя 'feature.new_record?' Не нужно как 'form_for' обрабатывает это сам. – vee

+0

Вы более осведомлены о проблеме, чем я, вы хотите ответить на нее? Я имею в виду это искренне :) – jason328

4

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

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

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

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

В конечном счете, является статус ответа (в отличие от обычного 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'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 в разрешенном Params, как указано выше, является правильным и безопасными.

Удивительные рельсы.

+0

Как сделать это красиво, если возможно несколько вложенных атрибутов? Предположим, что есть комментарии, связанные с * вопросником *. Я просто присваиваю * id * всем возможным вложенным параметрам? Кажется, это перебор. В моем случае у меня есть несколько 1: 1 с вложенными записями, существующими только для одной конкретной модели на запись. Думайте причудливое наследование. – mlt

+1

В моем личном опыте, если он становится слишком сложным, вам лучше уйти от вложенных атрибутов и переключиться на объект формы. Это хороший ресурс http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ – ecoologic

+0

Хорошо читайте! Я закончил с 'p.each_pair {| k, v | v [: id] = o.id, если k.end_with? ('_ attributes')} 'на данный момент, поскольку у меня есть отношение 1: 1, где * p * - родительские параметры и * o * - родительский объект. – mlt