首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >具有Virtus的Rails Form对象: has_many关联

具有Virtus的Rails Form对象: has_many关联
EN

Stack Overflow用户
提问于 2017-03-17 15:27:18
回答 3查看 3.5K关注 0票数 3

我很难弄清楚如何创建一个form_object,为has_many维塔斯宝石的关联创建多个关联对象。

下面是一个精心设计的示例,其中的form对象可能是过火的,但它确实显示了我遇到的问题:

假设有一个user_form对象创建了一个user记录,然后创建了两个相关的user_email记录。以下是模型:

代码语言:javascript
复制
# models/user.rb
class User < ApplicationRecord
  has_many :user_emails
end

# models/user_email.rb
class UserEmail < ApplicationRecord
  belongs_to :user
end

我继续创建一个表单对象来表示用户表单:

代码语言:javascript
复制
# 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表单:

代码语言:javascript
复制
# 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

现在,我将继续演示设置users_controller的user_form。

代码语言:javascript
复制
# 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

The new.html.erb

代码语言:javascript
复制
<h1>New User</h1>

<%= render 'form', user_form: @user_form %>

_form.html.erb

代码语言:javascript
复制
<%= 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散列:

代码语言:javascript
复制
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"}

在我看来,帕拉姆哈希是可以的。

在日志中,我得到了两个不推荐的警告,这使我认为virtus可能已经过时,因此不再是rails中表单对象的有效解决方案:

弃用警告:方法to_hash是不推荐的,将在Rails 5.1中删除,因为ActionController::Parameters不再从哈希继承。使用这种不推荐的行为会暴露潜在的安全问题。如果您继续使用此方法,您可能会在应用程序中创建一个可被利用的安全漏洞。相反,请考虑使用这些文档中不推荐的方法之一:http://api.rubyonrails.org/v5.0.2/classes/ActionController/Parameters.html (从新的at (pry):1)弃用警告:方法to_a被弃用,并将在Rails 5.1中删除,因为ActionController::Parameters不再从哈希继承。使用这种不推荐的行为会暴露潜在的安全问题。如果您继续使用此方法,您可能会在应用程序中创建一个可被利用的安全漏洞。相反,请考虑使用以下几种不推荐的文档方法之一:http://api.rubyonrails.org/v5.0.2/classes/ActionController/Parameters.html (从新的at (pry):1) NoMethodError:预期的"0“、"foofoo"}允许的: true>响应来自#to_hash的#to_hash`胁迫‘

然后,整件事情出错,并给出以下信息:

代码语言:javascript
复制
Expected ["0", <ActionController::Parameters {"email_text"=>"foofoo"} permitted: true>] to respond to #to_hash

我觉得我要么是亲近了,为了让它发挥作用,要么是错过了一些小东西,要么我意识到virtus已经过时,不再可用了(通过废弃警告)。

我看过的资源:

我确实尝试让相同的表单工作,但使用改革-rails创业板。我在那里也遇到了问题。这个问题是贴在这里

提前感谢!

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2017-03-22 00:44:48

我只是将emails_attributes从user_form_params中的user_form.rb设置为一个setter方法。这样,您就不必自定义表单字段。

完整答案:

模型:

代码语言:javascript
复制
#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

表单对象:

代码语言:javascript
复制
# 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

控制器:

代码语言:javascript
复制
# 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

视图:

代码语言:javascript
复制
#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 %>
票数 3
EN

Stack Overflow用户

发布于 2017-03-17 16:01:48

您有一个问题,因为您没有在:emails下白化任何属性。这很让人困惑,但是这位来自帕特·肖内西的绝妙建议应该能帮你理清问题。

不过,这就是你要找的:

代码语言:javascript
复制
params.require(:user_form).permit(:name, { emails: [:email_text, :id] })

注意id属性:它对于更新记录很重要。您需要确保在表单对象中说明了这种情况。

如果所有这些带有Virtus的表单对象malarkey变得太多,请考虑改革。它有类似的方法,但其存在的理由是从模型中分离形式。

您的表单…也有问题。我不确定您希望用所使用的语法实现什么,但是如果您查看HTML,就会发现您的输入名称无法实现。试一试更传统的方法:

代码语言:javascript
复制
<%= f.fields_for :emails do |ff| %>
  <%= ff.text_field :email_text %>
<% end %>

这样,您就可以得到像user_form[emails][][email_text]这样的名称,Rails可以方便地将其切片和切成如下所示:

代码语言:javascript
复制
user_form: { 
  emails: [
    { email_text: '...', id: '...' },
    { ... }
  ]
}

你可以用上面的解决方案来白名单。

票数 0
EN

Stack Overflow用户

发布于 2017-03-25 15:58:28

问题是,传递给UserForm.new()的JSON格式不是人们所期望的。

user_form_params变量中,您要传递给它的JSON当前具有以下格式:

代码语言:javascript
复制
{  
   "name":"testform",
   "emails":{  
      "0":{  
         "email_text":"email1@test.com"
      },
      "1":{  
         "email_text":"email2@test.com"
      },
      "2":{  
         "email_text":"email3@test.com"
      }
   }
}

UserForm.new()实际上期望这种格式的数据:

代码语言:javascript
复制
{  
   "name":"testform",
   "emails":[   
       {"email_text":"email1@test.com"}, 
       {"email_text":"email2@test.com"},  
       {"email_text":"email3@test.com"}
   }
}

在将JSON传递给UserForm.new()之前,您需要更改JSON的格式。如果您将create方法更改为以下内容,您将不会再看到该错误。

代码语言:javascript
复制
  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
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42861425

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档