edge
Más en rubyonrails.org: Más Ruby on Rails

Fundamentos de Active Model

Esta guía te proporcionará todo lo que necesitas para comenzar a usar las clases de modelo. Active Model permite que los ayudantes de Action Pack interactúen con objetos Ruby simples. Active Model también ayuda a construir ORMs personalizados para su uso fuera del framework Rails.

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

1 ¿Qué es Active Model?

Active Model es una biblioteca que contiene varios módulos utilizados en el desarrollo de clases que necesitan algunas características presentes en Active Record. Algunos de estos módulos se explican a continuación.

1.1 API

ActiveModel::API agrega la capacidad de que una clase funcione con Action Pack y Action View directamente.

class EmailContact
  include ActiveModel::API

  attr_accessor :name, :email, :message
  validates :name, :email, :message, presence: true

  def deliver
    if valid?
      # enviar correo electrónico
    end
  end
end

Al incluir ActiveModel::API, obtienes algunas características como:

  • introspección del nombre del modelo
  • conversiones
  • traducciones
  • validaciones

También te brinda la capacidad de inicializar un objeto con un hash de atributos, al igual que cualquier objeto de Active Record.

irb> email_contact = EmailContact.new(name: 'David', email: '[email protected]', message: 'Hola Mundo')
irb> email_contact.name
=> "David"
irb> email_contact.email
=> "[email protected]"
irb> email_contact.valid?
=> true
irb> email_contact.persisted?
=> false

Cualquier clase que incluya ActiveModel::API se puede utilizar con form_with, render y cualquier otro método de ayuda de Action View, al igual que los objetos de Active Record.

1.2 Métodos de atributo

El módulo ActiveModel::AttributeMethods puede agregar prefijos y sufijos personalizados a los métodos de una clase. Se utiliza definiendo los prefijos y sufijos y qué métodos del objeto los utilizarán.

class Person
  include ActiveModel::AttributeMethods

  attribute_method_prefix 'reset_'
  attribute_method_suffix '_highest?'
  define_attribute_methods 'age'

  attr_accessor :age

  private
    def reset_attribute(attribute)
      send("#{attribute}=", 0)
    end

    def attribute_highest?(attribute)
      send(attribute) > 100
    end
end
irb> person = Person.new
irb> person.age = 110
irb> person.age_highest?
=> true
irb> person.reset_age
=> 0
irb> person.age_highest?
=> false

1.3 Callbacks

ActiveModel::Callbacks proporciona callbacks al estilo de Active Record. Esto proporciona la capacidad de definir callbacks que se ejecutan en momentos apropiados. Después de definir los callbacks, puedes envolverlos con métodos personalizados antes, después y alrededor.

class Person
  extend ActiveModel::Callbacks

  define_model_callbacks :update

  before_update :reset_me

  def update
    run_callbacks(:update) do
      # Este método se llama cuando se llama a update en un objeto.
    end
  end

  def reset_me
    # Este método se llama cuando se llama a update en un objeto, ya que se ha definido un callback before_update.
  end
end

1.4 Conversión

Si una clase define los métodos persisted? e id, entonces puedes incluir el módulo ActiveModel::Conversion en esa clase y llamar a los métodos de conversión de Rails en objetos de esa clase.

class Person
  include ActiveModel::Conversion

  def persisted?
    false
  end

  def id
    nil
  end
end
irb> person = Person.new
irb> person.to_model == person
=> true
irb> person.to_key
=> nil
irb> person.to_param
=> nil

1.5 Dirty

Un objeto se vuelve "dirty" cuando ha sufrido uno o más cambios en sus atributos y no se ha guardado. ActiveModel::Dirty proporciona la capacidad de verificar si un objeto ha cambiado o no. También tiene métodos de acceso basados en atributos. Consideremos una clase Person con los atributos first_name y last_name:

class Person
  include ActiveModel::Dirty
  define_attribute_methods :first_name, :last_name

  def first_name
    @first_name
  end

  def first_name=(value)
    first_name_will_change!
    @first_name = value
  end

  def last_name
    @last_name
  end

  def last_name=(value)
    last_name_will_change!
    @last_name = value
  end

  def save
    # hacer trabajo de guardado...
    changes_applied
  end
end

1.5.1 Consultar directamente un objeto para obtener su lista de todos los atributos cambiados

irb> person = Person.new
irb> person.changed?
=> false

irb> person.first_name = "Nombre"
irb> person.first_name
=> "Nombre"

