v7.0.0
更多資訊請前往 rubyonrails.org: 更多在 Ruby on Rails

Active Model 基礎知識

本指南應為您提供開始使用 model 所需的一切 類。 Active Model 允許 Action Pack helpers 與 普通的 Ruby 物件。 Active Model 還有助於構建自定義 ORM 以供使用 在 Rails 框架之外。

閱讀本指南後,您將瞭解:

1 Active Model 是什麼?

Active Model 是一個包含開發中使用的各種 modules 的庫 Active Record 上需要一些功能的類。 下面解釋了其中一些 modules。

1.1 屬性方法

ActiveModel::AttributeMethods module 可以新增自定義字首和字尾 在類的方法上。它透過定義字首和字尾來使用 物件上的哪些方法將使用它們。

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.2 Callbacks

ActiveModel::Callbacks 給出 Active Record 樣式 callbacks。這提供了一個 能夠定義在適當時間執行的 callbacks。 定義 callbacks 後,可以用 before、after 和 around 包裹它們 自定義方法。

class Person
  extend ActiveModel::Callbacks

  define_model_callbacks :update

  before_update :reset_me

  def update
    run_callbacks(:update) do
      # This method is called when update is called on an object.
    end
  end

  def reset_me
    # This method is called when update is called on an object as a before_update callback is defined.
  end
end

1.3 轉換

如果一個類定義了 persisted?id 方法,那麼你可以包含 ActiveModel::Conversion module在那個類中,並呼叫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.4 髒

一個物件在經歷了一次或多次更改後變得髒了 屬性,尚未儲存。 ActiveModel::Dirty 能夠 檢查物件是否已更改。它還具有基於屬性的 訪問器方法。讓我們考慮一個具有 first_name 屬性的 Person 類 和 last_name

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
    # do save work...
    changes_applied
  end
end
1.4.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"]

# 返回已更改為原始 values 的屬性的 Hash。
irb> person.changed_attributes
=> {"first_name"=>nil}

# 返回更改的雜湊,屬性名稱為 keys,values 為該欄位的新舊 values 陣列。
irb> person.changes
=> {"first_name"=>[nil, "First Name"]}
1.4.2 基於屬性的訪問器方法

跟蹤特定屬性是否已更改。

irb> person.first_name
=> "First Name"

# attr_name_changed?
irb> person.first_name_changed?
=> true

跟蹤屬性的前一個 value。

# attr_name_was 訪問器
irb> person.first_name_was
=> nil

跟蹤已更改屬性的先前和當前 values。返回一個數組 如果更改,否則返回 nil。

# attr_name_change
irb> person.first_name_change
=> [nil, "First Name"]
irb> person.last_name_change
=> nil

1.5 驗證

ActiveModel::Validations module 添加了驗證物件的能力 就像在 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

1.6 命名

ActiveModel::Naming 增加了幾個類方法,使命名和路由 更容易管理。 module 定義了 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"

1.7 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?
      # deliver email
    end
  end
end

當包含 ActiveModel::Model 時,您將獲得一些功能,例如:

  • model 名稱自省
  • 轉換
  • 翻譯
  • 驗證

它還使您能夠使用屬性雜湊初始化物件, 很像任何 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::Model 的類都可以與 form_with 一起使用, render 和任何其他 Action View helper 方法,就像 Active Record 物件。

1.8 序列化

ActiveModel::Serialization 為您的物件提供基本的序列化。 您需要宣告一個屬性雜湊,其中包含您想要的屬性 連載。屬性必須是字串,而不是 symbols。

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.8.1 ActiveModel::Serializers

Active Model 還提供了 ActiveModel::Serializers::JSON module 用於 JSON 序列化/反序列化。此 module 自動包含 之前討論過 ActiveModel::Serialization module。

1.8.1.1 ActiveModel::Serializers::JSON

要使用 ActiveModel::Serializers::JSON,您只需更改 module 你包括從 ActiveModel::SerializationActiveModel::Serializers::JSON

class Person
  include ActiveModel::Serializers::JSON

  attr_accessor :name

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

as_json 方法與 serializable_hash 類似,提供了一個 Hash 表示 model。

irb> person = Person.new
irb> person.as_json
=> {"name"=>nil}
irb> person.name = "Bob"
irb> person.as_json
=> {"name"=>"Bob"}

您還可以從 JSON 字串定義 model 的屬性。 但是,您需要在類上定義 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.9 翻譯

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.10 棉絨測試

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

# 跑步:

......

Finished in 0.024899s, 240.9735 runs/s, 1204.8677 assertions/s.

6 runs, 30 assertions, 0 failures, 0 errors, 0 skips

物件不需要實現所有 API 才能使用 Action Pack。此 module 僅用於指導萬一你想要所有 開箱即用的功能。

1.11 安全密碼

ActiveModel::SecurePassword 提供了一種安全儲存任何 加密形式的密碼。當你包含這個 module 時,一個 提供了 has_secure_password 類方法,它定義了 預設情況下帶有某些驗證的 password 訪問器。

1.11.1 要求

ActiveModel::SecurePassword 依賴於 bcrypt, 所以在你的 Gemfile 中包含這個 gem 以正確使用 ActiveModel::SecurePassword。 為了使這個工作,model 必須有一個名為 XXX_digest 的訪問器。 其中 XXX 是您所需密碼的屬性名稱。 自動新增以下驗證:

  1. 密碼必須存在。
  2. 密碼應等於其確認(提供 XXX_confirmation 傳遞)。 3.密碼的最大長度為72(由bcrypt要求,ActiveModel::SecurePassword依賴)
1.11.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

# 當只提供密碼而沒有提供 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"

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"

回饋

我們鼓勵您幫助提高本指南的品質。

如果您發現任何拼寫錯誤或資訊錯誤,請提供回饋。 要開始回饋,您可以閱讀我們的 回饋 部分。

您還可能會發現不完整的內容或不是最新的內容。 請務必為 main 新增任何遺漏的文件。假設是 非穩定版指南(edge guides) 請先驗證問題是否已經在主分支上解決。 請前往 Ruby on Rails 指南寫作準則 查看寫作風格和慣例。

如果由於某種原因您發現要修復的內容但無法自行修補,請您 提出 issue

關於 Ruby on Rails 的任何類型的討論歡迎提供任何文件至 rubyonrails-docs 討論區