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!