1 ¿Qué es Active Model?
Para entender Active Model, necesita saber un poco sobre Active Record. Active Record es un ORM (Mapeador de Objetos Relacional) que conecta objetos cuyos datos requieren almacenamiento persistente a una base de datos relacional. Sin embargo, tiene funcionalidades que son útiles fuera del ORM, algunas de estas incluyen validaciones, callbacks, traducciones, la capacidad de crear atributos personalizados, etc.
Parte de esta funcionalidad fue abstraída de Active Record para formar Active Model. Active Model es una biblioteca que contiene varios módulos que se pueden usar en objetos Ruby simples que requieren características similares a los modelos pero no están vinculados a ninguna tabla en una base de datos.
En resumen, mientras que Active Record proporciona una interfaz para definir modelos que corresponden a tablas de bases de datos, Active Model proporciona funcionalidad para construir clases Ruby similares a modelos que no necesariamente necesitan estar respaldadas por una base de datos. Active Model se puede usar independientemente de Active Record.
Algunos de estos módulos se explican a continuación.
1.1 API
ActiveModel::API
agrega la capacidad para que una clase funcione con Action Pack y Action View directamente.
Al incluir ActiveModel::API
, se incluyen otros módulos por defecto que le permiten obtener características como:
Aquí hay un ejemplo de una clase que incluye ActiveModel::API
y cómo se puede usar:
class EmailContact
include ActiveModel::API
attr_accessor :name, :email, :message
validates :name, :email, :message, presence: true
def deliver
if valid?
# Entregar correo electrónico
end
end
end
irb> email_contact = EmailContact.new(name: "David", email: "david@example.com", message: "Hello World")
irb> email_contact.name # Asignación de Atributos
=> "David"
irb> email_contact.to_model == email_contact # Conversión
=> true
irb> email_contact.model_name.name # Nombramiento
=> "EmailContact"
irb> EmailContact.human_attribute_name("name") # Traducción si la configuración regional está establecida
=> "Name"
irb> email_contact.valid? # Validaciones
=> true
irb> empty_contact = EmailContact.new
irb> empty_contact.valid?
=> false
Cualquier clase que incluya ActiveModel::API
se puede usar con form_with
, render
y cualquier otro método de ayuda de Action View, al igual que los objetos de Active Record.
Por ejemplo, form_with
se puede usar para crear un formulario para un objeto EmailContact
de la siguiente manera:
<%= form_with model: EmailContact.new do |form| %>
<%= form.text_field :name %>
<% end %>
lo que resulta en el siguiente HTML:
<form action="/email_contacts" method="post">
<input type="text" name="email_contact[name]" id="email_contact_name">
</form>
render
se puede usar para renderizar un parcial con el objeto:
<%= render @email_contact %>
NOTA: Puede aprender más sobre cómo usar form_with
y render
con objetos compatibles con ActiveModel::API
en las guías Ayudantes de Formularios de Action View y Layouts y Renderizado, respectivamente.
1.2 Model
ActiveModel::Model
incluye ActiveModel::API para interactuar con Action Pack y Action View por defecto, y es el enfoque recomendado para implementar clases Ruby similares a modelos. Se extenderá en el futuro para agregar más funcionalidad.
class Person
include ActiveModel::Model
attr_accessor :name, :age
end
irb> person = Person.new(name: 'bob', age: '18')
irb> person.name # => "bob"
irb> person.age # => "18"
1.3 Attributes
ActiveModel::Attributes
le permite definir tipos de datos, establecer valores predeterminados y manejar la conversión y serialización en objetos Ruby simples. Esto puede ser útil para datos de formularios que producirán conversiones similares a Active Record para cosas como fechas y booleanos en objetos regulares.
Para usar Attributes
, incluya el módulo en su clase de modelo y defina sus atributos usando la macro attribute
. Acepta un nombre, un tipo de conversión, un valor predeterminado y cualquier otra opción compatible con el tipo de atributo.
class Person
include ActiveModel::Attributes
attribute :name, :string
attribute :date_of_birth, :date
attribute :active, :boolean, default: true
end
irb> person = Person.new
irb> person.name = "Jane"
irb> person.name
=> "Jane"
# Convierte la cadena a una fecha establecida por el atributo
irb> person.date_of_birth = "2020-01-01"
irb> person.date_of_birth
=> Wed, 01 Jan 2020
irb> person.date_of_birth.class
=> Date
# Usa el valor predeterminado establecido por el atributo
irb> person.active
=> true
# Convierte el entero a un booleano establecido por el atributo
irb> person.active = 0
irb> person.active
=> false
Algunos métodos adicionales descritos a continuación están disponibles cuando se utiliza ActiveModel::Attributes
.
1.3.1 Método: attribute_names
El método attribute_names
devuelve un array de nombres de atributos.
irb> Person.attribute_names
=> ["name", "date_of_birth", "active"]
1.3.2 Método: attributes
El método attributes
devuelve un hash de todos los atributos con sus nombres como claves y los valores de los atributos como valores.
irb> person.attributes
=> {"name" => "Jane", "date_of_birth" => Wed, 01 Jan 2020, "active" => false}
1.4 Asignación de Atributos
ActiveModel::AttributeAssignment
le permite establecer los atributos de un objeto pasando un hash de atributos con claves que coinciden con los nombres de los atributos. Esto es útil cuando desea establecer múltiples atributos a la vez.
Considere la siguiente clase:
class Person
include ActiveModel::AttributeAssignment
attr_accessor :name, :date_of_birth, :active
end
irb> person = Person.new
# Establecer múltiples atributos a la vez
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
Si el hash pasado responde al método permitted?
y el valor de retorno de este método es false
, se lanza una excepción ActiveModel::ForbiddenAttributesError
.
NOTA: permitted?
se utiliza para la integración de parámetros fuertes en la que está asignando un atributo de parámetros de una solicitud.
irb> person = Person.new
# Usando comprobaciones de parámetros fuertes, construir un hash de atributos similar a los parámetros de una solicitud
irb> params = ActionController::Parameters.new(name: "John")
=> #<ActionController::Parameters {"name" => "John"} permitted: false>
irb> person.assign_attributes(params)
=> # Raises ActiveModel::ForbiddenAttributesError
irb> person.name
=> nil
# Permitir los atributos que queremos permitir la asignación
irb> permitted_params = params.permit(:name)
=> #<ActionController::Parameters {"name" => "John"} permitted: true>
irb> person.assign_attributes(permitted_params)
irb> person.name
=> "John"
1.4.1 Alias del Método: attributes=
El método assign_attributes
tiene un alias attributes=
.
Un alias de método es un método que realiza la misma acción que otro método, pero se llama de manera diferente. Los alias existen por el bien de la legibilidad y la conveniencia.
El siguiente ejemplo demuestra el uso del método attributes=
para establecer múltiples atributos a la vez:
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
y attributes=
son ambas llamadas de método y aceptan el hash de atributos para asignar como argumento. En muchos casos, Ruby permite que se omitan los paréntesis ()
de las llamadas a métodos y las llaves {}
de las definiciones de hash.
Los métodos "setter" como attributes=
se escriben comúnmente sin ()
, aunque incluirlos funciona de la misma manera y requieren que el hash siempre incluya {}
. person.attributes=({ name: "John" })
está bien, pero person.attributes = name: "John"
resulta en un SyntaxError
.
Otras llamadas a métodos como assign_attributes
pueden o no contener tanto paréntesis ()
como {}
para el argumento hash. Por ejemplo, assign_attributes name: "John"
y assign_attributes({ name: "John" })
son ambos códigos Ruby perfectamente válidos, sin embargo, assign_attributes { name: "John" }
no lo es, porque Ruby no puede diferenciar ese argumento hash de un bloque y lanzará un SyntaxError
.
1.5 Métodos de Atributo
ActiveModel::AttributeMethods
proporciona una forma de definir métodos dinámicamente para los atributos de un modelo. Este módulo es particularmente útil para simplificar el acceso y manipulación de atributos, y puede agregar prefijos y sufijos personalizados a los métodos de una clase. Puede definir los prefijos y sufijos y qué métodos en el objeto los usarán de la siguiente manera:
- Incluya
ActiveModel::AttributeMethods
en su clase. - Llame a cada uno de los métodos que desea agregar, como
attribute_method_suffix
,attribute_method_prefix
,attribute_method_affix
. - Llame a
define_attribute_methods
después de los otros métodos para declarar el(los) atributo(s) que deberían tener prefijos y sufijos. - Defina los diversos métodos genéricos
_attribute
que ha declarado. El parámetroattribute
en estos métodos será reemplazado por el argumento pasado endefine_attribute_methods
. En el ejemplo a continuación esname
.
NOTA: attribute_method_prefix
y attribute_method_suffix
se utilizan para definir los prefijos y sufijos que se usarán para crear los métodos. attribute_method_affix
se utiliza para definir tanto el prefijo como el sufijo al mismo tiempo.
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
# Llamada al método de atributo para 'first_name'
def first_attribute(attribute)
public_send(attribute).split.first
end
# Llamada al método de atributo para 'last_name'
def last_attribute(attribute)
public_send(attribute).split.last
end
# Llamada al método de atributo para 'name_short?'
def attribute_short?(attribute)
public_send(attribute).length < 5
end
# Llamada al método de atributo 'reset_name_to_default!'
def reset_attribute_to_default!(attribute)
public_send("#{attribute}=", "Default Name")
end
end
irb> 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"
Si llama a un método que no está definido, lanzará un error NoMethodError
.
1.5.1 Método: alias_attribute
ActiveModel::AttributeMethods
proporciona alias de métodos de atributo usando alias_attribute
.
El ejemplo a continuación crea un atributo alias para name
llamado full_name
. Devuelven el mismo valor, pero el alias full_name
refleja mejor que el atributo incluye un nombre y un apellido.
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
end
irb> person = Person.new
irb> person.name = "Joe Doe"
irb> person.name
=> "Joe Doe"
# `full_name` es el alias de `name` y devuelve el mismo valor
irb> person.full_name
=> "Joe Doe"
irb> person.name_short?
=> false
# `full_name_short?` es el alias de `name_short?` y devuelve el mismo valor
irb> person.full_name_short?
=> false
1.6 Callbacks
ActiveModel::Callbacks
proporciona a los objetos Ruby simples callbacks al estilo de Active Record. Los callbacks le permiten engancharse en eventos del ciclo de vida del modelo, como before_update
y after_create
, así como definir lógica personalizada para ejecutarse en puntos específicos en el ciclo de vida del modelo.
Puede implementar ActiveModel::Callbacks
siguiendo los pasos a continuación:
- Extienda
ActiveModel::Callbacks
dentro de su clase. - Emplee
define_model_callbacks
para establecer una lista de métodos que deberían tener callbacks asociados. Cuando designe un método como:update
, incluirá automáticamente los tres callbacks predeterminados (before
,around
yafter
) para el evento:update
. - Dentro del método definido, utilice
run_callbacks
, que ejecutará la cadena de callbacks cuando se active el evento específico. - En su clase, puede utilizar los métodos
before_update
,after_update
yaround_update
de la misma manera que los usaría en un modelo de Active Record.
class Person
extend ActiveModel::Callbacks
define_model_callbacks :update
before_update :reset_me
after_update :finalize_me
around_update :log_me
# Método `define_model_callbacks` que contiene `run_callbacks` que ejecuta el callback(s) para el evento dado
def update
run_callbacks(:update) do
puts "update method called"
end
end
private
# Cuando se llama a update en un objeto, este método es llamado por el callback `before_update`
def reset_me
puts "reset_me method: called before the update method"
end
# Cuando se llama a update en un objeto, este método es llamado por el callback `after_update`
def finalize_me
puts "finalize_me method: called after the update method"
end
# Cuando se llama a update en un objeto, este método es llamado por el callback `around_update`
def log_me
puts "log_me method: called around the update method"
yield
puts "log_me method: block successfully called"
end
end
La clase anterior producirá lo siguiente, lo que indica el orden en que se están llamando los callbacks:
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
Según el ejemplo anterior, al definir un callback 'around', recuerde yield
al bloque, de lo contrario, no se ejecutará.
NOTA: method_name
pasado a define_model_callbacks
no debe terminar con !
, ?
o =
. Además, definir el mismo callback varias veces sobrescribirá las definiciones de callbacks anteriores.
1.6.1 Definición de Callbacks Específicos
Puede optar por crear callbacks específicos pasando la opción only
al método define_model_callbacks
:
define_model_callbacks :update, :create, only: [:after, :before]
Esto creará solo los callbacks before_create
/ after_create
y before_update
/ after_update
, pero omitirá los around_*
. La opción se aplicará a todos los callbacks definidos en esa llamada de método. Es posible llamar a define_model_callbacks
varias veces para especificar diferentes eventos del ciclo de vida:
define_model_callbacks :create, only: :after
define_model_callbacks :update, only: :before
define_model_callbacks :destroy, only: :around
Esto creará solo los métodos after_create
, before_update
y around_destroy
.
1.6.2 Definición de Callbacks con una Clase
Puede pasar una clase a before_<type>
, after_<type>
y around_<type>
para tener más control sobre cuándo y en qué contexto se activan sus callbacks. El callback activará el método <action>_<type>
de esa clase, pasando una instancia de la clase como argumento.
class Person
extend ActiveModel::Callbacks
define_model_callbacks :create
before_create PersonCallbacks
end
class PersonCallbacks
def self.before_create(obj)
# `obj` es la instancia de Person en la que se está llamando el callback
end
end
1.6.3 Abortando Callbacks
La cadena de callbacks se puede abortar en cualquier momento lanzando :abort
. Esto es similar a cómo funcionan los callbacks de Active Record.
En el ejemplo a continuación, dado que lanzamos :abort
antes de una actualización en el método reset_me
, la cadena de callbacks restante, incluido before_update
, se abortará, y el cuerpo del método update
no se ejecutará.
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
end
irb> person = Person.new
irb> person.update
reset_me method: called before the update method
=> false
1.7 Conversión
ActiveModel::Conversion
es una colección de métodos que le permiten convertir su objeto a diferentes formas para diferentes propósitos. Un caso de uso común es convertir su objeto a una cadena o un entero para construir URLs, campos de formulario y más.
El módulo ActiveModel::Conversion
agrega los siguientes métodos: to_model
, to_key
, to_param
y to_partial_path
a las clases.
Los valores de retorno de los métodos dependen de si persisted?
está definido y si se proporciona un id
. El método persisted?
debe devolver true
si el objeto ha sido guardado en la base de datos o almacén, de lo contrario, debe devolver false
. El id
debe hacer referencia al id del objeto o nil si el objeto no está guardado.
class Person
include ActiveModel::Conversion
attr_accessor :id
def initialize(id)
@id = id
end
def persisted?
id.present?
end
end
1.7.1 to_model
El método to_model
devuelve el propio objeto.
irb> person = Person.new(1)
irb> person.to_model == person
=> true
Si su modelo no actúa como un objeto Active Model, entonces debe definir :to_model
usted mismo devolviendo un objeto proxy que envuelva su objeto con métodos compatibles con Active Model.
class Person
def to_model
# Un objeto proxy que envuelve su objeto con métodos compatibles con Active Model.
PersonModel.new(self)
end
end
1.7.2 to_key
El método to_key
devuelve un array de los atributos clave del objeto si alguno de los atributos está configurado, independientemente de si el objeto está persistido. Devuelve nil si no hay atributos clave.
irb> person.to_key
=> [1]
NOTA: Un atributo clave es un atributo que se utiliza para identificar el objeto. Por ejemplo, en un modelo respaldado por una base de datos, el atributo clave es la clave primaria.
1.7.3 to_param
El método to_param
devuelve una representación string
de la clave del objeto adecuada para su uso en URLs, o nil
en el caso de que persisted?
sea false
.
irb> person.to_param
=> "1"
1.7.4 to_partial_path
El método to_partial_path
devuelve una string
que representa la ruta asociada con el objeto. Action Pack utiliza esto para encontrar un parcial adecuado para representar el objeto.
irb> person.to_partial_path
=> "people/person"
1.8 Dirty
ActiveModel::Dirty
es útil para rastrear cambios realizados en los atributos del modelo antes de que se guarden. Esta funcionalidad le permite determinar qué atributos han sido modificados, cuáles eran sus valores anteriores y actuales, y realizar acciones basadas en esos cambios. Es particularmente útil para auditoría, validación y lógica condicional dentro de su aplicación. Proporciona una forma de rastrear cambios en su objeto de la misma manera que Active Record.
Un objeto se vuelve "sucio" cuando ha pasado por uno o más cambios en sus atributos y no ha sido guardado. Tiene métodos de acceso basados en atributos.
Para usar ActiveModel::Dirty
, necesita:
- Incluir el módulo en su clase.
- Definir los métodos de atributo para los que desea rastrear cambios, usando
define_attribute_methods
. - Llamar a
[attr_name]_will_change!
antes de cada cambio en el atributo rastreado. - Llamar a
changes_applied
después de que los cambios se hayan persistido. - Llamar a
clear_changes_information
cuando desee restablecer la información de cambios. - Llamar a
restore_attributes
cuando desee restaurar los datos anteriores.
Luego puede usar los métodos proporcionados por ActiveModel::Dirty
para consultar el objeto por su lista de todos los atributos cambiados, los valores originales de los atributos cambiados y los cambios realizados en los atributos.
Consideremos una clase Person
con los atributos first_name
y last_name
y determinemos cómo podemos usar ActiveModel::Dirty
para rastrear cambios en estos atributos.
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
# Persistir datos - borra datos sucios y mueve `changes` a `previous_changes`.
changes_applied
end
def reload!
# Borra todos los datos sucios: cambios actuales y cambios anteriores.
clear_changes_information
end
def rollback!
# Restaura todos los datos anteriores de los atributos proporcionados.
restore_attributes
end
end
1.8.1 Consultar un Objeto Directamente por su Lista de Todos los Atributos Cambiados
irb> person = Person.new
# Un objeto `Person` recién instanciado no ha cambiado:
irb> person.changed?
=> false
irb> person.first_name = "Jane Doe"
irb> person.first_name
=> "Jane Doe"
changed?
devuelve true
si alguno de los atributos tiene cambios no guardados, false
de lo contrario.
irb> person.changed?
=> true
changed
devuelve un array con el nombre de los atributos que contienen cambios no guardados.
irb> person.changed
=> ["first_name"]
changed_attributes
devuelve un hash de los atributos con cambios no guardados indicando sus valores originales como attr => valor original
.
irb> person.changed_attributes
=> {"first_name" => nil}
changes
devuelve un hash de cambios, con los nombres de los atributos como las claves, y los valores como un array de los valores originales y nuevos como attr => [valor original, valor nuevo]
.
irb> person.changes
=> {"first_name" => [nil, "Jane Doe"]}
previous_changes
devuelve un hash de atributos que fueron cambiados antes de que el modelo fuera guardado (es decir, antes de que se llame a changes_applied
).
irb> person.previous_changes
=> {}
irb> person.save
irb> person.previous_changes
=> {"first_name" => [nil, "Jane Doe"]}
1.8.2 Métodos de Acceso Basados en Atributos
irb> person = Person.new
irb> person.changed?
=> false
irb> person.first_name = "John Doe"
irb> person.first_name
=> "John Doe"
[attr_name]_changed?
comprueba si el atributo en particular ha sido cambiado o no.
irb> person.first_name_changed?
=> true
[attr_name]_was
rastrea el valor anterior del atributo.
irb> person.first_name_was
=> nil
[attr_name]_change
rastrea tanto los valores anteriores como actuales del atributo cambiado. Devuelve un array con [valor original, valor nuevo]
si ha cambiado, de lo contrario devuelve nil
.
irb> person.first_name_change
=> [nil, "John Doe"]
irb> person.last_name_change
=> nil
[attr_name]_previously_changed?
comprueba si el atributo en particular ha sido cambiado antes de que el modelo fuera guardado (es decir, antes de que se llame a changes_applied
).
irb> person.first_name_previously_changed?
=> false
irb> person.save
irb> person.first_name_previously_changed?
=> true
[attr_name]_previous_change
rastrea tanto los valores anteriores como actuales del atributo cambiado antes de que el modelo fuera guardado (es decir, antes de que se llame a changes_applied
). Devuelve un array con [valor original, valor nuevo]
si ha cambiado, de lo contrario devuelve nil
.
irb> person.first_name_previous_change
=> [nil, "John Doe"]
1.9 Nombramiento
ActiveModel::Naming
agrega un método de clase y métodos de ayuda para facilitar la gestión de nombres y enrutamiento. El módulo define el método de clase model_name
que definirá varios accesores utilizando algunos métodos de ActiveSupport::Inflector
.
class Person
extend ActiveModel::Naming
end
name
devuelve el nombre del modelo.
irb> Person.model_name.name
=> "Person"
singular
devuelve el nombre de clase singular de un registro o clase.
irb> Person.model_name.singular
=> "person"
plural
devuelve el nombre de clase plural de un registro o clase.
irb> Person.model_name.plural
=> "people"
element
elimina el espacio de nombres y devuelve el nombre en snake_case singular. Generalmente es utilizado por Action Pack y/o ayudantes de Action View para ayudar en la representación del nombre de parciales/formularios.
irb> Person.model_name.element
=> "person"
human
transforma el nombre del modelo en un formato más humano, utilizando I18n. Por defecto, subrayará y luego humanizará el nombre de la clase.
irb> Person.model_name.human
=> "Person"
collection
elimina el espacio de nombres y devuelve el nombre en snake_case plural. Generalmente es utilizado por Action Pack y/o ayudantes de Action View para ayudar en la representación del nombre de parciales/formularios.
irb> Person.model_name.collection
=> "people"
param_key
devuelve una cadena para usar en nombres de parámetros.
irb> Person.model_name.param_key
=> "person"
i18n_key
devuelve el nombre de la clave i18n. Subraya el nombre del modelo y luego lo devuelve como un símbolo.
irb> Person.model_name.i18n_key
=> :person
route_key
devuelve una cadena para usar al generar nombres de rutas.
irb> Person.model_name.route_key
=> "people"
singular_route_key
devuelve una cadena para usar al generar nombres de rutas.
irb> Person.model_name.singular_route_key
=> "person"
uncountable?
identifica si el nombre de clase de un registro o clase es incontable.
irb> Person.model_name.uncountable?
=> false
NOTA: Algunos métodos de Naming
, como param_key
, route_key
y singular_route_key
, difieren para modelos con espacio de nombres según si están dentro de un motor aislado.
1.9.1 Personalizar el Nombre del Modelo
A veces puede querer personalizar el nombre del modelo que se utiliza en los ayudantes de formularios y generación de URL. Esto puede ser útil en situaciones donde desea usar un nombre más amigable para el modelo, mientras que aún puede referenciarlo usando su espacio de nombres completo.
Por ejemplo, digamos que tiene un espacio de nombres Person
en su aplicación Rails y desea crear un formulario para un nuevo Person::Profile
.
Por defecto, Rails generaría el formulario con la URL /person/profiles
, que incluye el espacio de nombres person
. Sin embargo, si desea que la URL simplemente apunte a profiles
sin el espacio de nombres, puede personalizar el método model_name
de esta manera:
module Person
class Profile
include ActiveModel::Model
def self.model_name
ActiveModel::Name.new(self, nil, "Profile")
end
end
end
Con esta configuración, cuando use el ayudante form_with
para crear un formulario para crear un nuevo Person::Profile
, Rails generará el formulario con la URL /profiles
en lugar de /person/profiles
, porque el método model_name
ha sido sobrescrito para devolver Profile
.
Además, los ayudantes de ruta se generarán sin el espacio de nombres, por lo que puede usar profiles_path
en lugar de person_profiles_path
para generar la URL para el recurso profiles
. Para usar el ayudante profiles_path
, necesita definir las rutas para el modelo Person::Profile
en su archivo config/routes.rb
de esta manera:
Rails.application.routes.draw do
resources :profiles
end
Consecuentemente, puede esperar que el modelo devuelva los siguientes valores para los métodos que se describieron en la sección anterior:
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"
1.10 SecurePassword
ActiveModel::SecurePassword
proporciona una forma de almacenar de forma segura cualquier contraseña en un formulario cifrado. Cuando incluye este módulo, se proporciona un método de clase has_secure_password
que define un accesor password
con ciertas validaciones por defecto.
ActiveModel::SecurePassword
depende de bcrypt
, por lo que incluya este gem en su Gemfile
para usarlo.
gem "bcrypt"
ActiveModel::SecurePassword
requiere que tenga un atributo password_digest
.
Las siguientes validaciones se agregan automáticamente:
- La contraseña debe estar presente en la creación.
- Confirmación de la contraseña (usando un atributo
password_confirmation
). - La longitud máxima de una contraseña es de 72 bytes (requerido ya que
bcrypt
trunca la cadena a este tamaño antes de cifrarla).
NOTA: Si no se necesita la validación de confirmación de contraseña, simplemente deje el valor para password_confirmation
(es decir, no proporcione un campo de formulario para ello). Cuando este atributo tiene un valor nil
, la validación no se activará.
Para una mayor personalización, es posible suprimir las validaciones predeterminadas pasando validations: false
como argumento.
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
# Cuando la contraseña está en blanco.
irb> person.valid?
=> false
# Cuando la confirmación no coincide con la contraseña.
irb> person.password = "aditya"
irb> person.password_confirmation = "nomatch"
irb> person.valid?
=> false
# Cuando la longitud de la contraseña excede 72.
irb> person.password = person.password_confirmation = "a" * 100
irb> person.valid?
=> false
# Cuando solo se proporciona la contraseña sin password_confirmation.
irb> person.password = "aditya"
irb> person.valid?
=> true
# Cuando se pasan todas las validaciones.
irb> person.password = person.password_confirmation = "aditya"
irb> person.valid?
=> true
irb> person.recovery_password = "42password"
# `authenticate` es un alias para `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"
1.11 Serialización
ActiveModel::Serialization
proporciona serialización básica para su objeto. Necesita declarar un hash de atributos que contenga los atributos que desea serializar. Los atributos deben ser cadenas, no símbolos.
class Person
include ActiveModel::Serialization
attr_accessor :name, :age
def attributes
# Declaración de atributos que se serializarán
{ "name" => nil, "age" => nil }
end
def capitalized_name
# Los métodos declarados se pueden incluir posteriormente en el hash serializado
name&.capitalize
end
end
Ahora puede acceder a un hash serializado de su objeto utilizando el método serializable_hash
. Las opciones válidas para serializable_hash
incluyen :only
, :except
, :methods
y :include
.
irb> person = Person.new
irb> person.serializable_hash
=> {"name" => nil, "age" => nil}
# Establecer los atributos name y age y serializar el objeto
irb> person.name = "bob"
irb> person.age = 22
irb> person.serializable_hash
=> {"name" => "bob", "age" => 22}
# Usar la opción methods para incluir el método capitalized_name
irb> person.serializable_hash(methods: :capitalized_name)
=> {"name" => "bob", "age" => 22, "capitalized_name" => "Bob"}
# Usar el método only para incluir solo el atributo name
irb> person.serializable_hash(only: :name)
=> {"name" => "bob"}
# Usar el método except para excluir el atributo name
irb> person.serializable_hash(except: :name)
=> {"age" => 22}
El ejemplo para utilizar la opción includes
requiere un escenario ligeramente más complejo como se define a continuación:
class Person
include ActiveModel::Serialization
attr_accessor :name, :notes # Emular 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
end
irb> 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"}]}
1.11.1 ActiveModel::Serializers::JSON
Active Model también proporciona el módulo ActiveModel::Serializers::JSON
para serialización/deserialización JSON.
Para usar la serialización JSON, cambie el módulo que está incluyendo de ActiveModel::Serialization
a ActiveModel::Serializers::JSON
. Ya incluye el primero, por lo que no hay necesidad de incluirlo explícitamente.
class Person
include ActiveModel::Serializers::JSON
attr_accessor :name
def attributes
{ "name" => nil }
end
end
El método as_json
, similar a serializable_hash
, proporciona un hash que representa el modelo con sus claves como una cadena. El método to_json
devuelve una cadena JSON que representa el modelo.
irb> person = Person.new
# Un hash que representa el modelo con sus claves como una cadena
irb> person.as_json
=> {"name" => nil}
# Una cadena JSON que representa el modelo
irb> person.to_json
=> "{\"name\":null}"
irb> person.name = "Bob"
irb> person.as_json
=> {"name" => "Bob"}
irb> person.to_json
=> "{\"name\":\"Bob\"}"
También puede definir los atributos para un modelo a partir de una cadena JSON. Para hacer esto, primero defina el método attributes=
en su clase:
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
Ahora es posible crear una instancia de Person
y establecer atributos usando 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"
1.12 Traducción
ActiveModel::Translation
proporciona integración entre su objeto y el marco de internacionalización (i18n) de Rails.
class Person
extend ActiveModel::Translation
end
Con el método human_attribute_name
, puede transformar nombres de atributos en un formato más legible para humanos. El formato legible para humanos se define en su(s) archivo(s) de configuración regional.
# 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"
1.13 Validaciones
ActiveModel::Validations
agrega la capacidad de validar objetos y es importante para garantizar la integridad y consistencia de los datos dentro de su aplicación. Al incorporar validaciones en sus modelos, puede definir reglas que gobiernan la corrección de los valores de los atributos y prevenir datos no válidos.
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
end
irb> 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` usa validate! y lanzará una excepción cuando no esté establecido.
irb> person.token = nil
irb> person.valid?
=> "Token can't be blank (ActiveModel::StrictValidationFailed)"
1.13.1 Métodos y Opciones de Validación
Puede agregar validaciones usando algunos de los siguientes métodos:
validate
: Agrega validación a través de un método o un bloque a la clase.validates
: Un atributo se puede pasar al métodovalidates
y proporciona un acceso directo a todos los validadores predeterminados.validates!
o establecerstrict: true
: Se utiliza para definir validaciones que no pueden ser corregidas por los usuarios finales y se consideran excepcionales. Cada validador definido con un signo de exclamación o la opción:strict
establecida en true siempre lanzaráActiveModel::StrictValidationFailed
en lugar de agregar a los errores cuando la validación falla.validates_with
: Pasa el registro a la clase o clases especificadas y les permite agregar errores basados en condiciones más complejas.validates_each
: Valida cada atributo contra un bloque.
Algunas de las opciones a continuación se pueden usar con ciertos validadores. Para determinar si la opción que está utilizando se puede usar con un validador específico, lea la documentación de validación.
:on
: Especifica el contexto en el que agregar la validación. Puede pasar un símbolo o un array de símbolos. (por ejemplo,on: :create
oon: :custom_validation_context
oon: [:create, :custom_validation_context]
). Las validaciones sin una opción:on
se ejecutarán sin importar el contexto. Las validaciones con alguna opción:on
solo se ejecutarán en el contexto especificado. Puede pasar el contexto al validar a través devalid?(:context)
.:if
: Especifica un método, proc o cadena para llamar para determinar si la validación debe ocurrir (por ejemplo,if: :allow_validation
, oif: -> { signup_step > 2 }
). El método, proc o cadena debe devolver o evaluar a un valortrue
ofalse
.:unless
: Especifica un método, proc o cadena para llamar para determinar si la validación no debe ocurrir (por ejemplo,unless: :skip_validation
, ounless: Proc.new { |user| user.signup_step <= 2 }
). El método, proc o cadena debe devolver o evaluar a un valortrue
ofalse
.:allow_nil
: Omite la validación si el atributo esnil
.:allow_blank
: Omite la validación si el atributo está en blanco.:strict
: Si la opción:strict
está establecida en true, lanzaráActiveModel::StrictValidationFailed
en lugar de agregar el error. La opción:strict
también se puede establecer en cualquier otra excepción.
NOTA: Llamar a validate
varias veces en el mismo método sobrescribirá las definiciones anteriores.
1.13.2 Errores
ActiveModel::Validations
agrega automáticamente un método errors
a sus instancias inicializadas con un nuevo objeto ActiveModel::Errors
, por lo que no hay necesidad de hacerlo manualmente.
Ejecute valid?
en el objeto para verificar si el objeto es válido o no. Si el objeto no es válido, devolverá false
y los errores se agregarán al objeto 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"]
1.14 Pruebas de Lint
ActiveModel::Lint::Tests
le permite probar si un objeto cumple con la API de Active Model. Al incluir ActiveModel::Lint::Tests
en su TestCase, incluirá pruebas que le dirán si su objeto es completamente compatible, o si no lo es, qué aspectos de la API no están implementados.
Estas pruebas no intentan determinar la corrección semántica de los valores devueltos. Por ejemplo, podría implementar valid?
para que siempre devuelva true
, y las pruebas pasarían. Depende de usted asegurarse de que los valores sean semánticamente significativos.
Se espera que los objetos que pase devuelvan un objeto compatible de una llamada a to_model
. Está perfectamente bien que to_model
devuelva self
.
app/models/person.rb
class Person include ActiveModel::API end
test/models/person_test.rb
require "test_helper" class PersonTest < ActiveSupport::TestCase include ActiveModel::Lint::Tests setup do @model = Person.new end end
Consulte la documentación de métodos de prueba para obtener más detalles.
Para ejecutar las pruebas, puede usar el siguiente comando:
$ 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
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor contribuye si ves algún error tipográfico o errores fácticos. Para comenzar, puedes leer nuestra sección de contribuciones a la documentación.
También puedes encontrar contenido incompleto o cosas que no están actualizadas. Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar Guías Edge primero para verificar si los problemas ya están resueltos o no en la rama principal. Revisa las Guías de Ruby on Rails para estilo y convenciones.
Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor abre un issue.
Y por último, pero no menos importante, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.