- 作者: すがわらまさのり,前島真一,近藤宇智朗,橋立友宏
- 出版社/メーカー: 技術評論社
- 発売日: 2014/06/06
- メディア: 大型本
- この商品を含むブログ (8件) を見る
Rails環境について
- 本の環境 : Rails4.1.1
- 本記事執筆時の環境 : Rails4.2.3
9-1. アーキテクチャパータンから見るRails
Skinny Controller, Fat Model
モデルを厚く、コントローラーを薄く
- コントローラーをシンプルに → ビジネスロジックがわかりやすくなる
- データ加工処理はモデルに担当させる
用語の整理
- モデリング : 業務知識を噛み砕いて形にする工程
- ドメインモデル : モデリングにより、抽出した概念に名前を付けてオブジェクトとして表現できるようにしたもの
- ドメインロジック(ビジネスロジック) : ドメインモデルが行う業務に必要な振る舞い
Rails の model層は、ドメインモデル、ドメインロジックをコードとして形にするレイヤー
- controller は?
Railsでは、ドメインモデルと、実際のRDBのマッピングの為に、 AcitveRecored を使用
アクティブレコードパターン
ドメインモデルに関わるロジックとデータの永続化処理を1つのクラスにまとめてカプセル化する手法
- クラス → テーブル、インスタンス → レコード にそれぞれ相当
- SQLの実行結果からインスタンスを生成して、インスタンスメソッドでレコードを操作
- メリット : 直感的に理解しやすい
- デメリット : 複雑なドメインモデルは表現しづらい
AcitveRecoredの落とし穴と回避方法
modelの誇大化
- バリデーションやらコールバックやらドメインロジックやら、modelに書くこと多いよね...
対策
- 責任を分割した小さなクラスに分ける
- バリデーションとコールバックの抽出
- RDBに依存しないモデルクラス
- システムの機能の一部をモデルの外に抽出する
9-2. バリデーションとコールバックの抽出
メリット
- 責任の分割
- コードの可読性
- テストがわかりやすくなる
- モデルクラスはRDSと密接に関連しているので、テスト用のモデルを用意するのが大変
- テストの本質と関係ないところにコストが掛かるのは健全ではない
どういう時分ける?
- テストの為の事前条件を満たすのが困難な場合
- 同じコールバックや、バリデーションが複数のモデルで実装されている場合
- 業務知識として重要な場合
- 「独立した概念である」ということをコード上で表現する
- 業務上の概念と、設計上の概念はできるだけ一致するべき
コールバックの抽出
- コールバックにオブジェクトを渡せる
- そのオブジェクトは コールバックと同じ名前のメソッド を持つ
class BankAccount < ActiveRecord::Base before_save EncryptionWrapper.new after_save EncryptionWrapper.new after_initialize EncryptionWrapper.new end class EncryptionWrapper def before_save(record) record.credit_card_number = encrypt(record.credit_card_number) end def after_save(record) record.credit_card_number = decrypt(record.credit_card_number) end alias_method :after_initialize, :after_save private def encrypt(value) # Secrecy is committed end def decrypt(value) # Secrecy is unveiled end end
参考
バリデーションの抽出
ActiveModel::EachValidator
個別の属性を検証するためのカスタムバリデータ
ActiveModel::EachValidator
を継承validate_each
メソッドを持つrecord
: 検証対象のレコードattribute
: 検証対象のカラム名(下記の場合、:email
)value
: 検証対象の値
EmailValidator
ならば、validates :user_email, email: true
という形で呼び出される
class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i record.errors[attribute] << (options[:message] || "は正しいメールアドレスではありません") end end end class Person < ActiveRecord::Base validates :email, presence: true, email: true end
ActiveModel::Validator
レコード全体を検証するためのカスタムバリデータ
class MyValidator < ActiveModel::Validator def validate(record) unless record.name.starts_with? 'X' record.errors[:name] << '名前はXで始まる必要があります' end end end class Person include ActiveModel::Validations validates_with MyValidator end
validates_with
にHashでオプションを渡せる- Validatorクラスでは、
options
メソッドでオプションを取り出せる
- Validatorクラスでは、
Validatorクラスの注意点
Validatorクラスには一つ注意点があります。Validatorクラスは通常一度しかインスタンス化されず、そのオブジェクトをずっと使い回すことになります。変にインスタンス変数とかを利用するとずっと残ってしまうので利用する場合は、意図せず変更されないように注意しましょう。
抽出したクラスってどこに置くのが良いかな?
- 1つのモデルでしか使わないクラス :
/app/model/bank_account/
みたいにモデル名のディレクトリの下 - 複数のモデルで使うクラス :
/app/model/validator/
みたいに、役割名のディレクトリの下
かなぁ
TODO
Skinny Controller, Fat Model についてもう少し理解を深める