本ガイドでは、モデルクラスを使って作業を開始するのに必要な知識について解説します。Active Modelは、Action PackやAction ViewヘルパーにプレーンなRubyオブジェクトとのやりとりを行う手段を提供します。Active Modelを用いることで、カスタムのORM(オブジェクト/リレーショナルマッパー)を作成してRailsフレームワークの外で利用できるようになります。
このガイドの内容:
Active Modelを理解するには、まずActive Recordについて少し知っておく必要があります。Active RecordはORM(オブジェクト/リレーショナルマッパー)の一種であり、データを永続的に保存する必要のあるオブジェクトをリレーショナルデータベースに接続します。ただし、ORM以外にも、バリデーション、コールバック、変換、カスタム属性を作成する機能といった有用な機能がたくさんあります。
Active Recordのそうした機能の一部が抽象化されてActive Modelに移転しました。Active Modelは、モデルのような機能を必要としているが、データベース内のテーブルには関連付けないプレーンなRubyオブジェクト(PORO)で利用できるさまざまなモジュールを含むライブラリです。
要約すると、Active Recordは「データベースのテーブルに対応するモデル」を定義するインターフェイスを提供するものであり、Active Modelは「必ずしもデータベースを必要としない、モデル風のRubyクラス」を構築するための機能を提供するものです。Active Modelは、Active Recordとは独立して利用できます。
いくつかのモジュールについては、以下で説明します。
ActiveModel::APIは、クラスをAction PackやAction Viewと連携させる機能をすぐ利用可能な形でクラスに追加します。
ActiveModel::APIをincludeすると、他のモジュールがデフォルトでincludeされ、以下のような機能が使えるようになります。
ActiveModel::APIをincludeしたクラスとその使い方の例を以下に示します。
class EmailContact include ActiveModel::API attr_accessor :name, :email, :message validates :name, :email, :message, presence: true def deliver if valid? # メールを配信する end end end
irb> email_contact = EmailContact.new(name: "David", email: "david@example.com", message: "Hello World") irb> email_contact.name # 属性の代入 => "David" irb> email_contact.to_model == email_contact # 変換 => true irb> email_contact.model_name.name # 名前の取得 => "EmailContact" irb> EmailContact.human_attribute_name("name") # 翻訳(ロケールが設定済みの場合) => "Name" irb> email_contact.valid? # バリデーション => true irb> empty_contact = EmailContact.new irb> empty_contact.valid? => false ActiveModel::APIをincludeしたクラスは、Active Recordオブジェクトと同様に、form_withやrenderなどのAction Viewのヘルパーメソッドでも利用できます。
たとえば、次のようにform_withメソッドを利用してEmailContactオブジェクトのフォームを作成できます。
<%= form_with model: EmailContact.new do |form| %> <%= form.text_field :name %> <% end %>
上によって以下のHTMLが生成されます。
<form action="/email_contacts" method="post"> <input type="text" name="email_contact[name]" id="email_contact_name"> </form>
以下のようにrenderメソッドを使うことで、このオブジェクトのパーシャルをレンダリングすることも可能です。
<%= render @email_contact %>
form_withやrenderをActiveModel::API互換オブジェクトで利用する方法について詳しくは、それぞれAction Viewフォームヘルパーガイドとレイアウトとレンダリングガイドを参照してください。
ModelモジュールActiveModel::Modelモジュールには、Action PackやAction ViewとやりとりするためのActiveModel::APIがデフォルトで含まれており、モデル的なRubyクラスを実装する場合はこの方法が推奨されています。将来的には拡張され、より多くの機能が追加される予定です。
class Person include ActiveModel::Model attr_accessor :name, :age end
irb> person = Person.new(name: 'bob', age: '18') irb> person.name # => "bob" irb> person.age # => "18"
AttributesモジュールActiveModel::Attributesモジュールを利用することで、データ型の定義、デフォルト値の設定、PORO(プレーンなRubyオブジェクト)のキャストやシリアライズの処理が可能になります。これは、フォームデータで通常のオブジェクトの日付やブーリアン値などに対してActive Recordと同じような変換を行うときに便利です。
Attributesを利用するには、以下のようにモデルクラスにモジュールをincludeしてから、attributeマクロで属性を定義します。この属性には、「属性名」「キャスト型」「デフォルト値」など、属性型でサポートされる任意のオプションを指定できます。
class Person include ActiveModel::Attributes attribute :name, :string attribute :date_of_birth, :date attribute :active, :boolean, default: true end
irb> person = Person.new irb> person.name = "Jane" irb> person.name => "Jane" # 文字列を、属性によって設定された日付にキャストする irb> person.date_of_birth = "2020-01-01" irb> person.date_of_birth => Wed, 01 Jan 2020 irb> person.date_of_birth.class => Date # 属性に設定されているデフォルト値を利用する irb> person.active => true # 整数値を、属性に設定されているブーリアン値にキャストする irb> person.active = 0 irb> person.active => false
ActiveModel::Attributesで利用できるメソッドを以下にいくつか紹介します。
attribute_namesメソッドattribute_namesメソッドは、属性名のリストを配列で返します。
irb> Person.attribute_names => ["name", "date_of_birth", "active"]
attributesメソッドattributesは、すべての属性のリストをハッシュで返します。ハッシュのキーは属性名で、値は属性の値です。
irb> person.attributes => {"name" => "Jane", "date_of_birth" => Wed, 01 Jan 2020, "active" => false} AttributeAssignmentモジュールActiveModel::AttributeAssignmentモジュールを利用すると、属性名と一致するキーを持つ属性のハッシュを渡す形でオブジェクトの属性を一括で設定できます。これは、複数の属性を一度にまとめて設定したい場合に便利です。
以下のクラスを考えてみましょう。
class Person include ActiveModel::AttributeAssignment attr_accessor :name, :date_of_birth, :active end
irb> person = Person.new # 複数の属性を一括で設定する irb> person.assign_attributes(name: "John", date_of_birth: "1998-01-01", active: false) irb> person.name => "John" irb> person.date_of_birth => Thu, 01 Jan 1998 irb> person.active => false
渡されたハッシュがpermitted?メソッドに応答し、かつこのメソッドの戻り値がfalseの場合は、ActiveModel::ForbiddenAttributesError例外が発生します。
permitted?メソッドは、strong parametersでリクエストからのパラメータをparams属性に設定するときに使われます。
irb> person = Person.new # strong parametersチェックを用いて、リクエストから受け取るパラメータと同じようなハッシュをビルドする irb> params = ActionController::Parameters.new(name: "John") => #<ActionController::Parameters {"name" => "John"} permitted: false> irb> person.assign_attributes(params) => # Raises ActiveModel::ForbiddenAttributesError irb> person.name => nil # 代入を許可したい属性で代入を許可する irb> permitted_params = params.permit(:name) => #<ActionController::Parameters {"name" => "John"} permitted: true> irb> person.assign_attributes(permitted_params) irb> person.name => "John" attributes=エイリアスメソッドassign_attributesにはattributes=というエイリアスメソッドもあります。
エイリアスメソッドは、同じ操作を実行しますが呼び名が異なるメソッドです。エイリアスはコードの読みやすく使いやすくするために存在します。
attributes=メソッドで複数の属性を一度に設定する方法の例を以下に示します。
irb> person = Person.new irb> person.attributes = { name: "John", date_of_birth: "1998-01-01", active: false } irb> person.name => "John" irb> person.date_of_birth => "1998-01-01" assign_attributesとattributes=はどちらもメソッド呼び出しであり、代入する属性のハッシュを引数として渡せます。Rubyでは多くの場合、メソッド呼び出しの丸かっこ()やハッシュ定義の波かっこ{}を省略できます。 attributes=のような「セッター」メソッドの呼び出しでは、丸かっこ()を省略することがよくあります(()を省略しなくても振る舞いは変わりません)が、セッターメソッドにハッシュを渡す場合は波かっこ{}を省略してはいけない点にご注意ください。たとえばperson.attributes=({ name: "John" })は正常に動作しますが、person.attributes = name: "John"ではSyntaxErrorが発生します。assign_attributesなどの(セッターでない)メソッド呼び出しでは、ハッシュ引数の丸かっこ()や{}を両方書くことも両方省略することも可能です。たとえば、assign_attributes name: "John"やassign_attributes({ name: "John" })はどちらもRubyコードとして完全に有効です。ただしassign_attributes { name: "John" }という波かっこ{}だけの書き方は有効ではなく、SyntaxErrorが発生します(波かっこ{}がハッシュ引数なのかブロックなのかをRubyが区別できないため)。
AttributeMethodsモジュールActiveModel::AttributeMethodsモジュールは、モデルの属性メソッドを動的に定義する方法を提供します。このモジュールは、属性へのアクセスと操作をシンプルにするうえで特に有用であり、クラスのメソッドにカスタムのプレフィックスやサフィックスを追加することも可能です。プレフィックスとサフィックス、およびそれらをオブジェクトのどのメソッドで利用するかを定義するには、次の手順で実装できます。
ActiveModel::AttributeMethodsをincludeします。attribute_method_suffix、attribute_method_prefix、attribute_method_affixなど)を呼び出します。define_attribute_methodsを呼び出して、プレフィックスやサフィックスを付ける必要がある属性を宣言します。_attributeメソッドを定義します。これらのメソッドのattributeパラメータは、define_attribute_methodsで渡される引数に置き換えられます(以下の例ではname)。attribute_method_prefixやattribute_method_suffixは、メソッドの作成で利用するプレフィックスまたはサフィックスを定義するのに使います。attribute_method_affixは、プレフィックスとサフィックスを両方同時に定義するのに使います。
class Person include ActiveModel::AttributeMethods attribute_method_affix prefix: "reset_", suffix: "_to_default!" attribute_method_prefix "first_", "last_" attribute_method_suffix "_short?" define_attribute_methods "name" attr_accessor :name private # 'first_name'用の属性メソッド呼び出し def first_attribute(attribute) public_send(attribute).split.first end # 'last_name'用の属性メソッド呼び出し def last_attribute(attribute) public_send(attribute).split.last end # 'name_short?'用の属性メソッド呼び出し def attribute_short?(attribute) public_send(attribute).length < 5 end # 'reset_name_to_default!'用の属性メソッド呼び出し def reset_attribute_to_default!(attribute) public_send("#{attribute}=", "Default Name") end end
irb> person = Person.new irb> person.name = "Jane Doe" irb> person.first_name => "Jane" irb> person.last_name => "Doe" irb> person.name_short? => false irb> person.reset_name_to_default! => "Default Name"
定義されていないメソッドを呼び出すと、NoMethodErrorをraiseします。
alias_attributeActiveModel::AttributeMethodsは、alias_attributeによる属性メソッドのエイリアス作成機能を提供しています。
以下の例では、nameのエイリアス属性full_nameを作成しています。どちらの属性も同じ値を返しますが、エイリアスfull_nameはこの属性に名と姓が含まれていることが明示されています。
class Person include ActiveModel::AttributeMethods attribute_method_suffix "_short?" define_attribute_methods :name attr_accessor :name alias_attribute :full_name, :name private def attribute_short?(attribute) public_send(attribute).length < 5 end end
irb> person = Person.new irb> person.name = "Joe Doe" irb> person.name => "Joe Doe" # `full_name`は`name`のエイリアスであり、`name`と同じ値を返す irb> person.full_name => "Joe Doe" irb> person.name_short? => false # `full_name_short?`は`name_short?`のエイリアスであり、`name_short?`と同じ値を返す irb> person.full_name_short? => false
CallbacksモジュールActiveModel::Callbacksは、Active RecordスタイルのコールバックをプレーンなRubyオブジェクトの形で提供します。コールバックを利用して、before_updateやafter_createなどのモデルのライフサイクルイベントにフックすることも、モデルのライフサイクルの特定の時点で実行されるカスタムロジックを定義することも可能になります。
ActiveModel::Callbacksは、以下の手順に沿って実装できます。
ActiveModel::Callbacksをextendします。define_model_callbacksで確立します。たとえば:updateなどのメソッドを指定すると、:updateイベントの3つのデフォルトコールバック(before、around、after)がすべて自動的にincludeされます。run_callbacksを利用して、特定のイベントがトリガーされたときにコールバックチェインを実行します。before_update、after_update、around_updateメソッドを利用できるようになります。class Person extend ActiveModel::Callbacks define_model_callbacks :update before_update :reset_me after_update :finalize_me around_update :log_me # `define_model_callbacks`メソッドには、指定のイベントでコールバックを実行する`run_callbacks`が含まれる def update run_callbacks(:update) do puts "updateメソッドが呼び出された" end end private # オブジェクトでupdateが呼び出されると、`before_update`コールバックでこのメソッドが呼び出される def reset_me puts "reset_me method: updateメソッドの前に呼び出される" end # オブジェクトでupdateが呼び出されると、`after_update`コールバックでこのメソッドが呼び出される def finalize_me puts "finalize_me method: updateメソッドの後に呼び出される" end # オブジェクトでupdateが呼び出されると、`around_update`コールバックでこのメソッドが呼び出される def log_me puts "log_me method: updateメソッドの前後に呼び出される" yield puts "log_me method: ブロックの呼び出し成功" end end
上記のクラスによって生成された以下の結果には、コールバックが呼び出された順序が示されています。
irb> person = Person.new irb> person.update reset_me method: updateメソッドの前に呼び出される log_me method: updateメソッドの前に呼び出された updateメソッドが呼び出された log_me method: ブロックの呼び出し成功 finalize_me method: updateメソッドの後に呼び出される => nil
aroundコールバックを定義するときは、上記のコード例のようにブロック内でyieldすることを忘れてはいけません(さもないとコールバックが実行されません)。
define_model_callbacksに渡すmethod_nameには、!、?、=で終わるメソッド名は使えません。また、同じコールバックを複数回定義すると以前のコールバック定義は上書きされます。
define_model_callbacksメソッドにonlyオプションを渡すと、特定のコールバックを指定して作成できます。
define_model_callbacks :update, :create, only: [:after, :before]
これにより、before_create/after_createと、before_update/after_updateコールバックだけが作成され、around_*コールバックは作成されずにスキップされます。このonlyオプションは、そのメソッド呼び出しで定義されたすべてのコールバックに適用されます。define_model_callbacksを複数回呼び出して、異なるライフサイクルイベントを指定することも可能です。
define_model_callbacks :create, only: :after define_model_callbacks :update, only: :before define_model_callbacks :destroy, only: :around
上のコードはafter_create、before_update、around_destroyだけを作成します。
before_<type>やafter_<type>やaround_<type>にクラスを渡すことで、コールバックがいつ、どのようなコンテキストでトリガーされるかをより細かく制御できます。コールバックは、そのクラスにある<action>_<type>メソッドをトリガーし、そのクラスのインスタンスを引数として渡します。
class Person extend ActiveModel::Callbacks define_model_callbacks :create before_create PersonCallbacks end class PersonCallbacks def self.before_create(obj) # この`obj`は、コールバックが呼び出されるPersonインスタンス end end
コールバックチェインは、:abortをスローすればいつでも中断できます。これは、Active Recordコールバックの仕組みと似ています。
以下の例では、reset_meメソッドの更新の前に:abortをスローしているので、before_updateを含む残りのコールバックチェインは中止され、updateメソッドの本体は実行されません。
class Person extend ActiveModel::Callbacks define_model_callbacks :update before_update :reset_me after_update :finalize_me around_update :log_me def update run_callbacks(:update) do puts "updateメソッドが呼び出された" end end private def reset_me puts "reset_me method: updateメソッドの前に呼び出される" throw :abort puts "reset_me method: abort後のコード" end def finalize_me puts "finalize_me method: updateメソッドの後に呼び出される" end def log_me puts "log_me method: updateメソッドの前後に呼び出される" yield puts "log_me method: ブロックの呼び出し成功" end end
irb> person = Person.new irb> person.update reset_me method: updateメソッドの前に呼び出される => false
ConversionモジュールActiveModel::Conversionモジュールは、オブジェクトをさまざまな目的に応じて多様な形式に変換できるメソッドのコレクションです。一般的なユースケースは、URLやフォームフィールドなどをビルドするときにオブジェクトを文字列や整数に変換することです。
ActiveModel::Conversionモジュールは、以下のメソッドをクラスに追加します。
to_modelto_keyto_paramto_partial_pathメソッドの戻り値は、persisted?メソッドが定義されているかどうか、およびidメソッドが指定されているかどうかによって異なります。 persisted?メソッドは、オブジェクトがデータベースまたはストアに保存されている場合はtrueを返し、それ以外の場合はfalseを返す必要があります。 idメソッドは、オブジェクトのIDを参照する必要があります。オブジェクトが保存されていない場合はnilを参照する必要があります。
class Person include ActiveModel::Conversion attr_accessor :id def initialize(id) @id = id end def persisted? id.present? end end
to_modelto_modelメソッドは、そのオブジェクト自身を返します。
irb> person = Person.new(1) irb> person.to_model == person => true
自作のモデルがActive Modelオブジェクトらしく振る舞わない場合は、:to_modelを自分で定義する必要があります。この場合:to_modelは、Active Modelに準拠したメソッドでオブジェクトをラップするプロキシオブジェクトを返さなければなりません。
class Person def to_model # Active Modelに準拠したメソッドでオブジェクトをラップするプロキシオブジェクト PersonModel.new(self) end end
to_keyto_keyメソッドは、オブジェクトが永続化されているかどうかにかかわらず、いずれかの属性が設定済みであれば、オブジェクトのキー属性の配列を返します。キー属性が存在しない場合はnilを返します。
irb> person.to_key => [1]
キー属性は、オブジェクトを識別するために使われる属性です。たとえば、データベースに裏付けられたモデルでは、キー属性は主キーです。
to_paramto_paramメソッドは、URLでの利用に適したオブジェクトのキーのstring表現を返します。 persisted?がfalseの場合はnilを返します。
irb> person.to_param => "1"
to_partial_pathto_partial_pathメソッドは、オブジェクトに関連付けられているパスをstring表現で返します。Action Packはこのメソッドを用いて、オブジェクトを表す適切なパーシャルを探索します。
irb> person.to_partial_path => "people/person"
DirtyモジュールActiveModel::Dirtyモジュールは、モデル属性に加えられた変更を保存前にトラッキングするときに有用です。この機能を活用することで、どの属性が変更されたか、その以前の値と現在の値が何であるかを判断して、変更に基づいた処理を実行できるようになるので、特にアプリケーション内の監査・バリデーション、条件付きロジックで便利です。このモジュールは、Active Recordと同じような方法でオブジェクトの変更をトラッキングする方法を提供します。
あるオブジェクトが数度にわたって変更され、保存されていない状態は、「ダーティ(dirty:汚れた)」状態です。このオブジェクトでは、属性名に基づいたアクセサメソッドが利用できます。
ActiveModel::Dirtyモジュールを利用するには、以下を実装する必要があります。
includeします。define_attribute_methodsで定義します。属性名_will_change!を呼び出します。changes_appliedを呼び出します。clear_changes_informationを呼び出します。restore_attributesを呼び出します。これで、そのオブジェクトでActiveModel::Dirtyモジュールが提供するメソッドを利用して、変更が発生したすべての属性のリストや、変更された属性の元の値や、属性に加えられた変更内容を取得できるようになります。
例として、first_name属性とlast_name属性を持つ以下のPersonクラスを例に、これらの属性への変更をActiveModel::Dirtyモジュールでトラッキングする方法を決定してみましょう。
class Person include ActiveModel::Dirty attr_reader :first_name, :last_name define_attribute_methods :first_name, :last_name def initialize @first_name = nil @last_name = nil end def first_name=(value) first_name_will_change! unless value == @first_name @first_name = value end def last_name=(value) last_name_will_change! unless value == @last_name @last_name = value end def save # データを永続化する(ダーティなデータは削除され、`changes`の内容は`previous_changes`に移動する) changes_applied end def reload! # ダーティなデータ(`changes`と`previous_changes`の内容)をすべて削除する clear_changes_information end def rollback! # 指定の属性の直前のデータをすべて復元する restore_attributes end end
irb> person = Person.new # `Person`オブジェクトのインスタンス生成直後は変更された属性はない irb> person.changed? => false irb> person.first_name = "Jane Doe" irb> person.first_name => "Jane Doe"
changed?: 1個以上の属性で変更が未保存の場合はtrue、それ以外の場合はfalseを返します。
irb> person.changed? => true
changed: 変更が保存されていない属性名のリストを配列で返します。
irb> person.changed => ["first_name"]
changed_attributes: 変更が未保存の属性名と元の値のリストをハッシュとして返します(例: 属性名 => 元の値)。
irb> person.changed_attributes => {"first_name" => nil} changes: 変更された属性名と「元の値と新しい値」のリストをハッシュとして返します(例: 属性名 => [元の値, 新しい値])。
irb> person.changes => {"first_name" => [nil, "Jane Doe"]} previous_changes: モデルが保存される前(つまりchanges_appliedが呼び出される前)に変更されていた属性のリストをハッシュで返します。
irb> person.previous_changes => {} irb> person.save irb> person.previous_changes => {"first_name" => [nil, "Jane Doe"]} irb> person = Person.new irb> person.changed? => false irb> person.first_name = "John Doe" irb> person.first_name => "John Doe"
属性名_changed?: その属性が変更されているかどうかをチェックします。
irb> person.first_name_changed? => true
属性名_was: その属性の直前の値を返します。
irb> person.first_name_was => nil
属性名_change: その属性が変更されていれば、属性の「変更直前の値」と「現在の値」を[元の値, 新しい値]という配列で返し、変更がない場合はnilを返します。
irb> person.first_name_change => [nil, "John Doe"] irb> person.last_name_change => nil
属性名_previously_changed?: その属性が、モデルの保存前(つまりchanges_appliedが呼び出される前)に変更されていたかどうかをチェックします。
irb> person.first_name_previously_changed? => false irb> person.save irb> person.first_name_previously_changed? => true
属性名_previous_change: その属性がモデルの保存前(つまりchanges_appliedが呼び出される前)に変更されていれば、属性の「変更直前の値」と「現在の値」を[元の値, 新しい値]という配列で返し、それ以外の場合はnilを返します。
irb> person.first_name_previous_change => [nil, "John Doe"]
NamingモジュールActiveModel::Namingモジュールは、命名やルーティングの管理を行いやすくするクラスメソッドとヘルパーメソッドを追加します。このモジュールが定義するmodel_nameクラスメソッドは、ActiveSupport::Inflectorのメソッドを活用してさまざまなアクセサを定義します。
class Person extend ActiveModel::Naming end
name: そのモデルの名前を返します。
irb> Person.model_name.name => "Person"
singular: レコードやクラスのクラス名を単数形で返します。
irb> Person.model_name.singular => "person"
plural: レコードやクラスのクラス名を複数形で返します。
irb> Person.model_name.plural => "people"
element: 名前空間を除いたsnake_case形式の名前(単数形)を返します。このメソッドは、Action PackやAction Viewのヘルパーがパーシャルやフォームの名前でレンダリングするときに使われます。
irb> Person.model_name.element => "person"
human: 国際化(I18n)に基づいてモデル名をより人間に分かりやすい形式に変換します。デフォルトでは、クラス名をアンダースコアで区切ってからhumanizeします。
irb> Person.model_name.human => "Person"
collection: 名前空間を除いたsnake_case形式の名前(複数形)を返します。このメソッドは、Action PackやAction Viewのヘルパーがパーシャルやフォームの名前でレンダリングするときに使われます。
irb> Person.model_name.collection => "people"
param_key: パラメータ名として利用できる文字列を返します。
irb> Person.model_name.param_key => "person"
i18n_key: I18nで使う訳文のキー名を返します。モデル名をsnake_case化したものをシンボルとして返します。
irb> Person.model_name.i18n_key => :person
route_key: ルーティング名の生成で利用できる文字列を返します。
irb> Person.model_name.route_key => "people"
singular_route_key: ルーティング名の生成で利用できる文字列を単数形で返します。
irb> Person.model_name.singular_route_key => "person"
uncountable?: レコードやクラスのクラス名が不可算名詞かどうかを判定します。
irb> Person.model_name.uncountable? => false
分離されたRailsエンジン内では、Namingの一部のメソッド(param_key、route_key、singular_route_keyなど)におけるモデルの名前空間が異なります。
フォームヘルパーやURL生成で使われるモデル名を別の名前に変更したい場合があります。これは、モデルを完全な名前空間で参照可能にしながら、モデル名をさらに使いやすくしたい場合に便利です。
たとえば、Railsアプリケーションにperson名前空間があり、新しいperson::Profileのフォームを作成したいとします。
デフォルトのRailsは、名前空間personを含む/person/profilesというURLでフォームを生成します。ただし、URLで名前空間を含まないprofilesを指すようにしたい場合は、model_nameメソッドを次のようにカスタマイズできます。
module Person class Profile include ActiveModel::Model def self.model_name ActiveModel::Name.new(self, nil, "Profile") end end end
上のセットアップでは、新しいperson::Profileを作成するためのフォームをform_withヘルパーで作成すると、/person/profilesというURLではなく/profilesというURLを持つフォームが生成されます(model_nameメソッドがProfileを返すようにオーバーライドされたため)。
さらに、パスヘルパーが名前空間なしで生成されるので、person_profiles_pathの代わりにprofiles_pathでprofilesリソースのURLを生成できます。profiles_pathヘルパーを利用可能にするには、config/routes.rbファイルでperson::Profileモデルのルーティングを以下のように定義する必要があります。
Rails.application.routes.draw do resources :profiles end
これで、前のセクションで説明したメソッドに対してモデルが次の値を返すようになります。
irb> name = ActiveModel::Name.new(Person::Profile, nil, "Profile") => #<ActiveModel::Name:0x000000014c5dbae0 irb> name.singular => "profile" irb> name.singular_route_key => "profile" irb> name.route_key => "profiles"
SecurePasswordモジュールActiveModel::SecurePasswordモジュールは、あらゆるパスワードを暗号化された形式で安全に保存する方法を提供します。このモジュールをincludeするとhas_secure_passwordクラスメソッドが提供され、デフォルトで特定の検証を伴うpasswordアクセサを定義します。
ActiveModel::SecurePasswordはbcryptに依存しているため、利用する場合は以下のようにこのgemをGemfileに追加してください。
gem "bcrypt"
ActiveModel::SecurePasswordを利用する場合は、password_digest属性が必要です。
以下のバリデーションは自動的に追加されます。
XXX_confirmationで渡された)パスワード確認入力と等しいことbcryptは暗号化の前に文字列をこの長さに切り詰めるため)。パスワードの確認が不要な場合は、password_confirmation属性の値はそのままにしておいてください(つまりパスワード確認用のフィールドをフォームに置かない)。この属性の値がnilの場合はバリデーションされません。
さらなるカスタマイズ方法として、引数にvalidations: falseオプションを渡すことでデフォルトのバリデーションを抑制することも可能です。
class Person include ActiveModel::SecurePassword has_secure_password has_secure_password :recovery_password, validations: false attr_accessor :password_digest, :recovery_password_digest end
irb> person = Person.new # パスワードが存在しない場合 irb> person.valid? => false # パスワード確認がパスワードと一致しない場合 irb> person.password = "aditya" irb> person.password_confirmation = "nomatch" irb> person.valid? => false # パスワードが72バイトを超えた場合 irb> person.password = person.password_confirmation = "a" * 100 irb> person.valid? => false # passwordのみが入力され、password_confirmationが入力されなかった場合 irb> person.password = "aditya" irb> person.valid? => true # すべてのバリデーションがパスした場合 irb> person.password = person.password_confirmation = "aditya" irb> person.valid? => true irb> person.recovery_password = "42password" # `authenticate`は`authenticate_password`のエイリアス irb> person.authenticate("aditya") => #<Person> # == person irb> person.authenticate("notright") => false irb> person.authenticate_password("aditya") => #<Person> # == person irb> person.authenticate_password("notright") => false irb> person.authenticate_recovery_password("aditya") => false irb> person.authenticate_recovery_password("42password") => #<Person> # == person irb> person.authenticate_recovery_password("notright") => false irb> person.password_digest => "$2a$04$gF8RfZdoXHvyTjHhiU4ZsO.kQqV9oonYZu31PRE4hLQn3xM2qkpIy" irb> person.recovery_password_digest => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC" SerializationモジュールActiveModel::Serializationモジュールはオブジェクトの基本的なシリアライズ機能を提供します。この機能を利用するには、シリアライズする属性を含む属性ハッシュを宣言する必要があります。また、属性はシンボルリテラル(:nameなど)ではなく文字列リテラル("name"など)で記述しなければならない点にご注意ください。
class Person include ActiveModel::Serialization attr_accessor :name, :age def attributes # シリアライズする属性を宣言する { "name" => nil, "age" => nil } end def capitalized_name # 宣言したメソッドは後からシリアライズ済みハッシュにinclude可能 name&.capitalize end end
これで、serializable_hashメソッドでオブジェクトのシリアライズ済みハッシュにアクセス可能になります。serializable_hashメソッドの有効なオプションには、:only、:except、:methods、および:includeが含まれます。
irb> person = Person.new irb> person.serializable_hash => {"name" => nil, "age" => nil} # name属性とage属性を設定してオブジェクトをシリアライズする irb> person.name = "bob" irb> person.age = 22 irb> person.serializable_hash => {"name" => "bob", "age" => 22} # capitalized_nameメソッドをincludeするにはmethodsオプションを使う irb> person.serializable_hash(methods: :capitalized_name) => {"name" => "bob", "age" => 22, "capitalized_name" => "Bob"} # name属性だけを含めたい場合はonlyオプションを使う irb> person.serializable_hash(only: :name) => {"name" => "bob"} # name属性だけを除外したい場合はexceptオプションを使う irb> person.serializable_hash(except: :name) => {"age" => 22} includesオプションの利用例については、以下に定義されているように、もう少し複雑なシナリオが必要です。
class Person include ActiveModel::Serialization attr_accessor :name, :notes # `has_many :notes`をエミュレーションする def attributes { "name" => nil } end end class Note include ActiveModel::Serialization attr_accessor :title, :text def attributes { "title" => nil, "text" => nil } end end
irb> note = Note.new irb> note.title = "Weekend Plans" irb> note.text = "Some text here" irb> person = Person.new irb> person.name = "Napoleon" irb> person.notes = [note] irb> person.serializable_hash => {"name" => "Napoleon"} irb> person.serializable_hash(include: { notes: { only: "title" }}) => {"name" => "Napoleon", "notes" => [{"title" => "Weekend Plans"}]} ActiveModel::Serializers::JSONモジュールActive Modelは、JSONシリアライズ/デシリアライズ用のActiveModel::Serializers::JSONモジュールも提供しています。
JSONシリアライズを利用するには、includeするモジュールをActiveModel::SerializationからActiveModel::Serializers::JSONに変更します。前者は既にinclude済みなので、明示的にincludeする必要はありません。
class Person include ActiveModel::Serializers::JSON attr_accessor :name def attributes { "name" => nil } end end
as_jsonメソッドは、serializable_hashと同様に、モデルを表すハッシュ(キーは文字列)を提供します。 to_jsonメソッドは、モデルを表すJSON文字列を返します。
irb> person = Person.new # モデルを表すハッシュを返す(キーは文字列) irb> person.as_json => {"name" => nil} # モデルを表すJSON文字列を返す irb> person.to_json => "{\"name\":null}" irb> person.name = "Bob" irb> person.as_json => {"name" => "Bob"} irb> person.to_json => "{\"name\":\"Bob\"}" JSON文字列を元にモデルの属性を定義することも可能です。これを行うには、まずクラスでattributes=メソッドを定義します。
class Person include ActiveModel::Serializers::JSON attr_accessor :name def attributes=(hash) hash.each do |key, value| public_send("#{key}=", value) end end def attributes { "name" => nil } end end
これで、以下のようにpersonのインスタンスを作成してfrom_jsonで属性を設定できます。
irb> json = { name: "Bob" }.to_json => "{\"name\":\"Bob\"}" irb> person = Person.new irb> person.from_json(json) => #<Person:0x00000100c773f0 @name="Bob"> irb> person.name => "Bob" TranslationモジュールActiveModel::Translationモジュールは、オブジェクトをRailsの国際化(I18n)フレームワークと統合します。
class Person extend ActiveModel::Translation end
human_attribute_nameメソッドを使えば、属性名を人間が読みやすい形式に変換できます。人間用のフォーマットはロケールファイルで定義されます。
# config/locales/app.pt-BR.yml pt-BR: activemodel: attributes: person: name: "Nome"
irb> Person.human_attribute_name("name") => "Name" irb> I18n.locale = :"pt-BR" => :"pt-BR" irb> Person.human_attribute_name("name") => "Nome" ValidationsモジュールActiveModel::Validationsモジュールは、オブジェクトをバリデーション(検証)する機能を追加します。バリデーションは、アプリケーション内のデータの整合性と一貫性を確保するために重要です。バリデーションをモデルに組み込むことで、属性値の正確性を管理するルールを定義して、無効なデータの発生を防げるようになります。
class Person include ActiveModel::Validations attr_accessor :name, :email, :token validates :name, presence: true validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } validates! :token, presence: true end
irb> person = Person.new irb> person.token = "2b1f325" irb> person.valid? => false irb> person.name = "Jane Doe" irb> person.email = "me" irb> person.valid? => false irb> person.email = "jane.doe@gmail.com" irb> person.valid? => true # `token`は未設定の場合に例外を発生する(validate!メソッドを利用) irb> person.token = nil irb> person.valid? => "Token can't be blank (ActiveModel::StrictValidationFailed)"
以下のいくつかのメソッドを利用してバリデーションを追加できます。
validate: バリデーションをメソッドまたはブロックの形でクラスに追加します。
validates: validatesメソッドに属性を渡すことで、すべてのデフォルトバリデータをショートカットで利用できます。
validates!(またはstrict: trueオプションを指定): エンドユーザー側で修正できない特殊なバリデーションを定義するのに使われます。バリデーションメソッドに!またはstrict: trueを指定すると、バリデーション失敗時にエラーを追加するのではなく、常にActiveModel::StrictValidationFailedをraiseします。
validates_with: 指定のクラス(複数可)にレコードを渡して、より複雑な条件に基づいてエラーを追加できます。
validates_each: 渡したブロックで個別の属性をバリデーションします。
以下のオプションの中には、特定のバリデータでしか利用できないものもあります。オプションが特定のバリデータで利用できるかどうかを判断するには、バリデーションのAPIドキュメントを参照してください。
:on: バリデーションを追加するコンテキストを指定します。引数にはシンボルまたはシンボルの配列を渡せます(例: on: :create、on: :custom_validation_context、on: [:create, :custom_validation_context])。 バリデーションで:onオプションを指定しない場合は、コンテキストと無関係に実行されます。 :onオプションを指定したバリデーションは、指定されたコンテキストでのみ実行されます。 バリデーションにはvalid?(:context)でコンテキストを渡せます。
:if: バリデーションを実行するかどうかを決定するために呼び出すメソッド、proc、または文字列を指定します(例: if: :allow_validation、if: -> {signup_step > 2 })。 渡すメソッド、proc、または文字列は、trueまたはfalse値を返すか、trueまたはfalseのいずれかに評価される必要があります。
:unless: バリデーションを実行するかどうかを決定するために呼び出すメソッド、proc、または文字列を指定します(例: unless: :skip_validation、unless: Proc.new { |user| user.signup_step <= 2 })。 渡すメソッド、proc、または文字列は、trueまたはfalse値を返すか、trueまたはfalseのいずれかに評価される必要があります。
:allow_nil: 属性がnilの場合にバリデーションをスキップします。
:allow_blank: 属性が空の場合にバリデーションをスキップします。
:strict: :strict: trueオプションを設定している場合、エラーを追加する代わりにActiveModel::StrictValidationFailedをraiseします。:strictオプションに他の例外を設定することも可能です。
validateを同じメソッドで複数回呼び出すと、以前の定義が上書きされます。
ErrorsモジュールActiveModel::Validationsモジュールは、このモジュールをincludeしたクラスのインスタンスに対して、errorsメソッドを自動的に追加し、さらに、ActiveModel::Errorsの新しいオブジェクトを自動的に用意します。したがって、これらの処理を手動で行う必要はありません。
オブジェクトが有効かどうかを確認するには、オブジェクトに対してvalid?を実行します。オブジェクトが有効でない場合はfalseが返され、エラーがerrorsオブジェクトに追加されます。
irb> person = Person.new irb> person.email = "me" irb> person.valid? => # Raises Token can't be blank (ActiveModel::StrictValidationFailed) irb> person.errors.to_hash => {:name => ["can't be blank"], :email => ["is invalid"]} irb> person.errors.full_messages => ["Name can't be blank", "Email is invalid"] Lint::TestsモジュールActiveModel::Lint::Testsモジュールを利用することで、オブジェクトがActive Model APIに準拠しているかどうかをテストできます。TestCaseにActiveModel::Lint::Testsをincludeすれば、オブジェクトがActive Model APIに完全に準拠しているかどうか、準拠していない場合はAPIのどの側面が実装されていないのかを示すテストが組み込まれます。
これらのテストは、戻り値の意味が正しいかどうかを判定するものではありません。たとえば、常にtrueを返すようにvalid?を実装すればテストはパスしますが、APIとしては無意味です。意味のある値を返すようにするのは、API開発者の責任です。
渡すオブジェクトは、to_modelを呼び出したときに準拠済みオブジェクトを返すことが期待されます。to_modelがselfを返すのはまったく問題ありません。
app/models/person.rb
class Person include ActiveModel::API end
test/models/person_test.rb
require "test_helper" class PersonTest < ActiveSupport::TestCase include ActiveModel::Lint::Tests setup do @model = Person.new end end
これらのテストメソッドは、APIドキュメントのActiveModel::Lint::Testsに記載されています。
このテストは、以下のコマンドで実行できます。
$ bin/rails test Run options: --seed 14596 # Running: ...... Finished in 0.024899s, 240.9735 runs/s, 1204.8677 assertions/s. 6 runs, 30 assertions, 0 failures, 0 errors, 0 skips
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。