※ この記事はRails5.2から導入されたActiveModel::Attributes
については記述していません。
はじめに
よくあるフォームの実装です。
class UserForm < ActiveModel::Model
WITH_KIDS_OPTIONS = [['こどもの同席あり', true], ['こどもの同席なし', false]].freeze
attr_reader :name, email
validates :name, presence: true
validates :email, presence: true, email: true
validates :with_kids, presence: true, inclusion: { in: [true, false] }
def save
user = User.new(
name: name,
email: email,
with_kids: with_kids
)
user.save
end
def with_kids
ActiveRecord::Type::Boolean.new.cast(@with_kids)
end
end
ActiveModel::ModelをincludeしてFormオブジェクトを定義しています。
このコードを見たとき、with_kids
メソッドを使って@with_kids
というインスタンス変数をキャストしている部分が何をやっているのか引っかかったので、丁寧に読んでみました。
attr_readerのはたらき
attr_reader
は、Rubyのメソッドです。
内部的にインスタンス変数を定義し、クラスやモジュールにインスタンス変数を読み出すことを可能にします。
下記のコードは attr_reader :name, email
と同じ挙動になります。
Class UserFrom
def name
@name
end
def email
@email
end
end
はじめに出てきたコードでは、attr_reader
でwith_kids属性だけ指定していません。
その代わり、自分でwith_kids
メソッドを定義しています。
つまり、with_kids属性のみ、attr_reader
を使わずに独自のメソッドを使ってインスタンス変数を定義しているのです。
キャストを行う理由
では、なぜwith_kidsメソッドでキャストを行なっているのでしょうか?
キャストしている理由は、UserFormオブジェクトのwith_kids属性がString型に変換されてしまうのを防ぐことが目的です。
定数 WITH_KIDS_OPTIONS
ではvalueをBoolean型で指定しており、それを守りたいのです。
この処理の背景には、ActiveModelの存在があります。
ActiveModelについて
今回のコードでは、UserFormというクラスを定義しています。
これはRailsのActiveModel::Modelをincludeしています。
このように、FormなどのオブジェクトをActiveModelを使って切り出し、ActiveRecordを使ったモデルが大きくなりすぎないようにする、というリファクタリングのパターンがあります。
7 Patterns to Refactor Fat ActiveRecord Models
翻訳版はこちら
ActiveRecordのattribute機能
ActiveRecordにあってActiveModelにないものが、attribute
の機能です。
ActiveRecordでは、attribute
機能を使って属性の型の指定が簡単にできます。
class User < ApplicationRecord
attribute :with_kids, :boolean, default: false
end
user = User.new
user.with_kids == false
上のコードでは、with_kids
属性に対し型とデフォルトの値を指定しています。
ActiveModelの場合は自分でこれをやってあげなければなりません。
そこで、with_kidsメソッド内でキャストしているのです。
まとめ
- Rubyのattr_readerは内部的にインスタンス変数を定義する
- ActiveRecordの代わりにActiveModelを使うモデル設計のパターンがある
- ActiveModelはActiveRecordと違い、attributeのような機能を持っていない(※)
- ActiveModelでattributeのようなことをしたいときは、attr_readerの外でインスタンス変数を定義して対処する(※)
※ 補足: Rails5.2から導入されたActiveModel::Attributesについて
Rails5.2からはActiveModel::Attributes
という大変便利なものが使えるようになりました。
これをincludeして使うことで、ActiveRecodeのattributeと同じような機能を使えるようになりました。
ActiveModel::Attributes
の導入についてのわかりやすい記事はこちらです。