edge
詳細はrubyonrails.orgで: もっとRuby on Rails

Active Modelの基礎

このガイドでは、モデルクラスの使用を開始するために必要なすべての情報を提供します。Active Modelは、Action PackヘルパーがプレーンなRubyオブジェクトと対話することを可能にします。Active Modelはまた、Railsフレームワークの外部で使用するためのカスタムORMの構築を支援します。

このガイドを読み終えると、以下のことがわかります。

1 Active Modelとは何ですか?

Active Modelは、Active Recordに存在するいくつかの機能を必要とするクラスの開発に使用されるさまざまなモジュールを含むライブラリです。 以下に、これらのモジュールのいくつかを説明します。

1.1 API

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?
      # メールを送信する
    end
  end
end

ActiveModel::APIを含めると、以下のような機能が提供されます。

  • モデル名の内部調査
  • 変換
  • 翻訳
  • バリデーション

また、Active Recordオブジェクトと同様に、属性のハッシュでオブジェクトを初期化することもできます。

irb> email_contact = EmailContact.new(name: 'David', email: '[email protected]', message: 'Hello World')
irb> email_contact.name
=> "David"
irb> email_contact.email
=> "[email protected]"
irb> email_contact.valid?
=> true
irb> email_contact.persisted?
=> false

ActiveModel::APIを含む任意のクラスは、Active Recordオブジェクトと同様にform_withrender、その他のAction Viewヘルパーメソッドと一緒に使用することができます。

1.2 属性メソッド

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

1.3 コールバック

ActiveModel::Callbacksは、Active Recordスタイルのコールバックを提供します。これにより、適切なタイミングで実行されるコールバックを定義することができます。 コールバックを定義した後、それらをbefore、after、aroundのカスタムメソッドでラップすることができます。

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
    # このメソッドはオブジェクトに対してupdateが呼び出されたときにbefore_updateコールバックとして定義されたときに実行されます。
  end
end

1.4 変換

クラスが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

1.5 Dirty

オブジェクトは、属性に対して1つ以上の変更を経験し、保存されていない場合にDirtyになります。ActiveModel::Dirtyは、オブジェクトが変更されたかどうかを確認する機能を提供します。また、属性ベースのアクセサメソッドも持っています。first_namelast_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

1.5.1 オブジェクトの変更された属性のリストを直接クエリする

irb> person = Person.new
irb> person.changed?
=> false

irb> person.first_name = "First Name"
irb> person.first_name
=> "First Name"

# いずれかの属性に未保存の変更がある場合は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"]}

1.5.2 属性ベースのアクセサメソッド

特定の属性が変更されたかどうかを追跡します。 ```irb irb> person.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, "名前"]
irb> person.last_name_change
=> nil

1.6 バリデーション

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 = '[email protected]'
irb> person.valid?
=> true
irb> person.token = nil
irb> person.valid?
ActiveModel::StrictValidationFailed

1.7 名前付け

ActiveModel::Namingは、名前付けとルーティングを管理しやすくするためのいくつかのクラスメソッドを追加します。このモジュールは、いくつかのActiveSupport::Inflectorメソッドを使用していくつかのアクセサを定義するmodel_nameクラスメソッドを定義します。

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"

1.8 モデル

ActiveModel::Modelを使用すると、ActiveRecord::Baseに似たモデルを実装できます。

class EmailContact
  include ActiveModel::Model

  attr_accessor :name, :email, :message
  validates :name, :email, :message, presence: true

  def deliver
    if valid?
      # deliver email
    end
  end
end

ActiveModel::Modelを含めると、ActiveModel::APIのすべての機能が使用できます。

1.9 シリアライズ

ActiveModel::Serializationは、オブジェクトの基本的なシリアライズを提供します。シリアライズしたい属性を含む属性ハッシュを宣言する必要があります。属性はシンボルではなく文字列である必要があります。

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"}

1.9.1 ActiveModel::Serializers

Active Modelは、JSONのシリアライズ/デシリアライズのためのActiveModel::Serializers::JSONモジュールも提供します。このモジュールは、先に説明したActiveModel::Serializationモジュールも自動的に含まれます。

1.9.1.1 ActiveModel::Serializers::JSON

ActiveModel::Serializers::JSONを使用するには、含めるモジュールをActiveModel::SerializationからActiveModel::Serializers::JSONに変更するだけです。

class Person
  include ActiveModel::Serializers::JSON

  attr_accessor :name

  def attributes
    { 'name' => nil }
  end
end

as_jsonメソッドは、serializable_hashと同様に、モデルを表すハッシュを提供します。

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"

1.10 翻訳

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'
Person.human_attribute_name('name') # => "Nome"

1.11 Lintテスト

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
    
$ 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

Action Packで動作するためには、すべてのAPIを実装する必要はありません。このモジュールは、すべての機能を提供するためのガイドとしてのみ使用されます。

1.12 SecurePassword

ActiveModel::SecurePasswordを使用すると、任意のパスワードを安全に暗号化して保存する方法が提供されます。このモジュールを含めると、デフォルトでpasswordアクセサに特定のバリデーションが定義されたhas_secure_passwordクラスメソッドが提供されます。

1.12.1 要件

ActiveModel::SecurePasswordbcryptに依存しているため、ActiveModel::SecurePasswordを正しく使用するためには、Gemfileにこのgemを含める必要があります。 これを動作させるためには、モデルにXXX_digestという名前のアクセサを持つ必要があります。 ここで、XXXは希望するパスワードの属性名です。 以下のバリデーションが自動的に追加されます:

  1. パスワードは必須です。
  2. パスワードは確認用のパスワードと一致している必要があります(XXX_confirmationが渡された場合)。
  3. パスワードの最大長は72です(ActiveModel::SecurePasswordが依存するbcryptによって要求されます)。

1.12.2 例

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"

フィードバック

このガイドの品質向上にご協力ください。

タイポや事実の誤りを見つけた場合は、ぜひ貢献してください。 開始するには、ドキュメントへの貢献セクションを読んでください。

不完全なコンテンツや最新でない情報も見つかるかもしれません。 メインのドキュメントに不足しているドキュメントを追加してください。 修正済みかどうかは、まずEdge Guidesを確認してください。 スタイルと規約については、Ruby on Rails Guides Guidelinesを確認してください。

修正すべき点を見つけたが、自分で修正できない場合は、 問題を報告してください

そして最後に、Ruby on Railsのドキュメントに関するあらゆる議論は、公式のRuby on Railsフォーラムで大歓迎です。