Rails Form Object with Reform-Rails with Collections Not Working or Validating

Rails form object with reform-rails with collections not working or validating

Complete Answer:

Models:

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

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

Form Object:

# app/forms/user_form.rb
# if using the latest version of reform (2.2.4): you can now call validates on property
class UserForm < Reform::Form
property :name, validates: {presence: true}

collection :user_emails do
property :email_text, validates: {presence: true}
end
end

Controller:

# app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :user_form, only: [:new, :create]

def new
end

# validate method actually comes from reform this will persist your params to the Class objects
# you added to the UserForm object.
# this will also return a boolean true or false based on if your UserForm is valid.
# you can pass either params[:user][:user_emails] or params[:user][user_email_attributes].
# Reform is smart enough to pick up on both.
# I'm not sure you need to use strong parameters but you can.

def create
if @user_form.validate(user_params)
@user_form.save
redirect_to users_path, notice: 'User was successfully created.'
else
render :new
end
end

private

# call this method in a hook so you don't have to repeat
def user_form
user = User.new(user_emails: [UserEmail.new, UserEmail.new])
@user_form ||= UserForm.new(user)
end

# no need to add :id in user_emails_attributes
def user_params
params.require(:user).permit(:name, user_emails_attributes: [:_destroy, :email_text])
end
end

The Form:

# 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 :user_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 %>

field_with_errors not being applied on reform form object

The easiest solution is to use simple_form_for gem which will create the entire component (label, input and inline error message):

<%= simple_form_for signup_form do |form| %>
<%= form.input :name %>

Otherwise you need to add the inline error messages yourself, for example:

<%= form.object.errors[:name].join(', ') %> 

Form Objects in Rails

Complete Answer

Models:

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

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

Controller:

#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

Form Objects:

#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

Views:

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

Rails: Form Object not finding I18n on activemodel (namespacing issue)

Well I found out the answer by digging into the source code. The issue I was having that my form object was in a Users::PasswordAndLoginUpdatingForm namespace.

The trick is to use activemodel: as your namespace, but, if you have further namespaces in your models, they're separated with /s.

So, the correct yml looks like this:

en:
activemodel:
errors:
models:
users/password_and_login_updating_form:
attributes:
requested_email_address:
invalid: "foo"

Rails Form Object with Virtus: has_many association

I would just set the emails_attributes from user_form_params in the user_form.rb as a setter method. That way you don't have to customize the form fields.

Complete Answer:

Models:

#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

Form Objects:

# 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

Controller:

# 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

Views:

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

Unexpected naming of fields using Reform Form Object with Composition

You can call ::model only once! Once you call it with the correct model (or any name), the rendering will name the fields to whatever ::model you specify. The point about Reform is to hide internals about your model names!



Related Topics



Leave a reply



Submit