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é.
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é.
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.
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
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.
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éthode
serialize` sur son propre objet type.
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