Rails Advent Calendar 2020 Day 16 Article!
Every day in my work, I am working hard to write code that touches Rails.
Meanwhile, I would like to share my thoughts on ** Form Object **, which I struggled with (still struggling) this year! !!
In the first place, I would like to briefly introduce FormObject because some people may think "What is it ???"!
Simply put, ** FormObject ** is one of Rails' design patterns ** design techniques **.
MVC
The basic design of Rails is ** MVC (Model, View, Controller) **.
View
accepts input from the user. The information is given to Controller
, and Controller
formats and controls the information.
Then, Controller
passes the formatted and controlled information to Model
.
Model
accesses the DB based on the information received from Controller
, saves / changes / deletes the data, and
The result is returned to Controller
and View
.
The returned result can be formatted and controlled by Controller
and displayed in View
, or displayed as it is without being controlled and formatted.
This is the basic flow of MVC
, but I think there are the following things.
Model
attributes entered in one View
.View
side separately from the restrictions on Model
and DB.View
in Controller
has become complicated and hard to see.
...If you try to implement these with MVC alone, your responsibilities will be biased towards Controller
or Model
.
This can make the code ** hard to keep up with, or ** have a large range of influence **.
If you do so, ** the maintainability of the app will be low **, so
Add new features ... You can fix it ... Change the structure of the DB,
When you do, you end up with ** very hard feelings **.
The concept of FormObject
exists as one of the methods to prevent them.
I think the main purpose of using FormObject
is ** to separate responsibilities **.
By encapsulating the logic ** that formats the information from ** View
, which is one of the causes of the bloat of Controller
, with FormObject
,
The responsibility of Controller
will be reduced by one.
This will keep the Controller
from bloating, and will be in a ** single duty ** state with fewer dependencies and ** maintainability **.
Also, if you put ** logic that is not related to the state of ** Model in Model
, Model
will become bloated. By writing such logic in FormObject
,
The responsibility of Model
is reduced by one, and bloat can be prevented.
In this way, by grouping the logic that tends to be biased toward Controller
and Model
into FormObject
, you can avoid over-duty! !!
However, I think the above can be a disadvantage.
Rarely, if you aggregate the logic in FormObject
, this time ** FormObject will be bloated **.
This is the end of the story. .. ..
Despite leaving the logic for View
of Controller
and Model
to FromObject
and using it for the purpose of separating responsibilities
It is not good that the responsibility of FormObject
becomes large and the maintainability there becomes low.
Therefore, before using it, make sure to clarify the responsibility of ** FormObject
** before using it!
I've got a general idea of what the FormObject
is.
It's a pretty convenient FromObject
, but ... I've seen various articles and thought about it while actually writing it. .. ..
"Should I do this with FomObject ???" "Isn't it wearing the logic of Controller or Model ???" "The responsibility of FormObject is ... how long should I have after all ???"
I would like to explain the reason for thinking about these things using actual code as an example.
When using FormObject, if you include ActiveModel :: Model
, you can write validate as well as Model
.
For example, suppose you have a form to enter tag information when registering an article.
In the form, if the title and content of the article and the name of the tag are required, call as follows.
class ArticlePostForm
include ActiveModel::Model
attr_accessor :article_body, :article_title, :tag_name
validates :article_body, presence: true
validates :article_title, presence: true
validates :tag_name, presence: true
end
You have now set limits on Form input. However, the article and tag Model
also had the following validate.
class Article < ApplicationRecord
validates :title, presence: true, uniqueness: true
validates :body, presence: true
validates :is_display, inclusion: [true, false]
end
class Tag < ApplicationRecord
validates :name, presence: true, uniqueness: true
end
The meaning of ** is different between validate with FormObject
and validate with Model
. ** **
While validate with FormObject
is ** limit on the input value **, validate with Model
is ** limit when saving to DB **.
However, this time, the attributes being verified are covered by FormObject
and Model
, so
** Is FormObject validate required? ** **
There is a question.
I often see it when I look at articles.
There is a save
method that creates ActiveRecord with FormObject
as shown below.
class ConractForm
include ActiveModel::Model
attr_accessor :first_name, :last_name, :email, :body
def save
return false if invalid?
contact = Contact.new(first_name: first_name, last_name: last_name, email: email, body: body)
if contact.save
true
else
errors.add(:base, contact.errors.full_messages)
false
end
end
end
In Controller
, you should be able to write:
class ContactController < ApplicationController
def new
@contact = ContactForm.new
end
def create
@contact = ContactForm.new(set_params)
if @contact.save
flash.now = 'We received inquiries'
redirect_to :contacts
else
flash.alert = 'Failed to send'
render :new
end
end
private
def set_params
params.require(:contact).permit(:first_name, :last_name, :email, :body)
end
end
At this point, I was wondering if FormObject
could have the role of creating / updating ** Model
**.
I think it's common for FormObject
to have logic ** for ** input (sorry if it's wrong ...)
Here I am responsible for the FomObject
,
"Only the control for the input of View is different from the generation of Model
"and" It is good to generate Model
because it is validated."
I thought that I could think of two ways, so I was wondering which one to go to.
I was wondering if FormObject
handles the error caused by validate in the generation of Model
.
The most annoying part is whether the Model
error and the FormObject
error should be ** separated or should be combined **. .. ..
(I haven't come up with ideas yet, so I'll share it again as I gain more knowledge.)
def save
#Here is a FormObject input error
return false if invalid?
#This is an error when creating a Model
contact = Contact.new(first_name: first_name, last_name: last_name, email: email, body: body)
if contact.save
true
else
errors.add(:base, contact.errors.full_messages)
false
end
end
I think it is important to think about the responsibilities for FormObject
each time and to implement ** so as not to do more than that responsibilities **.
However, I myself do not completely grasp and understand FormObject
, and there are other design ideas, so I combined them all over the place.
I feel that there is also the idea of FormObject
.
Consider the option called Form Object
[Rails] I want you to use FormObject
Rails Design Pattern: Form Object (https://product-development.io/posts/rails-design-pattern-form-objects)