2017-03-17 11 views
3

has_manyの関連オブジェクトを複数作成するform_objectを作成する方法を理解するのに苦労しています。virtus gem。以下はVirtusのRailsフォームオブジェクト:has_many関連

は、フォームオブジェクトはやり過ぎかもしれない不自然な例ですが、それは私が午前問題を示してい:

はその後user、レコード、および関連するカップルを作成user_formオブジェクトがあるとしましょう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 

一つは、私がattribute :emails, Array[EmailForm]を持ってUserFormクラスに気づくでしょう。これは、関連付けられた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

<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の属性を手で書き出す必要がありました。

良いニュースは、フォームがレンダリングないということです。

rendered form

フォームのHTMLは私にOKになります。

html of the form

上記の入力が提出された場合:ここでは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ハッシュは私には大丈夫です。私が手ログで

私はVirtus社は時代遅れず、したがって、もはや作業フォームオブジェクトのためのソリューションレール中かもしれないと思わせる2つの非推奨の警告:で全部のエラーを出し、その後

DEPRECATION WARNING: Method to_hash is deprecated and will be removed in Rails 5.1, as ActionController::Parameters no longer inherits from hash. Using this deprecated behavior exposes potential security problems. If you continue to use this method you may be creating a security vulnerability in your app that can be exploited. Instead, consider using one of these documented methods which are not deprecated: http://api.rubyonrails.org/v5.0.2/classes/ActionController/Parameters.html (called from new at (pry):1) DEPRECATION WARNING: Method to_a is deprecated and will be removed in Rails 5.1, as ActionController::Parameters no longer inherits from hash. Using this deprecated behavior exposes potential security problems. If you continue to use this method you may be creating a security vulnerability in your app that can be exploited. Instead, consider using one of these documented methods which are not deprecated: http://api.rubyonrails.org/v5.0.2/classes/ActionController/Parameters.html (called from new at (pry):1) NoMethodError: Expected ["0", "foofoo"} permitted: true>] to respond to #to_hash from /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です。

ありがとうございます!

答えて

2

私はちょうどuser_form_paramsのemails_attributesをsetterメソッドとしてuser_form.rbに設定します。フォームフィールドをカスタマイズする必要はありません。

完全な答え:

モデル:

#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 %> 
+0

編集を承認してください。私は喜んで回答を受け入れられた回答としてマークします。バリデーションが意図どおりに機能するように、いくつかのマイナーチェンジを行い、次にすべての主要な部分にコードを提供します。私はあなたの投稿された答えは、私が立ち往生した部分を介して私を得たので、あなたに信用を与えたい。 – Neil

+0

フォームオブジェクトにvirtus gemを使用する利点を考慮する必要があります。プロジェクトにvirtus gemの依存関係を追加しなくても簡単にフォームオブジェクトを作成できます。 [Railsフォームのオブジェクトの実装はこちら](http://stackoverflow.com/questions/42930117/form-objects-in-rails/43024542#43024542) – Neil

+0

この回答に感謝します。それは私が探していたものです。 私はRails newbです。誰かが 'emails_attributes =(attributes)'を定義する必要がある理由を私に説明することができますか?これを何と呼びますか?このメソッドが定義されていないと、 '@user_form.emails = [EmailForm.new、EmailForm.new、EmailForm.new]'は期待どおりに動作しません。 Railsのどこにこの魔法が起こっていますか? – user3574603

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

あなたはRailsが便利なこのような何かにスライスし、サイコロますuser_form[emails][][email_text]のような名前を、得るでしょう:

あなたは上記の溶液をホワイトリストに登録することができます
user_form: { 
    emails: [ 
    { email_text: '...', id: '...' }, 
    { ... } 
    ] 
} 

+0

はご投稿いただきありがとうございます。残念ながら、電子メールは依然としてparamsハッシュに渡されません。 '' user_form "=> {" name "=>" neil "、" emails "=>" "}'。そして、それでも、これは言う: 'Unpermitted parameter:emails' – Neil

+0

あなたは' emails'の下で値を得ていないことに気付かなかった。空の文字列を許可していないのは、現在ハッシュを期待しているからです。更新された回答をご覧ください。 – coreyward

0

問題がUserForm.new()に渡されるJSONの形式が期待されているものではないということです。

あなたはuser_form_params変数で、それに渡しているJSON、現在この形式になっています。

{ 
    "name":"testform", 
    "emails":{ 
     "0":{ 
     "email_text":"[email protected]" 
     }, 
     "1":{ 
     "email_text":"[email protected]" 
     }, 
     "2":{ 
     "email_text":"[email protected]" 
     } 
    } 
} 

UserForm.new()は、実際にこの形式でデータを期待している:

{ 
    "name":"testform", 
    "emails":[ 
     {"email_text":"[email protected]"}, 
     {"email_text":"[email protected]"}, 
     {"email_text":"[email protected]"} 
    } 
} 

あなたがする必要があるに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 
関連する問題