[RUBY] I want to use FormObject well

Introduction

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

What is FormObject?

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.

  1. I want to update the DB by having multiple Model attributes entered in one View.
  2. I want to set restrictions on the View side separately from the restrictions on Model and DB.
  3. The formatting of the information received from 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.

merit and demerit

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!

What is the responsibility of FormObject? ?? ??

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.

FormObject validate

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.

Save / update on FormObject

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.

FormObject error handling

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

Summary

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.

Reference site

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

Recommended Posts

I want to use FormObject well
I want to use Combine in UIKit as well.
I want to use DBViewer with Eclipse 2018-12! !!
I want to use @Autowired in Servlet
I want to use arrow notation in Ruby
I want to use java8 forEach with index
I want to convert characters ...
I want to use Clojure's convenient functions in Kotlin
I want to use NetBeans on Mac → I can use it!
I want to use fish shell in Laradock too! !!
I want to use ES2015 in Java too! → (´ ・ ω ・ `)
I want to use a little icon in Rails
[Android Studio] I want to use Maven library on Android
Swift: I want to chain arrays
[Beginner] I want to modify the migration file-How to use rollback-
I want to use screen sharing on the login screen on Ubuntu 18
I want to convert InputStream to String
I want to docker-compose up Next.js!
I want to use the Java 8 DateTime API slowly (now)
I want to use the sanitize method other than View.
I want to use FireBase to display a timeline like Twitter
I want to use swipeback on a screen that uses XLPagerTabStrip
I want to develop a web application!
I want to eliminate duplicate error messages
I want to display background-ground-image on heroku.
I want to RSpec even at Jest!
I want to write a unit test!
I want to install PHP 7.2 on Ubuntu 20.04.
I want to stop Java updates altogether
I tried to use Selenium like JQuery
I want to target static fields to @Autowired
I want to do team development remotely
eclipse I definitely want you to use Transcendental Recommended Shortcut Key (Windows)
[Memorandum] I've started working, so I want to study regular expressions well [Regular expressions]
[Java Spring MVC] I want to use DI in my own class
I want you to use Enum # name () for the Key of SharedPreference
I want to test Action Cable with RSpec test
Run R from Java I want to run rJava
[Swift] I want to draw grid lines (squares)
I want to send an email in Java.
I want to graduate from npm install properly [2020]
[Ruby] I want to do a method jump!
I want to var_dump the contents of the intent
I want to pass APP_HOME to logback in Gradle
I want to simply write a repeating string
When you want to use the method outside
I want to design a structured exception handling
rsync4j --I want to touch rsync in Java.
I want to play with Firestore from Rails
[Xcode] I want to manage images in folders
I want to be eventually even in kotlin
I want to write quickly from java to sqlite
I want to truncate after the decimal point
I want to reduce simple mistakes. To command yourself.
I want to perform aggregation processing with spring-batch
[Rails] I want to load CSS with webpacker
I want to delete files managed by Git
I want to get the value in Ruby
I want you to use Scala as Better Java for the time being
[Eclipse] I want to use the completion function, but I want to manage to confirm the completion with spaces.
I want to do something like "cls" in Java