v6.1.4
Veja mais em rubyonrails.org: Mais Ruby on Rails

Básico de Active Model

Esse guia deverá prover tudo que você precisa para começar a usar classes de modelo (models). O Active Model permite que o Action Pack Helpers interaja com os objetos ruby. O Active model também auxilia a criação de ORMs (mapeamento de objetos relacionais) para o uso fora do framework Rails.

Após a leitura desse guia você saberá: * Como um Active Record se comporta. * Como Callbacks e Validações funcionam. * Como serializers funcionam. * Como Active Model integra com o framework de internacionalização(i18n) do Rails.

1 O que é Active Model?

O Active Model é uma biblioteca que contém vários módulos utilizados para desenvolvimento de classes que precisam de algumas funções (features) existentes no Active Record.

Alguns desses módulos serão explicados abaixo.

1.1 Métodos de Atributo

O ActiveModel::AttributeMethods, módulo que pode adicionar prefixos e sufixos customizados nos metodos de uma classe. Isso é feito pela definição dos prefixos e sufixos e quais métodos no objeto que vai utilizá-los.

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.2 Callbacks

ActiveModel::Callbacks trazem os callbacks no padrão do Active Record. Isso provê a habilidade de definir o callback que rodará no tempo apropriado. Após definir os callbacks, você pode envolvê-los com métodos customizados antes, depois e durante.

class Person
  extend ActiveModel::Callbacks

  define_model_callbacks :update

  before_update :reset_me

  def update
    run_callbacks(:update) do
      # This method is called when update is called on an object.
    end
  end

  def reset_me
    # This method is called when update is called on an object as a before_update callback is defined.
  end
end

1.3 Conversão

Se a classe define os métodos persisted? e id, então você pode incluir o módulo ActiveModel::Conversion naquela classe e chamar os métodos de conversão do Rails nos objetos daquela classe.

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.4 Sujeira

Um objeto se torna sujo quando ele passa por uma ou mais mudanças nos seus atributos e isso não foi salvo. O ActiveModel::Dirty te concede a habilidade de checar quando um objeto foi alterado ou não. Também possui atributos baseados em métodos de acesso. Vamos considerar a classe Person com os atributos first_name e 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
    # do save work...
    changes_applied
  end
end
1.4.1 Consultando o objeto diretamente para obter uma lista de todos os atributos alterados.
irb> person = Person.new
irb> person.changed?
=> false

irb> person.first_name = "First Name"
irb> person.first_name
=> "First Name"

# Retorna true se algum dos atributos tem mudanças não salvas.
irb> person.changed?
=> true

# Retorna uma lista de atributos que mudaram antes de salvar.
irb> person.changed
=> ["first_name"]

# Retorna um Hash dos atributos que mudaram junto com seu valor original.
irb> person.changed_attributes
=> {"first_name"=>nil}

# Retorna um Hash de mudanças, com o nome dos atributos como chave, e o valor da chave como um array de valores antigos e novos para aquele campo.
irb> person.changes
=> {"first_name"=>[nil, "First Name"]}
1.4.2 Atributos baseados em métodos de acesso

Acompanhe se o atributo específico foi alterado ou não.

irb> person.first_name
=> "First Name"

# attr_name_changed?
irb> person.first_name_changed?
=> true

Track the previous value of the attribute.

# attr_name_was accessor
irb> person.first_name_was
=> nil

Track both previous and current value of the changed attribute. Returns an array if changed, otherwise returns nil.

# attr_name_change
irb> person.first_name_change
=> [nil, "First Name"]
irb> person.last_name_change
=> nil

1.5 Validações

O módulo ActiveModel::Validations adiciona a habilidade de validar objetos como no Active Record.

class Person
  include ActiveModel::Validations

  attr_accessor :name, :email, :token

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

1.6 Nomeação

ActiveModel::Naming adiciona vários métodos de classe que tornam a nomeação e o roteamento mais fácil de administrar. O módulo define o método da classe model_name que definirá vários acessadores usando alguns métodos do ActiveSupport::Inflector.

class Person
  extend ActiveModel::Naming
end

Person.model_name.name                # => "Person"
Person.model_name.singular            # => "person"
Person.model_name.plural              # => "people"
Person.model_name.element             # => "person"
Person.model_name.human               # => "Person"
Person.model_name.collection          # => "people"
Person.model_name.param_key           # => "person"
Person.model_name.i18n_key            # => :person
Person.model_name.route_key           # => "people"
Person.model_name.singular_route_key  # => "person"

1.7 Model

O ActiveModel::Model adiciona a capacidade de uma classe trabalhar com o Action Pack e Action View imediatamente.

class EmailContact
  include ActiveModel::Model

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

  def deliver
    if valid?
      # deliver email
    end
  end
end

Ao incluir o ActiveModel::Model, você obtém alguns recursos como:

  • introspecção do nome de model
  • conversões
  • traduções
  • validações

Também oferece a capacidade de inicializar um objeto com um hash de atributos, muito parecido com qualquer objeto Active Record.

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

Qualquer classe que inclua ActiveModel::Model pode ser usada com form_with, render e quaisquer outros métodos auxiliares do Action View, assim como o Active Record objetos.

1.8 Serialização

O ActiveModel::Serialization fornece serialização básica para o seu objeto. Você precisa declarar um Hash de atributos que contém os atributos que deseja serializar. Os atributos devem ser cadeias, não símbolos.

class Person
  include ActiveModel::Serialization

  attr_accessor :name

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

Agora você pode acessar um Hash serializado do seu objeto usando o método serializable_hash.

