recursos rails formularios form anidados accepts_nested_attributes_for ruby-on-rails activerecord nested-forms nested-attributes

ruby-on-rails - accepts_nested_attributes_for - rails formularios anidados



Utilice el modelo anidado de rieles para*crear*objeto externo y simultáneamente*editar*objeto anidado existente? (3)

Intente agregar un campo oculto para la identificación del usuario en el formulario anidado:

<%=user_form.hidden_field :id%>

El guardado anidado usará esto para determinar si es una creación o una actualización para el usuario.

Usando Rails 2.3.8

El objetivo es crear un Blogger mientras se actualiza simultáneamente el modelo de Usuario anidado (en caso de que la información haya cambiado, etc.), O crear un nuevo usuario si aún no existe.

Modelo:

class Blogger < ActiveRecord::Base belongs_to :user accepts_nested_attributes_for :user end

Controlador de Blogger:

def new @blogger = Blogger.new if user = self.get_user_from_session @blogger.user = user else @blogger.build_user end # get_user_from_session returns existing user # saved in session (if there is one) end def create @blogger = Blogger.new(params[:blogger]) # ... end

Formar:

<% form_for(@blogger) do |blogger_form| %> <% blogger_form.fields_for :user do |user_form| %> <%= user_form.label :first_name %> <%= user_form.text_field :first_name %> # ... other fields for user <% end %> # ... other fields for blogger <% end %>

Funciona bien cuando estoy creando un nuevo usuario a través del modelo anidado, pero falla si el usuario anidado ya existe y tiene ID (en cuyo caso me gustaría simplemente actualizar ese usuario).

Error:

Couldn''t find User with ID=7 for Blogger with ID=

Esta pregunta SO trata de un problema similar, y solo la respuesta sugiere que Rails simplemente no funcionará de esa manera. La respuesta sugiere simplemente pasar el ID del elemento existente en lugar de mostrar el formulario, lo que funciona bien, excepto que me gustaría permitir modificaciones a los atributos del Usuario si los hay.

¿Rails profundamente anidados forma using belong_to no funciona?

Sugerencias? Esto no parece ser una situación particularmente poco común, y parece que debe haber una solución.


Estoy usando Rails 3.2.8 y me encuentro con el mismo problema.

Parece que lo que está intentando hacer (asignar / actualizar un registro guardado existente a una asociación belongs_to ( user ) de un nuevo modelo padre no guardado ( Blogger ) simplemente no es posible en Rails 3.2.8 (o Rails 2.3.8, para eso importa, aunque espero que haya actualizado a 3.x por ahora) ... no sin algunas soluciones.

Encontré 2 soluciones alternativas que parecen funcionar (en Rails 3.2.8). Para entender por qué funcionan, primero debe entender el código donde se produjo el error.

Comprender por qué ActiveRecord está provocando el error ...

En mi versión de activerecord (3.2.8), el código que maneja la asignación de atributos anidados para una asociación belongs_to se puede encontrar en lib/active_record/nested_attributes.rb:332 y se ve así:

def assign_nested_attributes_for_one_to_one_association(association_name, attributes, assignment_opts = {}) options = self.nested_attributes_options[association_name] attributes = attributes.with_indifferent_access if (options[:update_only] || !attributes[''id''].blank?) && (record = send(association_name)) && (options[:update_only] || record.id.to_s == attributes[''id''].to_s) assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy], assignment_opts) unless call_reject_if(association_name, attributes) elsif attributes[''id''].present? && !assignment_opts[:without_protection] raise_nested_attributes_record_not_found(association_name, attributes[''id'']) elsif !reject_new_record?(association_name, attributes) method = "build_#{association_name}" if respond_to?(method) send(method, attributes.except(*unassignable_keys(assignment_opts)), assignment_opts) else raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?" end end end

En la sentencia if , si ve que ha pasado una identificación de usuario ( !attributes[''id''].blank? ), Intenta obtener el registro de user existente de la asociación de user del blogger ( record = send(association_name) donde association_name es :user ).

