NO LEA ESTE ARCHIVO EN GITHUB, LAS GUÍAS ESTÁN PUBLICADAS EN https://guides.rubyonrails.org.

Conceptos Básicos de Active Model

Esta guía le proporcionará lo que necesita para comenzar a usar Active Model. Active Model proporciona una forma para que los ayudantes de Action Pack y Action View interactúen con objetos Ruby simples. También ayuda a construir ORM personalizados para su uso fuera del marco de Rails.

Después de leer esta guía, sabrá:


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:

  1. Incluya ActiveModel::AttributeMethods en su clase.
  2. Llame a cada uno de los métodos que desea agregar, como attribute_method_suffix, attribute_method_prefix, attribute_method_affix.
  3. 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.
  4. Defina los diversos métodos genéricos _attribute que ha declarado. El parámetro attribute en estos métodos será reemplazado por el argumento pasado en define_attribute_methods. En el ejemplo a continuación es name.

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:

  1. Extienda ActiveModel::Callbacks dentro de su clase.
  2. 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 y after) para el evento :update.
  3. Dentro del método definido, utilice run_callbacks, que ejecutará la cadena de callbacks cuando se active el evento específico.
  4. En su clase, puede utilizar los métodos before_update, after_update y around_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:

  1. Incluir el módulo en su clase.
  2. Definir los métodos de atributo para los que desea rastrear cambios, usando define_attribute_methods.
  3. Llamar a [attr_name]_will_change! antes de cada cambio en el atributo rastreado.
  4. Llamar a changes_applied después de que los cambios se hayan persistido.
  5. Llamar a clear_changes_information cuando desee restablecer la información de cambios.
  6. 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:

  1. La contraseña debe estar presente en la creación.
  2. Confirmación de la contraseña (usando un atributo password_confirmation).
  3. 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étodo validates y proporciona un acceso directo a todos los validadores predeterminados.

  • validates! o establecer strict: 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 o on: :custom_validation_context o on: [: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 de valid?(:context).

  • :if: Especifica un método, proc o cadena para llamar para determinar si la validación debe ocurrir (por ejemplo, if: :allow_validation, o if: -> { signup_step > 2 }). El método, proc o cadena debe devolver o evaluar a un valor true o false.

  • :unless: Especifica un método, proc o cadena para llamar para determinar si la validación no debe ocurrir (por ejemplo, unless: :skip_validation, o unless: Proc.new { |user| user.signup_step <= 2 }). El método, proc o cadena debe devolver o evaluar a un valor true o false.

  • :allow_nil: Omite la validación si el atributo es nil.

  • :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.