irb> person = Person.new
irb> person.serializable_hash
=> {"name"=>nil}
irb> person.name = "Bob"
irb> person.serializable_hash
=> {"name"=>"Bob"}
1.8.1 ActiveModel::Serializers

O Active Model também fornece o módulo ActiveModel::Serializers::JSON para serialização / desserialização JSON. Este módulo inclui automaticamente o módulo ActiveModel::Serialization discutido anteriormente.

1.8.1.1 ActiveModel::Serializers::JSON

Para usar o ActiveModel::Serializers::JSON, você só precisa alterar o módulo que você está incluindo de ActiveModel::Serialization paraActiveModel::Serializers::JSON.

class Person
  include ActiveModel::Serializers::JSON

  attr_accessor :name

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

O método as_json, semelhante aoserializable_hash, fornece um Hash representando o modelo.

irb> person = Person.new
irb> person.as_json
=> {"name"=>nil}
irb> person.name = "Bob"
irb> person.as_json
=> {"name"=>"Bob"}

Você também pode definir os atributos para um modelo a partir de uma sequência JSON. No entanto, você precisa definir o método attribute= na sua classe:

class Person
  include ActiveModel::Serializers::JSON

  attr_accessor :name

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

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

Agora é possível criar uma instância de Person e definir atributos usandofrom_json.

irb> json = { name: 'Bob' }.to_json
irb> person = Person.new
irb> person.from_json(json)
=> #<Person:0x00000100c773f0 @name="Bob">
irb> person.name
=> "Bob"

1.9 Tradução

O ActiveModel::Translation fornece integração entre seu objeto e o Rails internacionalização (i18n).

class Person
  extend ActiveModel::Translation
end

Com o método human_attribute_name, você pode transformar nomes de atributos em um formato mais legível por humanos. O formato legível por humanos é definido nos seus arquivos de localidade.

  • config/locales/app.pt-BR.yml
pt-BR:
  activemodel:
    attributes:
      person:
        name: 'Nome'
Person.human_attribute_name('name') # => "Nome"

1.10 Lint Tests

O ActiveModel::Lint::Tests permite testar se um objeto é compatível com a API do model ativo.

  • app/models/person.rb

    class Person
      include ActiveModel::Model
    end
    
  • test/models/person_test.rb

    require "test_helper"
    
    class PersonTest < ActiveSupport::TestCase
      include ActiveModel::Lint::Tests
    
      setup do
        @model = Person.new
      end
    end
    
$ bin/rails test

Run options: --seed 14596

# Running:

......

Finished in 0.024899s, 240.9735 runs/s, 1204.8677 assertions/s.

6 runs, 30 assertions, 0 failures, 0 errors, 0 skips

Não é necessário um objeto para implementar todas as APIs para trabalhar com Action Pack. Este módulo pretende apenas fornecer orientação caso você queira que todos recursos prontos para uso.

1.11 SecurePassword

O ActiveModel::SecurePassword fornece uma maneira de armazenar com segurança qualquer senha de forma criptografada. Quando você inclui este módulo, é fornecido o método da classe has_secure_password que define um acessador de senha com certas validações por padrão.

1.11.1 Requerimentos

O ActiveModel::SecurePassword depende de bcrypt, portanto, inclua esta gem no seu Gemfile para usar oActiveModel::SecurePassword corretamente. Para fazer isso funcionar, o model deve ter um acessador chamado XXX_digest. Onde XXX é o nome do atributo da sua senha desejada. As seguintes validações são adicionadas automaticamente:

  1. A senha deve estar presente.
  2. A senha deve ser igual à sua confirmação (desde que XXX_confirmation seja passada adiante).
  3. O tamanho máximo de uma senha é 72 (exigido pelo bcrypt do qual o ActiveModel::SecurePassword depende)
1.11.2 Exemplos
class Person
  include ActiveModel::SecurePassword
  has_secure_password
  has_secure_password :recovery_password, validations: false

  attr_accessor :password_digest, :recovery_password_digest
end
irb> person = Person.new

# Quando a senha está em branco.
irb> person.valid?
=> false

# Quando a confirmação não é igual a senha.
irb> person.password = 'aditya'
irb> person.password_confirmation = 'nomatch'
irb> person.valid?
=> false

# Quando o tamanho da senha é maior que 72 caracteres.
irb> person.password = person.password_confirmation = 'a' * 100
irb> person.valid?
=> false

# Quando só a senha é enviada sem a password_confirmation.
irb> person.password = 'aditya'
irb> person.valid?
=> true

# Quando todas as validações foram atendidas.
irb> person.password = person.password_confirmation = 'aditya'
irb> person.valid?
=> true

irb> person.recovery_password = "42password"

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

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

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

Feedback

Você é incentivado a ajudar a melhorar a qualidade deste guia.

Por favor, contribua se vir qualquer erros, inclusive erros de digitação. Para começar, você pode ler nossa sessão de contribuindo com a documentação.

Você também pode encontrar conteúdo incompleto ou coisas que não estão atualizadas. Por favor, adicione qualquer documentação em falta na main do Rails. Certifique-se de checar o Edge Guides (en-US) primeiro para verificar se o problema já foi resolvido ou não no branch main. Verifique as Diretrizes do Guia Ruby on Rails para estilo e convenções.

Se, por qualquer motivo, você encontrar algo para consertar, mas não conseguir consertá-lo, por favor abra uma issue no nosso Guia.

E por último, mas não menos importante, qualquer tipo de discussão sobre a documentação do Ruby on Rails é muito bem vinda na lista de discussão rubyonrails-docs e nas issues do Guia em português.


dark theme icon