ruby on rails - modelo - Rails 4 NO actualizando atributos anidados
formularios anidados rails (2)
Como @ Philip7899 mencionó como un comentario en la respuesta aceptada, permitir que el usuario establezca la id
significa que podrían "robar" los registros secundarios que pertenecen a otro usuario.
Sin embargo , Rails accepts_nested_attributes_for
realidad comprueba el id
y plantea:
ActiveRecord::RecordNotFound:
Couldn''t find Answer with ID=5 for Questionnaire with ID=5
Básicamente, las identificaciones se buscan en la asociación de niños (de nuevo, como dijo @glampr). Por lo tanto, el registro hijo que pertenece a otro usuario no se encuentra.
En última instancia, 401 es el estado de respuesta (a diferencia del 404 habitual de ActiveRecord::RecordNotFound
)
Sigue algunos códigos que usé para probar el comportamiento.
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
En conclusión , agregar el id
a los parámetros permitidos como se indicó anteriormente es correcto y seguro .
Carriles fascinantes.
Problema: en lugar de actualizar los atributos anidados, se crean sobre los atributos anidados existentes cuando #update
acción #update
del features_controller.rb
asociado
Causa probable: creo que el problema radica en mi falta de comprensión en el form_for
Rails. Creo que el desglose está en mis vistas, cómo renderizo los atributos anidados persistentes y / o cómo no puedo especificar el ID del atributo anidado, lo que hace que se cree simplemente uno nuevo.
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 (simplificado)
= 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"
Estoy seguro de que hay una mejor manera de lograr el if @feature.new_record?
comprobar. También estoy usando algunos enlaces de Javascript para crear formas de atributos anidados dinámicos (que he omitido ), muy influenciados por el Formulario de modelo anidado de Railscast # 196 (revisado)
Me encantaría una implementación realmente agradable de Rails-y para tratar con este tipo de formas anidadas.
Intente agregar :id
a la parte :scenario_attributes
de su método feature_params
. Solo tienes el campo de descripción y la capacidad de permitir una destrucción.
def feature_params
# added => before nested attributes
params.require(:feature).permit(:id, :title, :narrative, :price, :eta, scenarios_attributes => [:id, :description, :_destroy])
end
Como lo sugirió @vinodadhikary, ya no necesita verificar si la función es un nuevo registro, ya que Rails, específicamente utilizando el método form_for
, lo hará por usted.
Actualizar:
No necesita definir if @feature.new_record? ... else
if @feature.new_record? ... else
en tu forma. Rails lo cuidará cuando uses form_for
. Rails verifica si la acción se create
o update
función de object.persisted?
Entonces, puedes actualizar tu formulario a:
= 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"