ActiveModel::ModelモジュールとActiveModel::Attributesモジュール

[ActiveModel::Model]モジュールと[ActiveModel::Attributes]モジュールの違いがいまいち分からなかったので、こちらにまとめます。

[ActiveModel::Model]モジュール
[ActiveModel::Model]モジュールをincludeしてできるようになる主な事は、以下のような事になります。(ActiveRecordのDBに連携しないようなモデルになる。)
モデル名の調査: モデル名からモデル名の複数形の予測などの機能が使えるようになる。(以下のような機能)

[1] pry(main)> Fish.model_name.name 
=> "Fish"
[2] pry(main)> Fish.model_name.singular
=> "fish"
[3] pry(main)> Fish.model_name.plural
=> "fishes"
[4] pry(main)> Fish.model_name.element
=> "fish"
[5] pry(main)> Fish.model_name.human
=> "魚"
[6] pry(main)> Fish.model_name.collection
=> "fishes"
[7] pry(main)> Fish.model_name.param_key 
=> "fish"
[8] pry(main)> Fish.model_name.i18n_key
=> :fish
[9] pry(main)> Fish.model_name.route_key
=> "fishes"
[10] pry(main)> Fish.model_name.singular_route_key
=> "fish"

変換: Railsのモデルの変換メソッド[ to_model , to_key , to_param , to_partial_path ]が利用できるようになる。(form_with modelなどが利用できるようになる。)
翻訳: i18nによる翻訳機能が使えるようになる。
バリデーション: 検証用の機能を提供する。
ヘルパーメソッド: [form_with]や[render]などのヘルパーメソッドが使えるようになる。

[attr_accessor]メソッド: インスタンスインスタンス変数を持たせることができ、インスタンス変数を呼び出せる。(以下のように検証)

・[ActiveModel::Model]モジュールをinclude

[app/forms/cooking_search_time_form.rb]

class CookingSearchTimeForm
    include ActiveModel::Model
    attr_accessor :name
end
[1] pry(main)> a = CookingSearchTimeForm.new(name: "テスト")
=> #<CookingSearchTimeForm:0x00007fb3684acc48 @name="テスト">
[2] pry(main)> a
=> #<CookingSearchTimeForm:0x00007fb3684acc48 @name="テスト">

・[ActiveModel::Model]モジュールをincludeしない

[app/forms/cooking_search_time_form.rb]

class CookingSearchTimeForm
    attr_accessor :name
end
[1] pry(main)> a = CookingSearchTimeForm.new(name: "テスト")
ActiveModel::UnknownAttributeError: unknown attribute 'name' for CookingSearchTimeForm.
from /Users/higmonta/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activemodel-6.0.4.1/lib/active_model/attribute_assignment.rb:52:in `_assign_attribute'
[2] pry(main)> a
=> nil

[ActiveModel::Attributes]モジュール
[ActiveModel::Attributes]モジュールをincludeすると[attribute]メソッドが使えるようになる。
[attribute]メソッドを使うと、[attr_accessor]メソッドと同じようにインスタンス変数が使えるようになり且つ、型の指定もできる。(以下のように検証)

・[ActiveModel::Model]モジュールをincludeして、[attr_accessor]メソッドを使ってインスタンス変数を設定(いろんな型でインスタンス変数を設定できる)

[app/forms/cooking_search_time_form.rb]

class CookingSearchTimeForm
    include ActiveModel::Model
    attr_accessor :name
end
[1] pry(main)> a = CookingSearchTimeForm.new(name: "テスト")
=> #<CookingSearchTimeForm:0x00007f9de0544900 @name="テスト">
[2] pry(main)> b = CookingSearchTimeForm.new(name: 11)
=> #<CookingSearchTimeForm:0x00007f9de58e6d60 @name=11>
[3] pry(main)> a
=> #<CookingSearchTimeForm:0x00007f9de0544900 @name="テスト">
[4] pry(main)> b
=> #<CookingSearchTimeForm:0x00007f9de58e6d60 @name=11>

・[ActiveModel::Attributes]モジュールをincludeしない

[app/forms/cooking_search_time_form.rb]

class CookingSearchTimeForm
    attribute :name, :string
end
[1] pry(main)> a = CookingSearchTimeForm.new(name: "テスト")
NoMethodError: undefined method `attribute' for CookingSearchTimeForm:Class
Did you mean?  attr_writer
from /Users/higmonta/workspace/fishing_cooking/app/forms/cooking_search_time_form.rb:2:in `<class:CookingSearchTimeForm>'

・[ActiveModel::Attributes]モジュールをincludeする([ActiveModel::Model]モジュールは、includeしない)
[ActiveModel::Model]モジュールをincludeしないと、そもそもモデルのように使えないのでエラーになる。

[app/forms/cooking_search_time_form.rb]

class CookingSearchTimeForm
    include ActiveModel::Attributes
    attribute :name, :string
end
[1] pry(main)> a = CookingSearchTimeForm.new(name: "テスト")
ArgumentError: wrong number of arguments (given 1, expected 0)
from /Users/higmonta/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activemodel-6.0.4.1/lib/active_model/attributes.rb:79:in `initialize'