Pero como se trata de un objeto Blogger recientemente creado, blogger.user inicialmente será nil , por lo que no llegará a la llamada assign_to_or_mark_for_destruction en esa rama que maneja la actualización del record existente. Esto es lo que debemos evitar (ver la siguiente sección).

Por lo tanto, se mueve a la rama 1st else if , que nuevamente comprueba si hay una identificación de usuario presente ( attributes[''id''].present? ). Está presente, por lo que comprueba la siguiente condición, que es !assignment_opts[:without_protection] .

Dado que está inicializando su nuevo objeto de Blogger con Blogger.new(params[:blogger]) (es decir, sin pasar as: :role o without_protection: true ), usa los without_protection: true assignment_opts predeterminados de {} . !{}[:without_protection] es verdadero, por lo que procede a raise_nested_attributes_record_not_found , que es el error que vio.

Finalmente, si no se tomó ninguna de las otras 2 ramas si, se comprueba si debe rechazar el nuevo registro y (si no) procede a construir un nuevo registro. Esta es la ruta que sigue en el caso "crea un nuevo usuario si aún no existe" en el caso que mencionaste.

Solución 1 (no recomendada): without_protection: true

La primera solución que pensé, pero no recomendaría, era asignar los atributos al objeto de Blogger usando without_protection: true (Rails 3.2.8).

Blogger.new(params[:blogger], without_protection: true)

De esta manera se salta el primer elsif y va al último elsif , que crea un nuevo usuario con todos los atributos de los params, incluyendo :id . En realidad, no sé si eso hará que actualice el registro de usuario existente como quisieras (probablemente, no, no he probado realmente esa opción), pero al menos evita el error ... :)

Solución 2 (recomendada): configure self.user en user_attributes=

Pero la solución alternativa que recomendaría más que eso es inicializar / establecer la asociación de user desde el parámetro: id para que se use la primera rama if y se actualiza el registro existente en la memoria como desee ...

accepts_nested_attributes_for :user def user_attributes=(attributes) if attributes[''id''].present? self.user = User.find(attributes[''id'']) end super end

Para poder sobrescribir el atributo de atributos anidados así y llamar a super , necesitarás usar edge rails o incluir el parche de monos que publiqué en https://github.com/rails/rails/pull/ 2945 . Alternativamente, puede llamar a assign_nested_attributes_for_one_to_one_association(:user, attributes) directamente desde su user_attributes= setter en lugar de llamar a super .

Si desea hacerlo siempre cree un nuevo registro de usuario y no actualice el usuario existente ...

En mi caso, terminé decidiendo que no quería que las personas pudieran actualizar los registros de usuario existentes de este formulario, así que terminé usando una pequeña variación de la solución anterior:

accepts_nested_attributes_for :user def user_attributes=(attributes) if user.nil? && attributes[''id''].present? attributes.delete(''id'') end super end

Este enfoque también evita que ocurra el error, pero lo hace de manera un poco diferente.

Si se pasa una identificación en los parámetros, en lugar de usarla para inicializar la asociación de user , simplemente elimino la identificación aprobada para que vuelva a formar un new usuario del resto de los parámetros de usuarios enviados.


Me encontré con el mismo error en los carriles 3.2. El error ocurrió al usar un formulario anidado para crear un nuevo objeto con una relación de pertenencia para un objeto existente. El enfoque de Tyler Rick no funcionó para mí. Lo que encontré para trabajar fue establecer la relación después de la inicialización del objeto y luego establecer los atributos de los objetos. Un ejemplo de esto es el siguiente ...

@report = Report.new() @report.user = current_user @report.attributes = params[:report]

suponiendo que params se parece a algo ... {: informe => {: nombre => "nombre",: atributos_usuarios => {: id => 1, {: atributos_componentes => {"1" => {: nombre => " nombre de la cosa "}}}}}}