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?
# deliver email
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
的類別都可以與 form_with
、render
和任何其他 Action View 輔助方法一起使用,就像 Active Record 物件一樣。
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
# 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.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
當物件經過一個或多個屬性變更且尚未儲存時,該物件就會變為 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
# do save work...
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"
# Returns true if any of the attributes have unsaved changes.
irb> person.changed?
=> true
# Returns a list of attributes that have changed before saving.
irb> person.changed
=> ["first_name"]
# Returns a Hash of the attributes that have changed with their original values.
irb> person.changed_attributes
=> {"first_name"=>nil}
# Returns a Hash of changes, with the attribute names as the keys, and the values as an array of the old and new values for that field.
irb> person.changes
=> {"first_name"=>[nil, "First Name"]}
1.5.2 基於屬性的存取器方法
追蹤特定屬性是否已變更。
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
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
新增多個類別方法,讓命名和路由更容易管理。此模組定義 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.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 也提供 ActiveModel::Serializers::JSON
模組,用於 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 Testes de Lint
ActiveModel::Lint::Tests
permite que você teste se um objeto está em conformidade com a API do Active Model.
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
Um objeto não é obrigado a implementar todas as APIs para funcionar com o Action Pack. Este módulo tem o objetivo apenas de orientar caso você queira todos os recursos prontos para uso.
1.12 SecurePassword
ActiveModel::SecurePassword
fornece uma forma de armazenar com segurança qualquer senha em um formato criptografado. Quando você inclui este módulo, um método de classe has_secure_password
é fornecido, que define um acessador password
com certas validações nele por padrão.
1.12.1 Requisitos
ActiveModel::SecurePassword
depende de bcrypt
, então inclua esta gem no seu Gemfile
para usar ActiveModel::SecurePassword
corretamente. Para que isso funcione, o modelo deve ter um acessador chamado XXX_digest
. Onde XXX
é o nome do atributo da sua senha desejada. As seguintes validações são adicionadas automaticamente
- A senha deve estar presente.
- A senha deve ser igual à sua confirmação (fornecida
XXX_confirmation
é passada junto). - O comprimento máximo de uma senha é de 72 bytes (obrigatório como
bcrypt
, do qual ActiveModel::SecurePassword depende, trunca a string para este tamanho antes de criptografá-la).
1.12.2 Exemplos
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
# When password is blank.
irb> person.valid?
=> false
# When the confirmation doesn't match the password.
irb> person.password = 'aditya'
irb> person.password_confirmation = 'nomatch'
irb> person.valid?
=> false
# When the length of password exceeds 72.
irb> person.password = person.password_confirmation = 'a' * 100
irb> person.valid?
=> false
# When only password is supplied with no password_confirmation.
irb> person.password = 'aditya'
irb> person.valid?
=> true
# When all validations are passed.
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"
Feedback
Você é encorajado a ajudar a melhorar a qualidade deste guia.
Contribua se você vir erros de digitação ou erros de fato. Para começar, você pode ler nossa seção de contribuições de documentação.
Você também pode encontrar conteúdo incompleto ou que não esteja atualizado. Adicione qualquer documentação ausente para o principal. Verifique primeiro Edge Guides para verificar se os problemas já foram corrigidos ou não no branch principal. Verifique as Diretrizes dos Guias do Ruby on Rails para estilo e convenções.
如果您發現需要修復但無法自行修補的任何原因,請 開啟問題。
最後但並非最不重要的一點,任何有關 Ruby on Rails 文件的討論都非常歡迎在 官方 Ruby on Rails 論壇 中進行。