・[ActiveModel::Attributes]モジュールと[ActiveModel::Model]モジュールをincludeする。
[attribute]メソッドで型指定をしているので、[11]という数字でインスタンス変数を設定しても文字列型に変換されている。

[app/forms/cooking_search_time_form.rb]

class CookingSearchTimeForm
    include ActiveModel::Model
    include ActiveModel::Attributes
    
    attribute :name, :string
end
[1] pry(main)> a = CookingSearchTimeForm.new(name: "テスト")
=> #<CookingSearchTimeForm:0x00007fc441e03690
 @attributes=
  #<ActiveModel::AttributeSet:0x00007fc441e035f0
   @attributes=
    {"name"=>
      #<ActiveModel::Attribute::FromUser:0x00007fc441e03348
       @name="name",
       @original_attribute=
        #<ActiveModel::Attribute::WithCastValue:0x00007fc441e03578
         @name="name",
         @original_attribute=nil,
         @type=#<ActiveModel::Type::String:0x00007fc445894ea8 @limit=nil, @precision=nil, @scale=nil>,
         @value_before_type_cast=nil>,
       @type=#<ActiveModel::Type::String:0x00007fc445894ea8 @limit=nil, @precision=nil, @scale=nil>,
       @value_before_type_cast="テスト">}>>
[2] pry(main)> b = CookingSearchTimeForm.new(name: 11)
=> #<CookingSearchTimeForm:0x00007fc44597afc0
 @attributes=
  #<ActiveModel::AttributeSet:0x00007fc44597af48
   @attributes=
    {"name"=>
      #<ActiveModel::Attribute::FromUser:0x00007fc44597ade0
       @name="name",
       @original_attribute=
        #<ActiveModel::Attribute::WithCastValue:0x00007fc44597aed0
         @name="name",
         @original_attribute=nil,
         @type=#<ActiveModel::Type::String:0x00007fc445894ea8 @limit=nil, @precision=nil, @scale=nil>,
         @value_before_type_cast=nil>,
       @type=#<ActiveModel::Type::String:0x00007fc445894ea8 @limit=nil, @precision=nil, @scale=nil>,
       @value_before_type_cast=11>}>>
[3] pry(main)> a.name
=> "テスト"
[4] pry(main)> b.name
=> "11"

参考記事

Active Modelについて - Qiita

ActiveModelを使ってDBと関係ないFormの構築してみた - その辺にいるWebエンジニアの備忘録

ActiveModel::Model で DBに依存しないモデルを作ろう - What is it, naokirin?

form object 検索機能を追加する - mmm_stの日記

Formオブジェクト~1つのフォームで複数モデルとやりとりをする画期的なヤツ~ - Qiita

【Ruby on Rails】フォームオブジェクトを使って検索キーワードをcontrollerに送る - Qiita

ActiveModel::Model で DBに依存しないモデルを作ろう - What is it, naokirin?

ActiveModel::Attributesを使う - Qiita

[Rails] ActiveModel::Attributesの使い方(配列化やネストしたhashの取り扱いなども) - Qiita

RailsでDBと連携しないModelを作成する - Qiita

Railsの技: Attributes APIでPOROの属性を自動的にキャストする(翻訳)|TechRacho by BPS株式会社