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)