# Devuelve true si alguno de los atributos tiene cambios no guardados.
irb> person.changed?
=> true

# Devuelve una lista de atributos que han cambiado antes de guardar.
irb> person.changed
=> ["first_name"]

# Devuelve un Hash de los atributos que han cambiado con sus valores originales.
irb> person.changed_attributes
=> {"first_name"=>nil}

# Devuelve un Hash de los cambios, con los nombres de los atributos como claves y los valores como un array de los valores antiguos y nuevos para ese campo.
irb> person.changes
=> {"first_name"=>[nil, "Nombre"]}

1.5.2 Métodos de acceso basados en atributos

Realizar un seguimiento de si el atributo en particular ha cambiado o no. ```irb irb> persona.nombre => "Nombre"

¿attr_nombre_cambiado?

irb> persona.nombre_cambiado? => true ```

Realizar un seguimiento del valor anterior del atributo.

# Accesor attr_nombre_era
irb> persona.nombre_era
=> nil

Realizar un seguimiento de los valores anterior y actual del atributo cambiado. Devuelve una matriz si ha cambiado, de lo contrario devuelve nil.

# Cambio attr_nombre
irb> persona.cambio_nombre
=> [nil, "Nombre"]
irb> persona.cambio_apellido
=> nil

1.6 Validaciones

El módulo ActiveModel::Validations agrega la capacidad de validar objetos como en Active Record.

class Persona
  include ActiveModel::Validations

  attr_accessor :nombre, :email, :token

  validates :nombre, presence: true
  validates_format_of :email, with: /\A([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})\z/i
  validates! :token, presence: true
end
irb> persona = Persona.new
irb> persona.token = "2b1f325"
irb> persona.valid?
=> false
irb> persona.nombre = 'vishnu'
irb> persona.email = 'yo'
irb> persona.valid?
=> false
irb> persona.email = '[email protected]'
irb> persona.valid?
=> true
irb> persona.token = nil
irb> persona.valid?
ActiveModel::StrictValidationFailed

1.7 Nomenclatura

ActiveModel::Naming agrega varios métodos de clase que facilitan la gestión de la nomenclatura y el enrutamiento. El módulo define el método de clase model_name que define varios accesores utilizando algunos métodos de ActiveSupport::Inflector.

class Persona
  extend ActiveModel::Naming
end

Persona.model_name.name                # => "Persona"
Persona.model_name.singular            # => "persona"
Persona.model_name.plural              # => "personas"
Persona.model_name.element             # => "persona"
Persona.model_name.human               # => "Persona"
Persona.model_name.collection          # => "personas"
Persona.model_name.param_key           # => "persona"
Persona.model_name.i18n_key            # => :persona
Persona.model_name.route_key           # => "personas"
Persona.model_name.singular_route_key  # => "persona"

1.8 Modelo

ActiveModel::Model permite implementar modelos similares a ActiveRecord::Base.

class ContactoEmail
  include ActiveModel::Model

  attr_accessor :nombre, :email, :mensaje
  validates :nombre, :email, :mensaje, presence: true

  def enviar
    if valid?
      # enviar correo electrónico
    end
  end
end

Al incluir ActiveModel::Model, se obtienen todas las características de ActiveModel::API.

1.9 Serialización

ActiveModel::Serialization proporciona una serialización básica para su objeto. Debe declarar un Hash de atributos que contenga los atributos que desea serializar. Los atributos deben ser cadenas, no símbolos.

class Persona
  include ActiveModel::Serialization

  attr_accessor :nombre

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

Ahora puede acceder a un Hash serializado de su objeto utilizando el método serializable_hash.

irb> persona = Persona.new
irb> persona.serializable_hash
=> {"nombre"=>nil}
irb> persona.nombre = "Bob"
irb> persona.serializable_hash
=> {"nombre"=>"Bob"}

1.9.1 ActiveModel::Serializers

Active Model también proporciona el módulo ActiveModel::Serializers::JSON para la serialización / deserialización JSON. Este módulo incluye automáticamente el módulo ActiveModel::Serialization mencionado anteriormente.

1.9.1.1 ActiveModel::Serializers::JSON

Para usar ActiveModel::Serializers::JSON, solo necesita cambiar el módulo que está incluyendo de ActiveModel::Serialization a ActiveModel::Serializers::JSON.

class Persona
  include ActiveModel::Serializers::JSON

  attr_accessor :nombre

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

