我很难搞清楚如何创建一个form_object,为与virtus gem的has_many
关联创建多个关联对象。
下面是一个人为的例子,表单对象可能过度,但它确实显示了我遇到的问题:
假设有一个user_form
对象创建了一个user
记录,然后是一对关联的user_email
记录。以下是模型:
# models/user.rb
class User < ApplicationRecord
has_many :user_emails
end
# models/user_email.rb
class UserEmail < ApplicationRecord
belongs_to :user
end
我继续创建一个表单对象来表示用户表单:
# app/forms/user_form.rb
class UserForm
include ActiveModel::Model
include Virtus.model
attribute :name, String
attribute :emails, Array[EmailForm]
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)
# emails.each do |email_form|
# UserEmail.create(user: user, email: email_form.email_text)
# end
end
end
我会在UserForm
课程中注意到我有attribute :emails, Array[EmailForm]
。这是尝试验证和捕获将为关联的user_email
记录保留的数据。以下是Embedded Value
记录的user_email
表单:
# app/forms/email_form.rb
# Note: this form is an "Embedded Value" Form Utilized in user_form.rb
class EmailForm
include ActiveModel::Model
include Virtus.model
attribute :email_text, String
validates :email_text, presence: true
end
现在我将继续显示设置user_form的users_controller
。
# app/controllers/users_controller.rb
class UsersController < ApplicationController
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 @user, notice: 'User was successfully created.'
else
render :new
end
end
private
def user_form_params
params.require(:user_form).permit(:name, {emails: [:email_text]})
end
end
new.html.erb
:
<h1>New User</h1>
<%= render 'form', user_form: @user_form %>
_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>
<% unique_index = 0 %>
<% f.object.emails.each do |email| %>
<%= label_tag "user_form[emails][#{unique_index}][email_text]","Email" %>
<%= text_field_tag "user_form[emails][#{unique_index}][email_text]" %>
<% unique_index += 1 %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
注意:如果有一种更简单,更传统的方式来显示此表单对象中user_emails
的输入:请告诉我。我无法让fields_for
工作。如上所示:我必须手工写出name
属性。
好消息是表单确实呈现:
表单的html对我来说没问题:
提交上述输入时:这是params hash:
Parameters: {"utf8"=>"✓", "authenticity_token"=>”abc123==", "user_form"=>{"name"=>"neil", "emails"=>{"0"=>{"email_text"=>"foofoo"}, "1"=>{"email_text"=>"bazzbazz"}, "2"=>{"email_text"=>""}}}, "commit"=>"Create User form"}
params hash对我来说没问题。
在日志中我得到两个弃用警告,这让我觉得virtus可能已经过时,因此不再是rails中表单对象的工作解决方案:
DEPRECATION WARNING:方法to_hash已弃用,将在Rails 5.1中删除,因为
ActionController::Parameters
不再继承哈希值。使用此弃用行为会暴露潜在的安全问题。如果您继续使用此方法,则可能会在您的应用中创建可被利用的安全漏洞。相反,请考虑使用其中一种未弃用的文档化方法:http://api.rubyonrails.org/v5.0.2/classes/ActionController/Parameters.html(从new at(pry)调用:1) 弃用警告:方法to_a已弃用,将在Rails 5.1中删除,因为ActionController::Parameters
不再继承哈希值。使用此弃用行为会暴露潜在的安全问题。如果您继续使用此方法,则可能会在您的应用中创建可被利用的安全漏洞。相反,请考虑使用其中一种未弃用的文档化方法:http://api.rubyonrails.org/v5.0.2/classes/ActionController/Parameters.html(从new at(pry)调用:1) NoMethodError:预期[“0”,“foofoo”}允许:true&gt;]响应#to_hash 来自/Users/neillocal/.rvm/gems/ruby-2.3.1/gems/virtus-1.0.5/lib/virtus/attribute_set.rb:196:in`coerce'
然后整个事情错误地出现以下消息:
Expected ["0", <ActionController::Parameters {"email_text"=>"foofoo"} permitted: true>] to respond to #to_hash
我觉得我要么接近并且为了工作而错过了一些小事,或者我意识到virtus已经过时且不再可用(通过弃用警告)。
我看过的资源:
我确实尝试使用reform-rails gem获得相同的表单。我也遇到了一个问题。那个问题是posted here。
提前致谢!
答案 0 :(得分:3)
我只是将user_form.rb中user_form_params的emails_attributes设置为setter方法。这样您就不必自定义表单字段。
完整答案:
<强>型号:强>
#app/modeles/user.rb
class User < ApplicationRecord
has_many :user_emails
end
#app/modeles/user_email.rb
class UserEmail < ApplicationRecord
# contains the attribute: #email
belongs_to :user
end
表单对象:
# app/forms/user_form.rb
class UserForm
include ActiveModel::Model
include Virtus.model
attribute :name, String
validates :name, presence: true
validate :all_emails_valid
attr_accessor :emails
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|
UserEmail.new(email: email.email_text)
end
user.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
# "Embedded Value" Form Object. Utilized within the user_form object.
class EmailForm
include ActiveModel::Model
include Virtus.model
attribute :email_text, String
validates :email_text, presence: true
end
<强>控制器:强>
# app/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
<强>查看:强>
#app/views/users/new.html.erb
<h1>New User</h1>
<%= render 'form', user_form: @user_form %>
#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 %>
答案 1 :(得分:0)
您遇到问题,因为您尚未将:emails
下的任何属性列入白名单。这很令人困惑,但是this wonderful tip from Pat Shaughnessy should help set you straight。
这就是你要找的东西:
params.require(:user_form).permit(:name, { emails: [:email_text, :id] })
请注意id
属性:更新记录非常重要。您需要确保在表单对象中考虑该情况。
如果Virtus的所有这些形式对象malarkey变得过多,请考虑Reform。它有类似的方法,但其存在的理由是将形式与模型分离。
你的表单也有问题...我不确定你希望用你使用的语法实现什么,但如果你查看你的HTML,你会看到你的输入名称不会出现平移尝试更传统的东西:
<%= f.fields_for :emails do |ff| %>
<%= ff.text_field :email_text %>
<% end %>
通过这个你可以得到像user_form[emails][][email_text]
这样的名字,Rails可以方便地切片和切成这样的东西:
user_form: {
emails: [
{ email_text: '...', id: '...' },
{ ... }
]
}
您可以使用上述解决方案将其列入白名单。
答案 2 :(得分:0)
问题是传递给UserForm.new()
的JSON的格式不是预期的。
您在user_form_params
变量中传递给它的JSON目前具有以下格式:
{
"name":"testform",
"emails":{
"0":{
"email_text":"email1@test.com"
},
"1":{
"email_text":"email2@test.com"
},
"2":{
"email_text":"email3@test.com"
}
}
}
UserForm.new()
实际上是期望这种格式的数据:
{
"name":"testform",
"emails":[
{"email_text":"email1@test.com"},
{"email_text":"email2@test.com"},
{"email_text":"email3@test.com"}
}
}
在将JSON格式传递给UserForm.new()
之前,您需要更改它的格式。如果您将create
方法更改为以下内容,则不会再看到该错误。
def create
emails = []
user_form_params[:emails].each_with_index do |email, i|
emails.push({"email_text": email[1][:email_text]})
end
@user_form = UserForm.new(name: user_form_params[:name], emails: emails)
if @user_form.save
redirect_to @user, notice: 'User was successfully created.'
else
render :new
end
end