Основы Active Model
Это руководство познакомит вас со всем необходимым для начала использования Active Model. Active Model предоставляет способ хелперам Action Pack и Action View взаимодействовать с обычными объектами на
Это руководство познакомит вас со всем необходимым для начала использования Active Model. Active Model предоставляет способ хелперам Action Pack и Action View взаимодействовать с обычными объектами на чистом Ruby. Также он помогает с созданием гибкой, настраиваемой ORM для использования вне фреймворка Rails.
После прочтение данного руководства, вы узнаете:
- Что такое Active Model, и как он относится к Active Record.
- О разных модулях, включенных в Active Model.
- Как использовать Active Model в ваших классах.
Что такое Active Model?
Чтобы понять Active Model, вам нужно немного знать об Active Record. Active Record - это ORM (Object Relational Mapper), который соединяет объекты, данные которых требуют постоянного хранения, с реляционной базой данных. Однако он обладает функциональностью, которая полезна вне ORM, например, валидация, колбэки, переводы, возможность создавать пользовательские атрибуты и т.д.
Некоторые из этих функций были абстрагированы из Active Record для формирования Active Model. Active Model - это библиотека, содержащая различные модули, которые можно использовать для обычных объектов Ruby, которым требуются функции, похожие на модели, но которые не привязаны к какой-либо таблице в базе данных.
Итак, Active Record предоставляет интерфейс для определения моделей, соответствующих таблицам базы данных, а Active Model предоставляет функционал для создания похожих на модели классов Ruby, которым не обязательно требуется поддержка базы данных. Active Model можно использовать независимо от Active Record.
Некоторые из этих модулей описаны ниже.
API
ActiveModel::API добавляет возможность классу работать с Action Pack и Action View прямо из коробки.
При включении ActiveModel::API, по умолчанию включаются другие модули, добавляющие особенности, такие как:
Вот пример класса, включающего ActiveModel::API, и как его можно использовать:
class EmailContact
include ActiveModel::API
attr_accessor :name, :email, :message
validates :name, :email, :message, presence: true
def deliver
if valid?
# Доставляем письмо
end
end
endirb> email_contact = EmailContact.new(name: "David", email: "david@example.com", message: "Hello World")
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, может быть использован с form_with, render и любыми другими вспомогательными методами Action View, точно так же, как и объекты Active Record.
Например, 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, можно найти в руководствах по хелперам форм и по макетам и рендерингу соответственно.
Модель
ActiveModel::Model по умолчанию включает ActiveModel::API для взаимодействия с Action Pack и Action View. Это рекомендуемый подход для реализации Ruby-классов, похожих на модели. В будущем он будет расширен для добавления дополнительных функций.
class Person
include ActiveModel::Model
attr_accessor :name, :age
endirb> person = Person.new(name: 'bob', age: '18')
irb> person.name # => "bob"
irb> person.age # => "18"Атрибуты
ActiveModel::Attributes позволяет определять типы данных, устанавливать значения по умолчанию, а также обрабатывать преобразование и сериализацию для обычных объектов Ruby. Это может быть полезно для данных форм, которые будут выполнять преобразование, похожее на Active Record, для таких вещей, как даты и логические значения в обычных объектах.
Для того, чтобы использовать Attributes, включите этот модуль в ваш класс модели и определите свои атрибуты с помощью макроса attribute. Он принимает имя, тип приведения, значение по умолчанию и любые другие опции, поддерживаемые типом атрибута.
class Person
include ActiveModel::Attributes
attribute :name, :string
attribute :date_of_birth, :date
attribute :active, :boolean, default: true
endirb> 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}Назначение Атрибутов
ActiveModel::AttributeAssignment позволяет устанавливать атрибуты объекта путем передачи хэша атрибутов, где ключи соответствуют именам атрибутов. Это удобно, когда вы хотите установить сразу несколько атрибутов.
Рассмотрим следующий класс:
class Person
include ActiveModel::AttributeAssignment
attr_accessor :name, :date_of_birth, :active
endirb> 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 params при назначении атрибута params из запроса.
irb> person = Person.new
# Используется проверка strong parameters, создаем хэш атрибутов, подобный params из запроса
irb> params = ActionController::Parameters.new(name: "John")
=> #<ActionController::Parameters {"name" => "John"} permitted: false>
irb> person.assign_attributes(params)
=> # Вызывает 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 не сможет отличить хэш-аргумент от блока кода.
Методы атрибутов
ActiveModel::AttributeMethods позволяет динамически определять методы для атрибутов модели. Этот модуль особенно полезен для упрощения доступа к атрибутам и их обработки. Он также может добавлять пользовательские префиксы и суффиксы к методам класса. Для определения префиксов, суффиксов и методов, их использующих, выполните следующие действия:
- Включите
ActiveModel::AttributeMethodsв ваш класс. - Вызовите необходимые методы, такие как
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
endirb> 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.
Метод: alias_attribute
ActiveModel::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
endirb> person = Person.new
irb> person.name = "Joe Doe"
irb> person.name
=> "Joe Doe"
# `full_name` - псевдоним для `name`, и возвращает то же самое значение
irb> person.full_name
=> "Joe Doe"
irb> person.name_short?
=> false
# `full_name_short?` псевдоним для `name_short?`, и возвращает то же самое значение
irb> person.full_name_short?
=> falseКолбэки
ActiveModel::Callbacks предоставляет обычным объектам Ruby возможность использовать колбэки в стиле Active Record. Колбэки позволяют вам подключаться к событиям жизненного цикла модели, таким как before_update и after_create, а также определять собственную логику, которая будет выполняться в определенные моменты жизненного цикла модели.
Вы можете реализовать ActiveModel::Callbacks, выполнив следующие действия:
- Расширьте ваш класс с помощью
ActiveModel::Callbacks. - Используйте
define_model_callbacksдля определения списка методов, с которыми должны быть связаны колбэки. Когда вы указываете метод, такой как:update, он автоматически включает все три колбэка по умолчанию (before,aroundиafter) для события:update. - Внутри определенного метода используйте
run_callbacks, который выполнит цепочку колбэков, когда будет вызвано определенное событие. - Затем в своем классе вы можете использовать методы
before_update,after_updateиaround_updateтак же, как вы использовали бы их в модели Active Record.
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 method called"
end
end
private
# Когда на объекте вызван update, этот метод будет вызван колбэком `before_update`
def reset_me
puts "reset_me method: called before the update method"
end
# Когда на объекте вызван update, этот метод будет вызван колбэком `after_update`
def finalize_me
puts "finalize_me method: called after the update method"
end
# Когда на объекте вызван update, этот метод будет вызван колбэком `around_update`
def log_me
puts "log_me method: called around the update method"
yield
puts "log_me method: block successfully called"
end
endКласс, описанный выше, выведет следующее, что указывает на очередность вызова колбэков:
irb> person = Person.new
irb> person.update
reset_me method: called before the update method
log_me method: called around the update method
update method called
log_me method: block successfully called
finalize_me method: called after the update method
=> nilВ соответствии с приведенным выше примером, при определении колбэка 'around' необходимо выполнять yield для блока, иначе он не будет выполнен.
method_name, передаваемый в define_model_callbacks, не должен заканчиваться на !, ? или =. Кроме того, многократное определение одного и того же колбэка перезапишет предыдущие определения колбэков.
Определение конкретных колбэков
Вы можете создавать определенные колбэки, передавая опцию only методу define_model_callbacks:
define_model_callbacks :update, :create, only: [:after, :before]Это создаст только колбэки before_create / after_create и before_update / after_update, пропуская around_*. Опция будет применяться ко всем колбэкам, определенным в данном вызове метода. Можно вызвать 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
endПрерывание колбэков
Цепочку колбэков можно прервать в любой момент времени, выбросив :abort. Это аналогично работе колбэков Active Record.
В приведенном ниже примере, поскольку мы бросаем :abort перед обновлением в методе reset_me, оставшаяся цепочка колбэков, включая 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 method called"
end
end
private
def reset_me
puts "reset_me method: called before the update method"
throw :abort
puts "reset_me method: some code after abort"
end
def finalize_me
puts "finalize_me method: called after the update method"
end
def log_me
puts "log_me method: called around the update method"
yield
puts "log_me method: block successfully called"
end
endirb> person = Person.new
irb> person.update
reset_me method: called before the update method
=> falseПреобразование
ActiveModel::Conversion - это набор методов для преобразования объекта в различные форматы для различных целей. Обычно используется для преобразования объекта в строку или число для построения URL, полей формы и так далее.
Модуль ActiveModel::Conversion добавляет классам следующие методы: to_model, to_key, to_param и to_partial_path.
Возвращаемое значение методов зависит от определения persisted? и наличия id. Метод persisted? должен возвращать true, если объект был сохранен в базе данных или хранилище, в противном случае должен возвращать false. id должен возвращать идентификатор объекта, если он был сохранен, или nil, если не был сохранен.
class Person
include ActiveModel::Conversion
attr_accessor :id
def initialize(id)
@id = id
end
def persisted?
id.present?
end
endto_model
Метод to_model возвращает сам объект.
irb> person = Person.new(1)
irb> person.to_model == person
=> trueЕсли ваша модель не ведёт себя как объект Active Model, вам следует определить метод :to_model самостоятельно. Он должен возвращать прокси-объект, который заворачивает ваш объект и предоставляет методы, совместимые с Active Model.
class Person
def to_model
# A proxy object that wraps your object with Active Model compliant methods.
PersonModel.new(self)
end
endto_key
Метод to_key возвращает массив ключевых атрибутов объекта, если таковые имеются, независимо от того, сохранен ли объект. Если ключевых атрибутов нет, метод возвращает nil.
irb> person.to_key
=> [1]Ключевой атрибут - это атрибут, используемый для идентификации объекта. Например, в модели, поддерживаемой базой данных, ключевым атрибутом является первичный ключ.
to_param
Метод to_param возвращает строковое представление ключа объекта, пригодное для использования в URL, или nil, если метод persisted? возвращает false.
irb> person.to_param
=> "1"to_partial_path
Метод to_partial_path возвращает строку, представляющую путь, связанный с объектом. Action Pack использует этот путь для поиска подходящего партиала для отображения объекта.
irb> person.to_partial_path
=> "people/person"Грязный объект
ActiveModel::Dirty - это полезный инструмент в Ruby on Rails, который позволяет отслеживать изменения, внесенные в атрибуты модели перед их сохранением. Эта функциональность позволяет вам определить, какие атрибуты объекта были изменены, каковы их предыдущие и текущие значения, и выполнять действия на основе этих изменений. Это особенно полезно для аудита, проверки данных и условной логики в вашем приложении. Он позволяет отслеживать изменения в вашем объекте так же, как и в Active Record.
Объект становится грязным после одного или нескольких изменений его атрибутов, и при этом он не был сохранен. У него имеются акцессор-методы на основе атрибутов.
Для использования ActiveModel::Dirty необходимо выполнить следующие шаги:
- Подключите модуль в ваш класс.
- Определите методы атрибутов, изменения которых вы хотите отслеживать, с помощью
define_attribute_methods. - Вызовите
[attr_name]_will_change!перед каждым изменением отслеживаемого атрибута. - Вызовите
changes_appliedпосле сохранения изменений. - Вызовите
clear_changes_informationдля сброса информации об изменениях, когда это необходимо. - Используйте
restore_attributesдля восстановления предыдущих данных объекта.
После этого вы можете использовать методы, предоставляемые ActiveModel::Dirty, чтобы запросить у объекта список всех измененных атрибутов, их исходные значения и внесенные изменения.
Рассмотрим класс Person с атрибутами first_name и last_name и определим, как использовать 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!
# Очищает все грязные данные: текущие изменения и предыдущие изменения.
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? возвращает true если любой из атрибутов имеет несохраненные изменения, в противном случае false.
irb> person.changed?
=> truechanged возвращает массив с именем атрибутов, содержащих несохраненные изменения.
irb> person.changed
=> ["first_name"]changed_attributes возвращает хэш атрибутов с несохраненными изменениями, указывающий их изначальные значения, наподобие attr => original value.
irb> person.changed_attributes
=> {"first_name" => nil}changes возвращает хэш изменений с именами атрибутов в качестве ключей и значениями массивами из оригинального и нового значений, наподобие attr => [original value, new value].
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"[attr_name]_changed? проверяет, был ли некоторый атрибут изменен или нет.
irb> person.first_name_changed?
=> true[attr_name]_was отслеживает предыдущее значение атрибута.
irb> person.first_name_was
=> nil[attr_name]_change отслеживает оба предыдущее и текущее значения измененного атрибута. Возвращает массив с [original value, new value], если изменен, в противном случае nil.
irb> person.first_name_change
=> [nil, "John Doe"]
irb> person.last_name_change
=> nil[attr_name]_previously_changed? проверяет, был ли некоторый атрибут изменен до сохранения модели (то есть до вызова changes_applied).
irb> person.first_name_previously_changed?
=> false
irb> person.save
irb> person.first_name_previously_changed?
=> true[attr_name]_previous_change отслеживает оба предыдущее и текущее значения измененного атрибута до сохранения модели (то есть до вызова changes_applied). Возвращает массив с [original value, new value], если изменен, в противном случае nil.
irb> person.first_name_previous_change
=> [nil, "John Doe"]Именование
ActiveModel::Naming добавляет метод класса и вспомогательные методы для упрощения именования и управления маршрутизацией. Модуль определяет метод класса model_name, который с помощью методов из ActiveSupport::Inflector создает несколько акцессоров.
class Person
extend ActiveModel::Naming
endname возвращает имя модели.
irb> Person.model_name.name
=> "Person"singular возвращает имя в единственном числе записи или класса.
irb> Person.model_name.singular
=> "person"plural возвращает имя во множественном числе записи или класса.
irb> Person.model_name.plural
=> "people"element удаляет пространство имен и возвращает имя в единственном числе snake_cased. Обычно этот метод используется хелперами Action Pack и/или Action View для помощи в отрисовке по имени партиалов/форм.
irb> Person.model_name.element
=> "person"human преобразует название модели в более понятный для человека формат, используя библиотеку I18n. По умолчанию он применяет знак подчеркивания, а затем преобразует его в более читаемый вид.
irb> Person.model_name.human
=> "Person"collection удаляет пространство имен и возвращает имя во множественном числе snake_cased. Обычно этот метод используется хелперами Action Pack и/или Action View для помощи в отрисовке по имени партиалов/форм.
irb> Person.model_name.collection
=> "people"param_key возвращает строку для использования в именах параметров.
irb> Person.model_name.param_key
=> "person"i18n_key возвращает имя ключа i18n. Он применяет знак подчеркивания к имени модели и затем возвращает как символ.
irb> Person.model_name.i18n_key
=> :personroute_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Некоторые методы Naming, такие как param_key, route_key и singular_route_key, ведут себя по-разному для моделей с пространством имен в зависимости от того, находятся ли они внутри изолированного Engine.
Настройка названия модели
Иногда вы можете захотеть изменить название модели, которое используется в хелперах форм и генерации URL. Это может быть полезно в ситуациях, когда вы хотите использовать более понятное для пользователя название модели, при этом сохраняя возможность ссылаться на нее с использованием полного пространства имен.
Например, предположим, в вашем Rails-приложении есть пространство имен Person, и вы хотите создать форму для нового Person::Profile.
По умолчанию Rails сгенерирует форму с URL /person/profiles, который включает пространство имен person. Однако, если вы хотите, чтобы URL просто указывал на profiles без пространства имен, вы можете настроить метод model_name следующим образом:
module Person
class Profile
include ActiveModel::Model
def self.model_name
ActiveModel::Name.new(self, nil, "Profile")
end
end
endПри такой настройке, когда вы используете хелпер form_with для создания формы добавления нового объекта Person::Profile, Rails сгенерирует форму с URL /profiles вместо /person/profiles. Это происходит потому, что метод model_name переопределен для возвращения значения Profile.
Помимо этого, хелперы путей будут генерироваться без пространства имен, поэтому вы сможете использовать profiles_path вместо person_profiles_path для генерации URL к ресурсу profiles. Для использования хелпера profiles_path вам необходимо определить маршруты для модели Person::Profile в файле config/routes.rb следующим образом:
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 предназначен для безопасного хранения паролей в зашифрованном виде. При подключении этого модуля появляется метод класса has_secure_password, который по умолчанию определяет акцессор password с некоторыми встроенными валидациями.
Для работы ActiveModel::SecurePassword необходима библиотека bcrypt, для ее использования добавьте этот гем в ваш Gemfile.
gem "bcrypt"ActiveModel::SecurePassword требует наличия атрибута password_digest.
Он также автоматически добавляет следующие валидации:
- Обязательное наличие пароля при создании.
- Подтверждение пароля (с помощью атрибута
password_confirmation). - Максимальная длина пароля составляет 72 символа (ограничение библиотеки
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
endirb> 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"Сериализация
ActiveModel::Serialization предназначен для базовой сериализации объектов. Вам потребуется определить хэш атрибутов, который будет содержать атрибуты, которые вы хотите сериализовать. Атрибуты должны быть строками, а не символами.
class Person
include ActiveModel::Serialization
attr_accessor :name, :age
def attributes
# Определение сериализуемых атрибутов
{ "name" => nil, "age" => nil }
end
def capitalized_name
# Объявленные методы потом могут быть включены в сериализованный хэш
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}
# Используем опцию methods для включения метода capitalized_name
irb> person.serializable_hash(methods: :capitalized_name)
=> {"name" => "bob", "age" => 22, "capitalized_name" => "Bob"}
# Используем опцию only для включения только атрибута name
irb> person.serializable_hash(only: :name)
=> {"name" => "bob"}
# Используем опцию except для исключения атрибута name
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
endirb> 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 также предоставляет модуль ActiveModel::Serializers::JSON для сериализации / десериализации в формат JSON.
Для использования сериализации в 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, возвращает хэш, представляющий модель, где ключи являются строками. Метод 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"Перевод
ActiveModel::Translation предоставляет интеграцию между вашим объектом и фреймворком интернационализации Rails (i18n).
class Person
extend ActiveModel::Translation
endС помощью метода human_attribute_name можно преобразовывать имена атрибутов в более удобочитаемый формат. Удобочитаемый формат определяется в вашем(-их) файле(-ах) локали.
- config/locales/app.pt-BR.yml
# 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"Валидации
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
endirb> 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вместо добавления к ошибкам, когда проверка не удается. -
validates_with: Передает запись в указанный класс или классы и позволяет им добавлять ошибки на основе более сложных условий. -
validates_each: Проверяет каждый атрибут в блоке.
Некоторые из приведенных ниже опций могут использоваться с определенными валидаторами. Чтобы определить, можно ли использовать опцию с конкретным валидатором, ознакомьтесь с документацией здесь.
-
: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. -
:unless: Указывает метод, proc или строку для вызова, чтобы определить, не должна ли выполняться валидация (например,unless: :skip_validation, илиunless: Proc.new { |user| user.signup_step <= 2 }). Метод, proc или строка должны возвращать или вычисляться в значениеtrueилиfalse. -
:allow_nil: Пропустите валидацию, если атрибутnil. -
:allow_blank: Пропустите валидацию, если атрибут пустой. -
:strict: Если опция:strictустановлена в значение true, она будет вызыватьActiveModel::StrictValidationFailedвместо добавления ошибки. Опция:strictтакже может быть установлена в любое другое исключение.
Многократный вызов validate на одном и том же методе перезапишет предыдущие определения.
Ошибки
ActiveModel::Validations автоматически добавляет метод 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"]Тесты совместимости
ActiveModel::Lint::Tests позволяет проверить, совместим ли объект с Active Model API. Включая ActiveModel::Lint::Tests в ваш TestCase, он будет включать тесты, которые сообщают вам, полностью ли ваш объект соответствует, или, если нет, какие аспекты API не реализованы.
Эти тесты не пытаются определить семантическую правильность возвращаемых значений. Например, вы можете реализовать valid? так, чтобы он всегда возвращал true, и тесты пройдут. Вы должны сами позаботиться о том, чтобы значения имели смысловое значение.
Ожидается, что объекты, которые вы передаете, будут возвращать совместимый объект при вызове to_model. Вполне допустимо, чтобы to_model возвращал self.
-
app/models/person.rbclass Person include ActiveModel::API end -
test/models/person_test.rbrequire "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