Rails 中的表单对象
[英] Form Objects in Rails
本文介绍了Rails 中的表单对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!
问题描述
下面的示例代码是一个人为的尝试表单对象的示例,在该示例中,使用表单对象可能有点矫枉过正.尽管如此:它显示了我遇到的问题:
我有两个模型:User
和 Email
:
# app/models/user.rb类用户<申请记录has_many : 电子邮件结尾
<小时>
# app/models/user.rb班级电子邮件<申请记录归属地:用户结尾
我想创建一个表单对象,它创建一个user
记录,然后创建三个关联的email
记录.
这是我的表单对象类:
# app/forms/user_form.rb类用户窗体包括 ActiveModel::Modelattr_accessor :name, :email_forms验证:名称,存在:真定义保存如果有效?坚持!真的别的错误的结尾结尾私人的def坚持!输入表格有效!"puts 我会继续手工创建所有必要的对象"用户 = 用户.创建(名称:名称)email_forms.each 做 |email|电子邮件.创建(用户:用户,email_text:email.email_text)结尾结尾结尾
<小时>
# app/forms/email_form.rb类电子邮件表单包括 ActiveModel::Modelattr_accessor :email_text, :user_id验证:email_text,存在:真定义保存如果有效?坚持!真的别的错误的结尾结尾私人的def坚持!输入表格有效!"# 不要以为我会在这里保留数据# 而是在 user_form 中做结尾结尾
注意:表单对象验证.如果 user_form
的 name
属性为空,或者任何 的 email_text
属性为空,则该 user_form
被认为是无效的email_forms
数组中的 email_form
对象.
为简洁起见:我将介绍使用 user_form
的 new
和 create
操作:
# app/controllers/user_controller.rb类用户控制器 <应用控制器定义新@user_form = UserForm.new@user_form.email_forms = [EmailForm.new, EmailForm.new, EmailForm.new]结尾定义创建@user_form = UserForm.new(user_form_params)如果@user_form.saveredirect_to users_path,注意:用户已成功创建."别的渲染:新结尾结尾私人的def user_form_paramsparams.require(:user_form).permit(:name, {email_forms: [:_destroy, :id, :email_text, :user_id]})结尾结尾
最后:表单本身:
# app/views/users/new.html.erb<h1>新用户</h1><%= render 'form', user_form: @user_form %><%= link_to '返回', users_path %>
<小时>
# app/views/users/_form.html.erb<%= form_for(user_form, url: users_path) 做 |f|%><% if user_form.errors.any?%><div id="error_explanation"><h2><%=pluralize(user_form.errors.count, "error") %>禁止保存此用户:</h2><ul><% user_form.errors.full_messages.each do |message|%><li><%=消息%></li><%结束%>
<%结束%><div class="field"><%= f.label :name %><%= f.text_field :name %>
# MESSY,但想不出更好的方法来做到这一点...<%unique_index = 0%><% user_form.email_forms.each do |email_form|%><div class="field"><%= label_tag "user_form[email_forms][#{unique_index}][email_text]", "电子邮件文本" %><%= text_field_tag "user_form[email_forms][#{unique_index}][email_text]" %>
<% unique_index += 1%><%结束%><div class="actions"><%= f.submit %>
<%结束%>
表单确实呈现:
这里是表单的 html:
我去提交表格.这是参数哈希:
参数:{"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"=>"创建用户表单"}
应该发生的情况是应该重新渲染表单并且没有任何内容保留,因为 form_object 无效:所有三个关联的电子邮件都不能为空.然而:form_object 认为它是有效的,它在 UserForm
上的 persist!
方法中爆炸.它突出显示 Email.create(user: user, email_text: email.email_text)
行并说:
<块引用>
未定义方法`email_text' for ["0", {"email_text"=>"test_email_1"}]:Array
显然有几件事情正在发生:嵌套验证似乎不起作用,而且我无法从 params 哈希重建每封电子邮件.
我已经检查过的资源:
- 这篇文章 看起来很有希望,但我无法正常工作.
- 我尝试使用 virtus gem 和改革导轨 gem 实现.我也为这两个实现发布了未决问题:virtus 在这里尝试 然后 reform-rails 在这里尝试.
- 我曾尝试插入
accepts_nested_attributes
,但无法弄清楚如何将其用于表单对象以及嵌套表单对象(如本代码示例中).部分问题是 has_many
和 accepts_nested_attributes_for
似乎没有包含在 ActiveModel::Model
中.
任何有关让此表单对象执行预期操作的指导将不胜感激!谢谢!
解决方案
完整答案
模型:
#app/models/user.rb类用户<申请记录has_many : 电子邮件结尾#app/models/email.rb班级电子邮件<申请记录归属地:用户结尾
控制器:
#app/controllers/users_controller.rb类用户控制器 <应用控制器定义索引@users = User.all结尾定义新@user_form = UserForm.new@user_form.emails = [EmailForm.new, EmailForm.new, EmailForm.new]结尾定义创建@user_form = UserForm.new(user_form_params)如果@user_form.saveredirect_to users_path,注意:用户已成功创建."别的渲染:新结尾结尾私人的def user_form_paramsparams.require(:user_form).permit(:name, {emails_attributes: [:email_text]})结尾结尾
表单对象:
#app/forms/user_form.rb类用户窗体包括 ActiveModel::Modelattr_accessor :name, :emails验证:名称,存在:真验证:all_emails_validdef emails_attributes=(属性)@emails ||= []attributes.each do |_int, email_params|email = EmailForm.new(email_params)@emails.push(email)结尾结尾定义保存如果有效?坚持!真的别的错误的结尾结尾私人的def坚持!用户 = 用户.new(名称:名称)new_emails = emails.map 做 |email_form|Email.new(email_text: email_form.email_text)结尾user.emails = new_emails用户.保存!结尾def all_emails_validemails.each 做 |email_form|errors.add(:base, "电子邮件必须存在") 除非 email_form.valid?结尾throw(:abort) 如果errors.any?结尾结尾应用程序/表格/email_form.rb类电子邮件表单包括 ActiveModel::Modelattr_accessor :email_text, :user_id验证:email_text,存在:真结尾
观看次数:
app/views/users/new.html.erb<h1>新用户</h1><%= render 'form', user_form: @user_form %><%= link_to '返回', users_path %>#app/views/users/_form.html.erb<%= form_for(user_form, url: users_path) 做 |f|%><% if user_form.errors.any?%><div id="error_explanation"><h2><%=pluralize(user_form.errors.count, "error") %>禁止保存此用户:</h2><ul><% user_form.errors.full_messages.each do |message|%><li><%=消息%></li><%结束%>
<%结束%><div class="field"><%= f.label :name %><%= f.text_field :name %>
<%= f.fields_for :emails do |email_form|%><div class="field"><%= email_form.label :email_text %><%=email_form.text_field :email_text %>
<%结束%><div class="actions"><%= f.submit %>
<%结束%>
The example code below is a contrived example of an attempt at a form object where it is probably overkill to utilize a form object. Nonetheless: it shows the issue I am having:
I have two models: a User
and an Email
:
# app/models/user.rb
class User < ApplicationRecord
has_many :emails
end
# app/models/user.rb
class Email < ApplicationRecord
belongs_to :user
end
I want to create a form object which creates a user
record, and then creates three associated email
records.
Here are my form object classes:
# 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
Notice: the validations on the form objects. A user_form
is considered to be invalid if it's name
attribute is blank, or if the email_text
attribute is left blank for any of the email_form
objects inside it's email_forms
array.
For brevity: I will just be going through the new
and create
action of utilizing the user_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
Lastly: the form itself:
# 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 %>
The form does render:
And here is the form's html:
I go to submit the form. Here is the params hash:
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"}
What should happen is the form should be re-rendered and nothing persisted because the form_object is invalid: All three associated emails must NOT be blank. However: the form_object thinks it is valid, and it blows up in the persist!
method on the UserForm
. It highlights the Email.create(user: user, email_text: email.email_text)
line and says:
undefined method `email_text' for ["0", {"email_text"=>"test_email_1"}]:Array
Clearly there are a couple things going on: The nested validations appear to not be working, and I am having trouble rebuilding each of the emails from the params hash.
Resources I have already examined:
- This Article seemed promising but I was having trouble getting it to work.
- I have attempted an implementation with the virtus gem and the reform-rails gem. I have pending questions posted for both of those implementations as well: virtus attempt here and then reform-rails attempt here.
- I have attempted plugging in
accepts_nested_attributes
, but was having trouble figuring out how to utilize that with a form object, as well as a nested form object (like in this code example). Part of the issue was that has_many
and accepts_nested_attributes_for
do not appear to be included in ActiveModel::Model
.
Any guidance on getting this form object to do what is expected would be very much appreciated! Thanks!
解决方案
Complete Answer
Models:
#app/models/user.rb
class User < ApplicationRecord
has_many :emails
end
#app/models/email.rb
class Email < ApplicationRecord
belongs_to :user
end
Controller:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def index
@users = User.all
end
def new
@user_form = UserForm.new
@user_form.emails = [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, {emails_attributes: [:email_text]})
end
end
Form Objects:
#app/forms/user_form.rb
class UserForm
include ActiveModel::Model
attr_accessor :name, :emails
validates :name, presence: true
validate :all_emails_valid
def emails_attributes=(attributes)
@emails ||= []
attributes.each do |_int, email_params|
email = EmailForm.new(email_params)
@emails.push(email)
end
end
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
user = User.new(name: name)
new_emails = emails.map do |email_form|
Email.new(email_text: email_form.email_text)
end
user.emails = new_emails
user.save!
end
def all_emails_valid
emails.each do |email_form|
errors.add(:base, "Email Must Be Present") unless email_form.valid?
end
throw(:abort) if errors.any?
end
end
app/forms/email_form.rb
class EmailForm
include ActiveModel::Model
attr_accessor :email_text, :user_id
validates :email_text, presence: true
end
Views:
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>
<%= f.fields_for :emails do |email_form| %>
<div class="field">
<%= email_form.label :email_text %>
<%= email_form.text_field :email_text %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
这篇关于Rails 中的表单对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
查看全文