Modelos asociados y un formulario nested con validación no funciona.

Update2: He limpiado el código, que parece haber resuelto algunos de los problemas. He publicado el nuevo código como una nueva pregunta aquí .

Actualización: Organización y usuario tienen una relación 1: muchos. Mi pregunta se refiere a un formulario de registro unido donde se requieren tanto una organización como un usuario. Después de la ayuda de maxcal en la publicación original, escribí un nuevo método de create para mi formulario nested (“la organización tiene muchos usuarios”), como se muestra a continuación. También agregué begin...rescue...end al método create . La situación / problema ahora:

  • Enviado con toda la información válida funciona correctamente.
  • Enviado con información no válida para la organización (no importa si el usuario también es inválido o no), muestra la página con los mensajes de error, como queremos, pero solo muestra errores para los detalles de la organización. Además, para los detalles del usuario, ha vaciado todos los campos, lo que no debería.
  • Enviado con información no válida solo para el usuario, vuelve a presentar el formulario pero sin ningún mensaje de error y todos los campos para el usuario se han vaciado.

¿Alguien tuvo una idea de lo que está mal con el código? El problema parece ser más con el usuario nested que con la organización (el padre). Además, users_attributes.empty? no funciona, ya que un formulario enviado vacío todavía incluye dichos atributos, de acuerdo con el registro:

 Parameters: {"utf8"=>"✓", "authenticity_token"=>"***", "organization"=>{"name"=>"", "bag"=>"", "users_attributes"=>{"0"=>{"email"=>"", "username"=>"", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "usertype"=>"2", "admin"=>"true"}}}, "commit"=>"Register"} 

.

  def create @organization = Organization.new(new_params.except(:users_attributes)) begin if users_attributes.empty? @organisation.errors.add(:users, 'No user provided') end @organization.transaction do @organization.save! if users_attributes.any? @organization.users.create!(users_attributes) end end rescue ActiveRecord::RecordInvalid => invalid if @organization.persisted? if @organization.users.any? @organization.users.each do |single_user| single_user.send_activation_email end end flash[:success] = "Confirmation email sent." redirect_to root_url else @organization.users.build if @organization.users.blank? render :new end end end private # converts the hash of nested attributes hashes to an array def users_attributes new_params[:users_attributes].values end end 


Pregunta original: Tengo dos modelos asociados y una forma anidada con validación. Desafortunadamente, no está funcionando. 1) Al sembrar , genera el error Validation failed: Users organization can't be blank . Anteriormente publiqué una pregunta sobre esto y prematuramente llegué a la conclusión de que la había resuelto. No tiene 2) Al enviar mi formulario de registro nested con todos los campos rellenados correctamente, The form contains 1 error. Users organization can't be blank mensaje de error flash The form contains 1 error. Users organization can't be blank The form contains 1 error. Users organization can't be blank .

¿Cómo debo ajustar mi código para resolver estos problemas?

Archivos de modelo:

 #User model belongs_to :organization, inverse_of: :users validates_presence_of :organization_id, :unless => 'usertype == 1' # Organization model has_many :users, dependent: :destroy accepts_nested_attributes_for :users, :reject_if => :all_blank, :allow_destroy => true validate :check_user private def check_user if users.empty? errors.add(:base, 'User not present') end end 

Métodos de control de la organización

  def new @organization = Organization.new @user = @organization.users.build end def create @organization = Organization.new(new_params) if @organization.save @organization.users.each do |single_user| single_user.send_activation_email # Method in user model file. end flash[:success] = "Confirmation email sent." redirect_to root_url else @organization.users.build if @organization.users.blank? render 'new' end end def new_params params.require(:organization).permit(:name, :bag, users_attributes: [:email, :username, :usertype, :password, :password_confirmation]) end 

La forma:

              

En mi archivo de semillas tengo:

 Organization.create!(name: "Fictious business", address: Faker::Address.street_address, city: Faker::Address.city, users_attributes: [email: "helpst@example.com", username: "helpyzghtst", usertype: 2, password: "foobar", password_confirmation: "foobar"]) 

