[RUBY] [Rails] ActiveRecord :: Attributes :: ClassMethods

introduction

Pour les ambiguïtés, lisez correctement la documentation Rails.

Suivez Rails ActiveRecord :: Attributes :: ClassMethods Documentation.

Lorsque vous créez récemment un objet de formulaire, obtenez une meilleure compréhension de «attribut», qui a un comportement ambigu en moi.

C'était une reconnaissance que ʻattr_accessor` peut être tapé.

Aperçu

attribute(name, cast_type = Type::Value.new, **options)

Définit un attribut qui a un type de modèle. Remplacez les types d'attributs existants si nécessaire. Cela vous permet de ** contrôler la façon dont les valeurs sont converties vers et depuis SQL lorsqu'elles sont affectées à un modèle **. Il modifie également le comportement de la valeur transmise à ʻActiveRecord :: Base.where`. Cela permet à la plupart des enregistrements actifs d'utiliser des objets de domaine sans recourir aux détails d'implémentation ou aux correctifs de singe.

name: Le nom de la méthode qui définit la méthode d'attribut et la colonne à conserver.

cast_type: Un symbole tel que: string ou: integer, ou un objet type utilisé pour cet attribut. Consultez l'exemple ci-dessous pour plus d'informations sur la fourniture d'objets de type personnalisé.

option

Les options suivantes sont disponibles.

default: La valeur par défaut à utiliser si aucune valeur n'est donnée. Si cette option n'est pas spécifiée, la valeur par défaut précédente (?) Sera utilisée si elle existe, sinon la valeur par défaut sera «nil».

ʻArray (PostgreSQL uniquement): indique que le type est ʻarray.

range (PostgreSQL uniquement): indique que le type est range.

L'utilisation du symbole cast_type transmet des options supplémentaires au constructeur de l'objet type.

Exemple

Les types trouvés par ActiveRecord peuvent être remplacés.

# db/schema.rb
create_table :store_listings, force: true do |t|
  t.decimal :price_in_cents
end

# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
end

store_listing = StoreListing.new(price_in_cents: '10.1')

# before
store_listing.price_in_cents #=> BigDecimal(10.1)

class StoreListing < ActiveRecord::Base
  attribute :price_in_cents, :integer
end

# after
store_listing.price_in_cents # => 10

Dans certains cas, la valeur par défaut est transmise.

# db/schema.rb
create_table :store_listings, force: true do |t|
  t.decimal :my_string, default: 'original default'
end

StoreListing.new.my_string # => "original default"

# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
  attribute :my_string, :string, default: "new default"
end

StoreListing.new.my_string # => "new default"

class Product < ActiveRecord::Base
  attribute :my_default_proc, :datetime, default: -> { Time.now }
end

Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
sleep 1
Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600

Les attributs ne doivent pas nécessairement être basés sur des colonnes de base de données.

# app/models/my_model.rb
class MyModel < ActiveRecord::Base
  attribute :my_string, :string
  attribute :my_int_array, :integer, array: true
  attribute :my_float_range, :float, range: true
end

model = MyModel.new(
  my_string: "string",
  my_int_array: ["1", "2", "3"]
  my_float_range: "[1,3.5]",
)
model.attributes
# =>
  {
    my_string: "string",
    my_int_array: [1, 2, 3],
    my_float_range: 1.0..3.5
  }

Si vous passez une option au constructeur de type

# app/models/my_model.rb
class MyModel < ActiveRecord::Base
  attribute :small_int, :integer, limit: 2
end

MyModel.create(small_int: 65537)
# => Error: 65537 is out of range for the limit of two bytes

Essayez de créer un type personnalisé

L'utilisateur peut également définir des types personnalisés tant qu'il s'agit de méthodes définies dans le type valeur. Les méthodes deserialize et cast sont appelées par leur propre objet type, en utilisant l'entrée brute de la base de données ou du contrôleur. Voir ʻActiveModel :: Type :: Value pour l'API attendue. Il est recommandé que l'objet type hérite d'un type existant ou ʻActiveRecord :: Type :: Value.

class MoneyType < ActiveRecord::Type::Integer
  def cast(value)
    if !value.kind_of?(Numeric) && value.include?('$')
      price_in_dollars 0 value.gsub(/\$/, '').to_f
      super(price_in_dollars * 100)
    else
      super
    end
  end
end

# config/initializers/types.rb
ActiveRecord::Type.register(:money, MoneyType)

# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
  attribute :price_in_cents, :money
end

store_listing = StoreListing.new(price_in_cents: '$10.00')
store_listing.price_in_cents # => 1000

Lisez la documentation ʻActiveModel :: Type :: Value pour plus d'informations sur les types personnalisés. Lisez la documentation ʻActiveRecord :: Type.register pour les types uniques référencés par des symboles. Vous pouvez également transmettre un objet de type directement au lieu d'un symbole.

Requete

Lorsque ʻActiveRecord :: Base.whereest appelé, il utilise le type défini par la classe model et le convertit en valeur SQL en appelant la méthodeserialize` sur son propre objet type.

