Rails表单对象与Virtus:has_many关联

我很难搞清楚如何创建一个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] 。 这是尝试validation和捕获将为关联的user_email记录user_email数据。 以下是user_email记录的Embedded Value表单:

 # 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

 

New User

_form.html.erb

   

prohibited this User from being saved:

注意:如果有一种更简单,更传统的方式来显示此表单对象中user_emails的输入:请告诉我。 我无法让fields_for工作。 如上所示:我必须手工写出name属性。

好消息是表单确实呈现:

渲染形式

表单的html对我来说没问题:

表格的HTML

提交上述输入时:这是params哈希:

 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中表单对象的工作解决方案:

弃用警告:方法to_hash已弃用,将在Rails 5.1中删除,因为ActionController::Parameters不再inheritance自hash。 使用此弃用行为会暴露潜在的安全问题。 如果您继续使用此方法,则可能会在您的应用中创建可被利用的安全漏洞。 相反,请考虑使用其中一种未被弃用的文档化方法: http : //api.rubyonrails.org/v5.0.2/classes/ActionController/Parameters.html (从new at(pry)调用:1)DEPRECATION警告:方法不推荐使用to_a,它将在Rails 5.1中删除,因为ActionController::Parameters不再inheritance自hash。 使用此弃用行为会暴露潜在的安全问题。 如果您继续使用此方法,则可能会在您的应用中创建可被利用的安全漏洞。 相反,请考虑使用其中一种未被弃用的文档化方法: http ://api.rubyonrails.org/v5.0.2/classes/ActionController/Parameters.html(从new at(pry)调用:1)NoMethodError:预期[允许:“0”,“foofoo”}允许:true>]从/Users/neillocal/.rvm/gems/ruby-2.3.1/gems/virtus-1.0.5/lib/virtus/attribute_set.rb回复#to_hash :196:在’coerce’

然后整个事情错误地出现以下消息:

 Expected ["0", "foofoo"} permitted: true>] to respond to #to_hash 

我觉得我要么接近并且缺少一些小东西才能使它工作,或者我意识到virtus已经过时且不再可用(通过弃用警告)。

我看过的资源:

  • 这篇文章 。
  • 这个video

我确实试图让同样的forms工作,但改革轨道gem 。 我也遇到了一个问题。 这个问题发布在这里 。

提前致谢!

我只是将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 

New User

<%= 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? %>

<%= pluralize(user_form.errors.count, "error") %> prohibited this User from being saved:

    <% user_form.errors.full_messages.each do |message| %>
  • <%= message %>
  • <% end %>
<% end %>
<%= f.label :name %> <%= f.text_field :name %>
<%= f.fields_for :emails do |email_form| %>
<%= email_form.label :email_text %> <%= email_form.text_field :email_text %>
<% end %>
<%= f.submit %>
<% end %>

您遇到问题,因为您尚未将以下任何属性列入白名单:emails 。 这很令人困惑,但是来自Pat Shaughnessy的精彩提示应该有助于让你顺利 。

这是你正在寻找的,但是:

 params.require(:user_form).permit(:name, { emails: [:email_text, :id] }) 

注意id属性:更新记录很重要。 您需要确保在表单对象中考虑该情况。

如果所有这些forms对象与Virtus的malarkey太多,请考虑改革 。 它有类似的方法,但其存在的理由是将forms与模型分离。


您的表单也有问题…我不确定您希望使用您正在使用的语法实现什么,但是如果您查看HTML,您会看到您的输入名称不会出现问题。 尝试更传统的东西:

 <%= f.fields_for :emails do |ff| %> <%= ff.text_field :email_text %> <% end %> 

有了这个,你会得到像user_form[emails][][email_text]这样的user_form[emails][][email_text] ,Rails会方便地切片和切成这样的东西:

 user_form: { emails: [ { email_text: '...', id: '...' }, { ... } ] } 

您可以使用上述解决方案将其列入白名单。

问题是传递给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()之前,您需要更改JSON的格式。 如果将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