もう使い古されたネタという感じですが、Railsを使わずとも ActiveSupport だけを利用してRubyのコードを書くことは結構多いのではないでしょうか。
blank?
, present?
であったり、 pluralize
や try
、 1.year.ago
のような呼び出しなど、ヘルパーメソッドはRubyの標準ライブラリだけではなかなかスッキリとかけないところに手が届き、非常に重宝します。また ActiveSupport::Concern は Mix-in をする際に非常に便利です。
が、今回はRailsの ActiveRecord インスタンスのように扱えるようになる ActiveModel::Model に注目してみることにしました。
ちなみに、ActiveSupport の Core Extensions については、以下を参考にするのが良いかと思います。
Active Support コア拡張機能 - Rails ガイド
ちなみにですが、バージョンはRails の v5.2.2 を元にしています。
ActiveModel::Model モジュール
ActiveModel::Model はコードを見るとわかりますが、以下のモジュールの組み合わせになっています。
extend ActiveSupport::Concern include ActiveModel::AttributeAssignment include ActiveModel::Validations include ActiveModel::Conversion included do extend ActiveModel::Naming extend ActiveModel::Translation end
参考: https://github.com/rails/rails/blob/v5.2.2/activemodel/lib/active_model/model.rb#L60-L68
ActiveSupport::Concern
は ActiveModel::Model
モジュール自体の実装に使われていますが、それ以外は include ActiveModel::Model
したクラスやモジュールで利用することになります。また、明示されていませんが、 ActiveModel::Callbacks
も include されます。
それぞれのモジュールについてざっくりと説明すると以下のようになります。
- ActiveModel::AttributeAssignment
- attr_accessor で定義されたアクセサに対して、一括で値を設定することができる機能を提供する
- ActiveModel::Validations
- モデルの検証用の機能を提供する
- ActiveModel::Conversion
- Rails のモデルの変換メソッド(
to_model
,to_key
,to_param
,to_partial_path
)を利用できるようにする
- Rails のモデルの変換メソッド(
- ActiveModel::Naming
- モデル名に対するヘルパクラスメソッドを提供する
- ActiveModel::Translation
- オブジェクトとi18nの間の統合機能を提供する
- ActiveSupport::Callbacks
- ActiveRecord 形式のコールバックの機能を提供する
ActiveRecordでないクラスをモデルのように扱う
では、サンプルコードを書いてみたので見てみましょう。 今回は ActiveModel::Attributes も利用してみました。
require 'active_model' class CustomModel include ActiveModel::Model include ActiveModel::Attributes # ActiveModel::Attributes で属性を定義する attribute :id, :integer, default: 0 attribute :name, :string, default: '' # ActiveModel::Validations の機能で妥当性検証用の設定を追加をする validates :id, presence: true, numericality: { only_integer: true, greater_than: 0 } validates :name, presence: true # ActiveModel::Callbacks の機能で save メソッドの前に妥当性検証をする define_model_callbacks :save, only: :before before_save { throw(:abort) if invalid? } def save # ActiveModel::Callbacks のコールバック呼び出しができるようにする run_callbacks :save do puts 'Saved!' end end end
上記のように定義をすると、以下のように利用することができます。
obj = CustomModel.new # ActiveModel::AttributeAssignment による値の設定 obj.attributes = {id: 1, name: 'Bob' } # 妥当性チェック & コールバック呼び出し # 条件を満たしているので `puts 'Saved!'` が呼ばれる obj.save # ActiveModel::Attributes により、適切なキャストが行われる obj.id = '0' # 妥当性チェック & コールバック呼び出し # 条件を満たしていないので `puts 'Saved!'` が呼ばれない obj.save
多少、コールバックのためにひと手間実装が必要だったりしますが、Railsでの実装でもDBに直接紐付かないモデルクラスを作成するなどに利用することができ、非常に便利です。
こういった機能を利用していくとより適切な粒度で実装を行うことができるようになりますね。同じようなモデル実装についても、 ActiveSupport::Concern を利用して実装したモジュールをMix-inすることで機能を共通化することもできますし、よく考えられていますね。