Exemple

class Money < Struct.new(:amount, :currency)
end

class MoneyType < Type::Value
  def initialize(currency_converter:)
    @currency_converter = currency_converter
  end

  # value will be the result of +deserialiez+ or
  # +cast+. Assumed to be an instance of +Money+ in
  # this case.
  def serialize(value)
    value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
    value_in_bitcoints.amount
  end
end

# config/initializers/types.rb
ActiveRecord::Type.register(:money, MoneyType)

# app/models/product.rb
class Product < ActiveRecord::Base
  currency_converter = ConversionRatesFromTheInternet.new
  attribute :price_in_bitcoins, :money, currency_converter: currency_converter
end

Product.where(price_in_bitcoins: Money.new(5, "USD"))
# => SELECT * FROM products WHERE price_in_bitcoins = 0.02230

Product.where(price_in_bitcoins: Money.new(5, "GBP"))
# => SELECT * FROM products WHERE price_in_bitcoins = 0.03412

Dirty Tracking

Le type d '«attribut» peut également changer le comportement du suivi sale. Les méthodes changed? Et changed_in_pace? Sont appelées depuis la classe ʻActiveModel :: Dirty. Voir les méthodes de la classe ʻActiveModel :: Type :: Value pour plus de détails.

# File activerecord/lib/active_record/attributes.rb, line 208
def attribute(name, cast_type = Type::Value.new, **options)
  name = name.to_s
  reload_schema_from_cache

  self.attributes_to_define_after_schema_loads = 
    attributes_to_define_after_schema_loads.merge(
      name => [cast_type, options]
    )
end

define_attribute( name, cast_type, default: NO_DEFAULT_PROVIDED, user_provided_default: true )

Il s'agit d'une API située sous le «attribut» de bas niveau. Accepte uniquement les objets de type et effectue le travail immédiatement au lieu d'attendre le chargement du schéma. La découverte automatique de schéma et l'attribut «ClassMethods #» l'appellent en interne. Cette méthode est fournie pour être utilisée par l'auteur du plug-in, mais le code de votre application devra probablement utiliser ClassMethods # attribute.

name: Le nom de l'attribut à définir. Devrait être "String".

cast_type: L'objet type utilisé pour cet attribut.

default: La valeur par défaut à utiliser si aucune valeur n'est donnée. Si cette option n'est pas spécifiée, la valeur par défaut précédente (?) Sera utilisée si elle existe, sinon la valeur par défaut sera «nil». Les procédures peuvent également être passées et seront appelées chaque fois que de nouvelles valeurs sont nécessaires.

ʻUser_provided_default: La valeur par défaut est convertie en utilisant la méthode cast ou deserialize`.

# File activerecord/lib/actvie_record/attributes.rb, line 236
def define_attribute(
  name,
  cast_type,
  default: NO_DEFAULT_PROVIDED,
  user_provided_default: true
)
  attribute_types[name] = cast_type
  define_default_attribute(name, default, cast_type, from_user: user_provided_default)
end

Résumé

Recommended Posts

[Rails] ActiveRecord :: Attributes :: ClassMethods
Résoudre ActiveRecord :: NoDatabaseError sur les rails6
Rails 5 Lecture de code Partie 1 ~ Nouvelle méthode ActiveRecord ~
[Rails] Comment utiliser ActiveRecord :: Bitemporal (BiTemporalDataModel)