LoginSignup
41
33

More than 5 years have passed since last update.

Form Object実装メモ

Last updated at Posted at 2015-11-10

やり方はいろいろ考えられる
ここに示すのはうまく動作した一例
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?

41
33
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
33