El método as_json, similar a serializable_hash, proporciona un Hash que representa el modelo.

irb> persona = Persona.new
irb> persona.as_json
=> {"nombre"=>nil}
irb> persona.nombre = "Bob"
irb> persona.as_json
=> {"nombre"=>"Bob"}

También puede definir los atributos de un modelo a partir de una cadena JSON. Sin embargo, debe definir el método attributes= en su clase:

class Persona
  include ActiveModel::Serializers::JSON

  attr_accessor :nombre

  def attributes=(hash)
    hash.each do |key, value|
      send("#{key}=", value)
    end
  end

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

Ahora es posible crear una instancia de Persona y establecer atributos usando from_json.

irb> json = { nombre: 'Bob' }.to_json
irb> persona = Persona.new
irb> persona.from_json(json)
=> #<Persona:0x00000100c773f0 @nombre="Bob">
irb> persona.nombre
=> "Bob"

1.10 Traducción

ActiveModel::Translation proporciona integración entre su objeto y el marco de internacionalización (i18n) de Rails.

class Persona
  extend ActiveModel::Translation
end

Con el método human_attribute_name, puede transformar los nombres de atributos en un formato más legible para los humanos. El formato legible para los humanos se define en su archivo(s) de localización.

  • config/locales/app.pt-BR.yml
pt-BR:
  activemodel:
    attributes:
      persona:
        nombre: 'Nome'
Persona.human_attribute_name('nombre') # => "Nome"

1.11 Pruebas de Lint

ActiveModel::Lint::Tests le permite probar si un objeto cumple con la API de Active Model.

  • app/models/persona.rb

    class Persona
      include ActiveModel::Model
    end
    
  • test/models/persona_test.rb

    require "test_helper"
    
    class PersonaTest < ActiveSupport::TestCase
      include ActiveModel::Lint::Tests
    
      setup do
        @model = Persona.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

No es necesario que un objeto implemente todas las API para funcionar con Action Pack. Este módulo solo pretende servir de guía en caso de que desee todas las características listas para usar.

1.12 SecurePassword

ActiveModel::SecurePassword proporciona una forma de almacenar de forma segura cualquier contraseña en forma cifrada. Cuando incluye este módulo, se proporciona un método de clase has_secure_password que define un accesor password con ciertas validaciones de forma predeterminada.

1.12.1 Requisitos

ActiveModel::SecurePassword depende de bcrypt, por lo tanto, incluye esta gema en tu Gemfile para usar ActiveModel::SecurePassword correctamente. Para que esto funcione, el modelo debe tener un accessor llamado XXX_digest. Donde XXX es el nombre del atributo de tu contraseña deseada. Las siguientes validaciones se agregan automáticamente:

  1. La contraseña debe estar presente.
  2. La contraseña debe ser igual a su confirmación (si se proporciona XXX_confirmation).
  3. La longitud máxima de una contraseña es 72 (requerido por bcrypt en el que depende ActiveModel::SecurePassword).

1.12.2 Ejemplos

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 supera los 72 caracteres.
irb> person.password = person.password_confirmation = 'a' * 100
irb> person.valid?
=> false

# Cuando solo se proporciona la contraseña sin la confirmación de la contraseña.
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"

irb> person.authenticate('aditya')
=> #<Person> # == person
irb> person.authenticate('notright')
=> false
irb> person.authenticate_password('aditya')
=> #<Person> # == person
irb> person.authenticate_password('notright')
=> false

irb> person.authenticate_recovery_password('42password')
=> #<Person> # == person
irb> person.authenticate_recovery_password('notright')
=> false

irb> person.password_digest
=> "$2a$04$gF8RfZdoXHvyTjHhiU4ZsO.kQqV9oonYZu31PRE4hLQn3xM2qkpIy"
irb> person.recovery_password_digest
=> "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"

Comentarios

Se te anima a ayudar a mejorar la calidad de esta guía.

Por favor, contribuye si encuentras algún error tipográfico o factual. Para empezar, puedes leer nuestra contribución a la documentación sección.

También puedes encontrar contenido incompleto o desactualizado. Por favor, añade cualquier documentación faltante para main. Asegúrate de revisar Edge Guides primero para verificar si los problemas ya están resueltos o no en la rama principal. Consulta las Directrices de las Guías de Ruby on Rails para el estilo y las convenciones.

Si por alguna razón encuentras algo que corregir pero no puedes solucionarlo tú mismo, por favor abre un problema.

Y por último, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.