El registro en el error al enviar el formulario de registro:

 Started POST "/organizations" Processing by OrganizationsController#create as HTML Parameters: {"utf8"=>"✓", "authenticity_token"=>"0cR***Nnx4iReMiePg==", "organization"=>{"name"=>"test21", "bag"=>"tes21", "users_attributes"=>{"0"=>{"email"=>"test21@example.com", "username"=>"test21", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "usertype"=>"2"}}}, "commit"=>"Register"} (0.2ms) BEGIN User Exists (1.1ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER('test21@example.com') LIMIT 1 (0.7ms) SELECT "users"."email" FROM "users" ORDER BY "users"."username" ASC User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE LOWER(users"."username") = LOWER('test21') LIMIT 1 Organization Exists (0.6ms) SELECT 1 AS one FROM "organizations" WHERE LOWER("organizations"."name") = LOWER('test21') LIMIT 1 Organization Exists (0.4ms) SELECT 1 AS one FROM "organizations" WHERE LOWER("organizations"."bag") = LOWER('tes21') LIMIT 1 (0.2ms) ROLLBACK 

Su validación no funciona debido a un Catch-22

Para solicitar este trabajo, tendrías que estar loco; Pero si estás loco, eres inaceptable.

Los modelos ActiveRecord obtienen su ID de la base de datos cuando se guardan. Pero la validación en el usuario nested se ejecuta antes de que la organización se inserte en la base de datos.

Se podría suponer que simplemente marcando validates_presence_of lugar pasaría:

 validates_presence_of :organization, unless: -> { usertype == 1 } 

Por desgracia no. Para que validates_presence_of :organization pase la organización debe persistir en la base de datos. Catch-22 de nuevo.

Para que la validación pase, tendríamos que dividir la creación de la organización y el usuario en dos pasos:

 org = Organization.create(name: 'M & M Enterprises') user = org.users.build(username: 'milo_minderbinder', ...) user.valid? 

Desafortunadamente, esto significa que no se puede utilizar accepts_nested_attributes_for :users – bueno, al menos no directamente.

Al utilizar una transacción , podemos insertar la organización en la base de datos y revertirla si el usuario no es válido.

 def create @organization = Organization.new(new_params.except(:users_attributes)) @organization.transaction do @organization.save! if new_params[:users_attributes].any? @organization.users.create!(new_params[:users_attributes]) end end if @organization.persisted? # ... if @organization.users.any? # send emails ... end else @organization.users.build if @organization.users.blank? render :new end end 

Preguntas de seguimiento

¿Usamos @organization.persisted? ya que presumiblemente queremos redirigirnos a la organización recién creada, no importa si existe un registro de Usuario creado.

¿Porque los correos electrónicos son enviados a los usuarios? No debería importar, ya que la organización se retrotrae si no se crea ningún usuario.

La transacción no se revierte si no se crea ningún usuario. Solo si el usuario (s) no puede guardar debido a parámetros no válidos. Esto se basa en su requisito:

Pero una organización también puede (temporalmente) no tener usuarios.

Si necesita que la organización @ sea inválida sin usuarios, podría hacer:

  @organisation.errors.add(:users, 'No users provided') unless new_params[:users_attributes].any? @organization.transaction do @organization.save! if new_params[:users_attributes].any? @organization.users.create!(new_params[:users_attributes]) end end 

¿Usarías @organization.users.any? Para comprobar si hay algún usuario. @organization.users.persisted? no funcionará desde .persisted? es un método en instancias de modelo, no colecciones.

En una nota diferente, asumo que no es posible sobrescribir / actualizar una organización / usuario existente con este método (que no debería ser) en lugar de crear siempre un nuevo registro.

Correcto, ya que esto siempre emitirá dos instrucciones de inserción SQL, no alterará los registros existentes.

Sin embargo, depende de usted crear validaciones que garanticen la unicidad de las columnas de la base de datos (es decir, no desea varios registros con el mismo usuario.email o organiation.name).

En el lado positivo, ninguna de estas advertencias se aplica al actualizar una organización existente:

 def update @organisation.update(... params for org and and users ...) end 

Ya que no obtienes el dilema del huevo o la gallina cuando validas a los usuarios.