Form Objects in Rails

The following sample example is a far-fetched example of an attempt at a form object, where it is probably unnecessary to use a form object. However: it shows the problem I am having:

I have two models: a Userand Email:

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

# app/models/user.rb
class Email < ApplicationRecord
  belongs_to :user
end

I want to create a form object that creates a record Userand then creates three related records Email.

Here are my form object classes:

# app/forms/user_form.rb
class UserForm 
  include ActiveModel::Model

  attr_accessor :name, :email_forms

  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)
    email_forms.each do |email|
      Email.create(user: user, email_text: email.email_text)
    end
  end
end

# app/forms/email_form.rb
class EmailForm 
  include ActiveModel::Model

  attr_accessor :email_text, :user_id

  validates :email_text, presence: true

  def save
    if valid?
      persist!
      true
    else
      false
    end
  end

  private

  def persist!
    puts "The Form is VALID!"
    # DON'T THINK I WOULD PERSIST DATA HERE
    # INSTEAD DO IT IN THE user_form
  end
end

Pay attention to : checks on form objects. A user_formis considered invalid if the attribute is nameempty, or if the attribute is email_textleft empty for any of the objects email_forminside it email_forms).

For short: I'll just go through the action newand createuse user_form:

# app/controllers/user_controller.rb
class UsersController < ApplicationController

  def new
    @user_form = UserForm.new
    @user_form.email_forms = [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, {email_forms: [:_destroy, :id, :email_text, :user_id]})
  end
end

Finally: the form itself:

# app/views/users/new.html.erb
<h1>New User</h1>

<%= render 'form', user_form: @user_form %>
<%= link_to 'Back', users_path %>

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

  # MESSY, but couldn't think of a better way to do this...
  <% unique_index = 0 %>
  <% user_form.email_forms.each do |email_form| %>
    <div class="field">
      <%= label_tag "user_form[email_forms][#{unique_index}][email_text]", "Email Text" %>
      <%= text_field_tag "user_form[email_forms][#{unique_index}][email_text]" %>
    </div>
    <% unique_index += 1 %>
  <% end %>


  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

:

image of the displayed form with input

html:

html forms

, . params:

Parameters: {"utf8"=>"✓", "authenticity_token"=>"abc123==", "user_form"=>{"name"=>"neil", "email_forms"=>{"0"=>{"email_text"=>"test_email_1"}, "1"=>{"email_text"=>"test_email_2"}, "2"=>{"email_text"=>""}}}, "commit"=>"Create User form"}

, , , form_object : . : form_object , , persist! UserForm. Email.create(user: user, email_text: email.email_text) :

undefined `email_text ' [ "0", { "email_text" = > "test_email_1" }]:

, : , , , params.

, :

  • , .
  • . , : virtus , - .
  • accepts_nested_attributes, , , ( ). , has_many accepts_nested_attributes_for ActiveModel::Model.

! !

+2
1

:

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

#app/models/email.rb
class Email < ApplicationRecord
  belongs_to :user
end

:

#app/controllers/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/forms/user_form.rb
class UserForm
  include ActiveModel::Model

  attr_accessor :name, :emails

  validates :name, presence: true
  validate  :all_emails_valid


  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_form|
      Email.new(email_text: email_form.email_text)
    end
    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
class EmailForm 
  include ActiveModel::Model

  attr_accessor :email_text, :user_id
  validates :email_text, presence: true
end

:

app/views/users/new.html.erb
<h1>New User</h1>

<%= render 'form', user_form: @user_form %>
<%= link_to 'Back', users_path %>


#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

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


All Articles