Objetos de formulário no Rails

O código de exemplo abaixo é um exemplo artificial de uma tentativa de um objeto de formulário em que provavelmente é um exagero utilizar um objeto de formulário. No entanto: mostra o problema que estou tendo:

Eu tenho dois modelos: umUser e umEmail:

# app/models/user.rb
class User < ApplicationRecord
  has_many :emails
end
# app/models/user.rb
class Email < ApplicationRecord
  belongs_to :user
end

Eu quero criar umobjeto de formulário o que cria umuser registro e cria três associadosemail registros.

Aqui estão as minhas classes de objeto de formulário:

# app/forms/user_form.rb
class UserForm 
  include ActiveModel::Model

  attr_accessor :name, :email_forms

  validates :name, presence: true

  def save
    if valid?
      persist!
      true
    else
      false
    end
  end

  private

  def persist!
    puts "The Form is VALID!"
    puts "I would proceed to create all the necessary objects by hand"

    user = User.create(name: name)
    email_forms.each do |email|
      Email.create(user: user, email_text: email.email_text)
    end
  end
end
# app/forms/email_form.rb
class EmailForm 
  include ActiveModel::Model

  attr_accessor :email_text, :user_id

  validates :email_text, presence: true

  def save
    if valid?
      persist!
      true
    else
      false
    end
  end

  private

  def persist!
    puts "The Form is VALID!"
    # DON'T THINK I WOULD PERSIST DATA HERE
    # INSTEAD DO IT IN THE user_form
  end
end

Aviso prévio: avalidações nos objetos de formulário. UMAuser_form é considerado inválido se forname atributo estiver em branco ou se oemail_text atributo é deixado em branco para qualquer um dosemail_form objetos dentro éemail_forms array.

Por uma questão de brevidade: vou apenas passar pelonew ecreate ação de utilizar ouser_form:

# app/controllers/user_controller.rb
class UsersController < ApplicationController

  def new
    @user_form = UserForm.new
    @user_form.email_forms = [EmailForm.new, EmailForm.new, EmailForm.new]
  end

  def create
    @user_form = UserForm.new(user_form_params)

    if @user_form.save
      redirect_to users_path, notice: 'User was successfully created.' 
    else
      render :new
    end 
  end

  private

  def user_form_params
    params.require(:user_form).permit(:name, {email_forms: [:_destroy, :id, :email_text, :user_id]})
  end
end

Por fim: o próprio formulário:

# app/views/users/new.html.erb
<h1>New User</h1>

<%= render 'form', user_form: @user_form %>
<%= link_to 'Back', users_path %>
# app/views/users/_form.html.erb
<%= form_for(user_form, url: users_path) do |f| %>
  <% if user_form.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(user_form.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% user_form.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %>
    <%= f.text_field :name %>
  </div>

  # MESSY, but couldn't think of a better way to do this...
  <% unique_index = 0 %>
  <% user_form.email_forms.each do |email_form| %>
    <div class="field">
      <%= label_tag "user_form[email_forms][#{unique_index}][email_text]", "Email Text" %>
      <%= text_field_tag "user_form[email_forms][#{unique_index}][email_text]" %>
    </div>
    <% unique_index += 1 %>
  <% end %>


  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

O formulário renderiza:

E aqui está o html do formulário:

Vou enviar o formulário. Aqui está o hash dos parâmetros:

Parameters: {"utf8"=>"✓", "authenticity_token"=>”abc123==", "user_form"=>{"name"=>"neil", "email_forms"=>{"0"=>{"email_text"=>"test_email_1"}, "1"=>{"email_text"=>"test_email_2"}, "2"=>{"email_text"=>""}}}, "commit"=>"Create User form"}

O que deve acontecer é que o formulário deve ser renderizado novamente e nada persistiu porque o formulário_objeto é inválido: Todos os três emails associados NÃO devem ficar em branco. No entanto: o form_object pensa que é válido e explode nopersist! método noUserForm. Destaca aEmail.create(user: user, email_text: email.email_text) linha e diz:

método indefinido `email_text 'para [" 0 ", {" email_text "=>" test_email_1 "}]: Matriz

Claramente, existem algumas coisas acontecendo: As validações aninhadas parecem não estar funcionando, e estou tendo problemas para reconstruir cada um dos emails do hash dos parâmetros.

Recursos que eu já examinei:

Este artigo parecia promissor, mas eu estava tendo problemas para fazê-lo funcionar.Eu tentei uma implementação com a jóia virtus e a jóia de reforma-trilhos. Também tenho perguntas pendentes postadas para ambas as implementações:virtus tentativa aqui e depoisreforma-trilhos tentativa aqui.Eu tentei conectaraccepts_nested_attributes, mas estava tendo problemas para descobrir como utilizá-lo com um objeto de formulário e com um objeto de formulário aninhado (como neste exemplo de código). Parte da questão era quehas_many eaccepts_nested_attributes_for não parecem estar incluídos noActiveModel::Model.

Qualquer orientação sobre como obter este objeto de formulário para fazer o que é esperado seria muito apreciada! Obrigado!

questionAnswers(1)

yourAnswerToTheQuestion