このガイドでは、モデルクラスを使って作業を開始するのに必要なことをすべて解説します。Action Packヘルパーは、Active Modelのおかげで非Active Recordモデルとやりとりすることができます。Active Modelを使用することで、カスタムのORM (オブジェクトリレーショナルマッピング) を作成してRailsフレームワークの外で使用することもできます。
このガイドの内容:
Active Modelは多くのモジュールを含むライブラリであり、それらのモジュールはRailsのAction Packライブラリとやりとりする必要のあるフレームワークで使用されます。Active Modelは、クラスで使用する既知の一連のインターフェイスを提供します。そのうちのいくつかについて以下で説明します。
ActiveModel::API
は、クラスをAction PackやAction Viewと連携させる機能をクラスに追加します。
class EmailContact include ActiveModel::API attr_accessor :name, :email, :message validates :name, :email, :message, presence: true def deliver if valid? # deliver email end end end
ActiveModel::API
をinclude
すると、以下のような機能が使えるようになります。
また、Active Recordオブジェクトと同様に、属性のハッシュを持つオブジェクトを初期化する機能も使えます。
irb> email_contact = EmailContact.new(name: 'David', email: 'david@example.com', message: 'Hello World') irb> email_contact.name => "David" irb> email_contact.email => "david@example.com" irb> email_contact.valid? => true irb> email_contact.persisted? => false
ActiveModel::API
をinclude
したクラスは、Active Recordオブジェクトと同様に、form_with
やrender
などのAction Viewヘルパーでも利用できます。
ActiveModel::AttributeMethods
モジュールは、クラスのメソッドにカスタムのプレフィックスやサフィックスを追加できます。このモジュールを使用するには、プレフィックスまたはサフィックスを定義し、オブジェクト内にあるプレフィックス/サフィックスの追加対象となるメソッドを指定します。
class Person include ActiveModel::AttributeMethods attribute_method_prefix 'reset_' attribute_method_suffix '_highest?' define_attribute_methods 'age' attr_accessor :age private def reset_attribute(attribute) send("#{attribute}=", 0) end def attribute_highest?(attribute) send(attribute) > 100 end end
irb> person = Person.new irb> person.age = 110 irb> person.age_highest? => true irb> person.reset_age => 0 irb> person.age_highest? => false
ActiveModel::Callbacks
は、Active Recordスタイルのコールバックを提供します。これにより、必要なタイミングで実行されるコールバックを定義することができるようになります。コールバックの定義後、それらをカスタムメソッドの実行前(before)、実行後(after)、あるいは実行中(around: beforeとafterの両方)にラップすることができます。
class Person extend ActiveModel::Callbacks define_model_callbacks :update before_update :reset_me def update run_callbacks(:update) do # updateメソッドがオブジェクトに対して呼び出されるとこのメソッドが呼び出される end end def reset_me # このメソッドは、before_updateコールバックで定義されているとおり、updateメソッドがオブジェクトに対して呼び出される直前に呼び出される。 end end
クラスでpersisted?
メソッドとid
メソッドが定義されていれば、このActiveModel::Conversion
モジュールをインクルードしてRailsの変換メソッドをそのクラスのオブジェクトに対して呼び出すことができます。
class Person include ActiveModel::Conversion def persisted? false end def id nil end end
irb> person = Person.new irb> person.to_model == person => true irb> person.to_key => nil irb> person.to_param => nil
あるオブジェクトが数度にわたって変更され、保存されていない状態は、「ダーティな(dirty:汚れた)」状態です。ActiveModel::Dirty
モジュールを使うと、オブジェクトで変更が生じたかどうかを検出できます。属性名に基づいたアクセサメソッドも使えます。first_name
属性とlast_name
を持つPersonというクラスを例に考えてみましょう。
class Person include ActiveModel::Dirty define_attribute_methods :first_name, :last_name def first_name @first_name end def first_name=(value) first_name_will_change! @first_name = value end def last_name @last_name end def last_name=(value) last_name_will_change! @last_name = value end def save # 保存を実行 changes_applied end end
irb> person = Person.new irb> person.changed? => false irb> person.first_name = "First Name" irb> person.first_name => "First Name" # 属性が1つ以上変更されている場合にtrueを返す irb> person.changed? => true # 保存前に変更された属性のリストを返す irb> person.changed => ["first_name"] # 元の値から変更された属性のハッシュを返す irb> person.changed_attributes => {"first_name"=>nil} # 変更のハッシュを返す (ハッシュのキーは属性名、ハッシュの値はフィールドの新旧の値の配列) irb> person.changes => {"first_name"=>[nil, "First Name"]}
特定の属性が変更されたかどうかを検出します。
irb> person.first_name => "First Name" # attr_name_changed? irb> person.first_name_changed? => true
属性の直前の値を返します。
# attr_name_was accessor irb> person.first_name_was => nil
変更された属性の、直前の値と現在の値を両方返します。変更があった場合は配列を返し、変更がなかった場合はnilを返します。
# attr_name_change irb> person.first_name_change => [nil, "First Name"] irb> person.last_name_change => nil
Validations
モジュールActiveModel::Validations
モジュールを追加すると、クラスオブジェクトをActive Recordスタイルで検証できます。
class Person include ActiveModel::Validations attr_accessor :name, :email, :token validates :name, presence: true validates_format_of :email, with: /\A([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})\z/i validates! :token, presence: true end
irb> person = Person.new irb> person.token = "2b1f325" irb> person.valid? => false irb> person.name = 'vishnu' irb> person.email = 'me' irb> person.valid? => false irb> person.email = 'me@vishnuatrai.com' irb> person.valid? => true irb> person.token = nil irb> person.valid? ActiveModel::StrictValidationFailed
Naming
モジュールActiveModel::Naming
は、命名やルーティングの管理を支援するクラスメソッドを多数追加します。このモジュールが定義するmodel_name
クラスメソッドは、ActiveSupport::Inflector
メソッドの一部を用いて多くのアクセサを定義します。
class Person extend ActiveModel::Naming end Person.model_name.name # => "Person" Person.model_name.singular # => "person" Person.model_name.plural # => "people" Person.model_name.element # => "person" Person.model_name.human # => "Person" Person.model_name.collection # => "people" Person.model_name.param_key # => "person" Person.model_name.i18n_key # => :person Person.model_name.route_key # => "people" Person.model_name.singular_route_key # => "person"
Model
モジュールActiveModel::Model
を追加すると、Action PackやAction Viewと連携する機能をすぐに使えるようになります。
class EmailContact include ActiveModel::Model attr_accessor :name, :email, :message validates :name, :email, :message, presence: true def deliver if valid? # メールを配信 end end end
ActiveModel::Model
をinclude
すると、ActiveModel::API
のすべての機能を利用できるようになります。
ActiveModel::Serialization
は、オブジェクトに基本的なシリアライズ機能を提供します。シリアライズの対象となる属性を含む属性ハッシュを1つ宣言する必要があります。属性は文字列でなければならず、シンボルは使えません。
class Person include ActiveModel::Serialization attr_accessor :name def attributes { 'name' => nil } end end
上のようにすることで、serializable_hash
を使ってオブジェクトのシリアライズ化ハッシュにアクセスできるようになります。
irb> person = Person.new irb> person.serializable_hash => {"name"=>nil} irb> person.name = "Bob" irb> person.serializable_hash => {"name"=>"Bob"}
ActiveModel::Serializers
モジュールActive Modelは、JSONシリアライズ/デシリアライズ用のActiveModel::Serializers::JSON
モジュールも提供しています。このモジュールは前述のActiveModel::Serialization
モジュールを自動でinclude
します。
ActiveModel::Serializers::JSON
include
するモジュールをActiveModel::Serialization
からActiveModel::Serializers::JSON
に変更するだけでActiveModel::Serializers::JSON
を使えるようになります。
class Person include ActiveModel::Serializers::JSON attr_accessor :name def attributes { 'name' => nil } end end
serializable_hash
と似ているas_json
メソッドは、モデルを表現するハッシュ形式を提供します。
irb> person = Person.new irb> person.as_json => {"name"=>nil} irb> person.name = "Bob" irb> person.as_json => {"name"=>"Bob"}
JSON文字列を元にモデルの属性を定義することもできます。ただし、そのクラスにattributes=
メソッドを定義しておく必要があります。
class Person include ActiveModel::Serializers::JSON attr_accessor :name def attributes=(hash) hash.each do |key, value| send("#{key}=", value) end end def attributes { 'name' => nil } end end
上のようにすることで、Person
のインスタンスを作成してfrom_json
で属性を設定できるようになります。
irb> json = { name: 'Bob' }.to_json 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
メソッドを使って属性名を人間にとって読みやすい形式に変換できます。人間が読むための形式は独自のロケールファイルで定義します。
pt-BR: activemodel: attributes: person: name: 'Nome'
Person.human_attribute_name('name') # => "Nome"
ActiveModel::Lint::Tests
を用いて、オブジェクトがActive Model APIに準拠しているかどうかをテストできます。
app/models/person.rb
class Person include ActiveModel::Model end
test/models/person_test.rb
require "test_helper" class PersonTest < ActiveSupport::TestCase include ActiveModel::Lint::Tests setup do @model = Person.new end end
$ 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
オブジェクトがAction Packと協調するためにAPIをすべて実装することが要求されているわけではありません。このモジュールは、すぐに使える機能をすべて揃えておきたい場合のガイダンスを提供することを意図しているに過ぎません。
SecurePassword
モジュールActiveModel::SecurePassword
は、任意のパスワードを暗号化して安全に保存する手段を提供します。このモジュールをinclude
すると、バリデーション機能を備えたpassword
アクセサを定義するhas_secure_password
クラスメソッドがデフォルトで提供されます。
ActiveModel::SecurePassword
モジュールはbcrypt
gemに依存しているので、ActiveModel::SecurePassword
を正しく使うにはこのgemをGemfile
に含める必要があります。モジュールが機能するには、モデルにpassword_digest
という名前のアクセサがなくてはなりません。has_secure_password
はpassword
アクセサに以下のバリデーションを追加します。
XXX_confirmation
で渡された)パスワード確認入力と等しいことActiveModel::SecurePassword
が依存しているbcrypt
による要件であり、暗号化前にこの文字数まで切り詰められます)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 # パスワードだけがありパスワード確認入力がない場合 irb> person.password = 'aditya' irb> person.valid? => true # すべてのバリデーションをパスした場合 irb> person.password = person.password_confirmation = 'aditya' irb> person.valid? => true irb> person.recovery_password = "42password" 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('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"
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。