Rails Form Object with Virtus: has_many association

I find it difficult to determine how to create a form_object that creates several related objects to associate has_manywith virtus gem .

The following is a contrived example in which a form object can be overflowing, but it shows the problem I am facing:

Suppose there is an object user_formthat creates a record user, and then a pair associated with the user_emailrecords. Here are the models:

# models/user.rb
class User < ApplicationRecord
  has_many :user_emails
end

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

I move on to creating a form object to represent the user form:

# 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

In the classroom, UserFormyou can see what I have attribute :emails, Array[EmailForm]. This is an attempt to check and record the data that will be stored for related records user_email. Here is the form Embedded Valueto write 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

users_controller, user_form.

# 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 .

, :

display form

html :

html forms

: 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 , , :

: to_hash Rails 5.1, ActionController::Parameters . . , , . , : http://api.rubyonrails.org/v5.0.2/classes/ActionController/Parameters.html ( new at (pry): 1) DEPRECATION: 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 > ], #to_hash /Users/neillocal/.rvm/gems/ruby-2.3.1/gems/virtus-1.0.5/lib/virtus/attribute_set.rb:196: `coerce '

:

Expected ["0", <ActionController::Parameters {"email_text"=>"foofoo"} permitted: true>] to respond to #to_hash

, , - , , , ( ).

, :

  • .

, gem. . .

!

+4
3

emails_attributes user_form_params user_form.rb 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 %>
+2

, :emails. , .

, , :

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

id: . , .

malarkey Virtus , Reform. , raison d'etre .


... , , , 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: '...' },
    { ... }
  ]
}

.

0

, JSON, UserForm.new(), .

JSON, , user_form_params, :

{  
   "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"}
   }
}

You need to change the JSON format before passing it to UserForm.new(). If you change the method createto the following, you will no longer see this error.

  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
source

Source: https://habr.com/ru/post/1672578/


All Articles