Help us understand the problem. What is going on with this article?

Form Object実装メモ

More than 3 years have passed since last update.

やり方はいろいろ考えられる
ここに示すのはうまく動作した一例
Railsとかのバージョンによっても書き方が変わる

7 Patterns to Refactor Fat ActiveRecord Models

ユースケース

例えばUserモデルのsaveのパターンがたくさんあり、それぞれに関連するcolumns, validationsなどの違いが多く複雑になった時に、関心を分けるために使える

  • パターン例
    • Signup
    • Sign In
    • Password Change
    • Update profile
    • Create users from importing
    • etc.
  • パターンが2つ程度でシンプルな時はvalidate :on/:ifなどで間に合う

@form = MyForm.new()があたかも@userであるかのようにform_forと一緒に使えるようにできる

実装

forms/my_form.rb
class MyForm
  include Virtus.model

  include ActiveModel::Model
  # 以下Rails3
  # include ActiveModel::Validations
  # include ActiveModel::Conversion
  # extend ActiveModel::Naming

  attr_reader :user

  attribute :email, String
  # Userに保存しないが、validationをかけたいattributeの例
  attribute :terms_of_use, Boolean

  # validations
  validates_acceptance_of :terms_of_use

  def initialize(user)
    @user = user
    self.attributes = @user.attributes
  end

  def persisted?
    false
  end

  # submit時のparamsのkeyに使われる -> params[:user] (無い場合params[:my_form])
  # form_forがURLを生成する時に使われる -> users_path (無い場合my_forms_pathなど)
  def self.model_name
    ActiveModel::Name.new(self, nil, "User")
  end

  # List attributes defined with virtus
  # controllerで @form.update(user_params) を呼ぶ時のpermitに使える
  #   user_params -> params.require(:user).permit(*MyForm.column_names)
  def self.column_names
    attribute_set.map(&:name)
  end

  def update(params)
    self.attributes = params.to_h # 実装によって変わる
    if valid?
      persist!
      true
    else
      false
    end
  end

  private

    def persist!
      @user.update!(self.attributes) # 実装によって変わる
      # userに含まれない一時的な値なら self.attributes.except(:terms_of_use) などとできる
      # 複数のmodelを扱うことも可能
    end
end

form + routes

routes.rb
resource :user, only: [:edit, :update]
edit.html.haml
= form_for @form, url: user_path, method: :put do |f|

I18n

ActiveRecord/ActiveModelの翻訳

activerecordのキーではうまく動作しなかった
activemodelにエイリアスする

en:
  activerecord: &activerecord
    errors:
      ...
    models:
      ...
    attributes:
      ...
  activemodel:
    <<: *activerecord

参考

RailsCasts Form Objects

関連gem
reform - Form objects decoupled from models
gemに依存するコストがかかる可能性がある

model_nameの部分
How should I handle edit and update action when I use Form Object?

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away