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 **.
The basic design of Rails is ** MVC (Model, View, Controller) **.
View accepts input from the user. The information is given to
Controller formats and controls the information.
Controller passes the formatted and controlled information to
Model accesses the DB based on the information received from
Controller, saves / changes / deletes the data, and
The result is returned to
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.
Modelattributes entered in one
Viewside separately from the restrictions on
Controllerhas become complicated and hard to see. ...
If you try to implement these with MVC alone, your responsibilities will be biased towards
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
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 will become bloated. By writing such logic in
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
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
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
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
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
** 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
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 **
I think it's common for
FormObject to have logic ** for ** input (sorry if it's wrong ...)
Here I am responsible for the
"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
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
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)
[Rails] Create a Model-independent Form using Form Object