Spec-Zone.ru › Ruby on Rails 7.1

module ActiveRecord::Attributes::ClassMethods

Атрибуты Active Record

Методы экземпляра public

attribute(name, cast_type = nil, default: NO_DEFAULT_PROVIDED, **options) { |Proc === prev_cast_type ? prev_cast_type : prev_cast_type| ... } Показать исходный код
# File activerecord/lib/active_record/attributes.rb, line 208
def attribute(name, cast_type = nil, default: NO_DEFAULT_PROVIDED, **options)
  name = name.to_s
  name = attribute_aliases[name] || name

  reload_schema_from_cache

  case cast_type
  when Symbol
    cast_type = Type.lookup(cast_type, **options, adapter: Type.adapter_name_from(self))
  when nil
    if (prev_cast_type, prev_default = attributes_to_define_after_schema_loads[name])
      default = prev_default if default == NO_DEFAULT_PROVIDED
    else
      prev_cast_type = -> subtype { subtype }
    end

    cast_type = if block_given?
      -> subtype { yield Proc === prev_cast_type ? prev_cast_type[subtype] : prev_cast_type }
    else
      prev_cast_type
    end
  end

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

Определяет атрибут с типом в этой модели. Он перезапишет тип существующих атрибутов при необходимости. Это позволяет управлять тем, как значения преобразуются в SQL и из SQL при присвоении модели. Также это изменяет поведение значений, передаваемых в ActiveRecord::Base.where. Это позволит вам использовать ваши доменные объекты во многих частях Active Record, без необходимости полагаться на детали реализации или подмены методов.

name Имена методов для определения атрибутов и столбец, в который это будет сохраняться.

cast_type Символ, такой как :string или :integer, или объект типа, используемый для этого атрибута. См. примеры ниже для получения дополнительной информации о предоставлении пользовательских объектов типа.

Параметры

Принимаются следующие параметры:

default Значение по умолчанию, которое используется, когда значение не предоставлено. Если этот параметр не передан, используется предыдущее значение по умолчанию (если таковое имеется). В противном случае значение по умолчанию будет nil.

array (только PostgreSQL) указывает, что тип должен быть массивом (см. примеры ниже).

range (только PostgreSQL) указывает, что тип должен быть диапазоном (см. примеры ниже).

При использовании символа для cast_type, дополнительные параметры передаются в конструктор объекта типа.

Примеры

Тип, обнаруженный Active Record, может быть перезаписан.

# 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

Также может быть предоставлено значение по умолчанию.

# db/schema.rb
create_table :store_listings, force: true do |t|
  t.string :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

Атрибуты не обязательно должны иметь поддержку столбца в базе данных.

# 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
  }

Передача параметров в конструктор типа

# 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

Создание пользовательских типов

Пользователи также могут определять свои собственные пользовательские типы, если они отвечают методам, определённым для типа значения. Метод deserialize или cast будет вызываться на вашем объекте типа с исходными данными из базы данных или из ваших контроллеров. См. ActiveModel::Type::Value для ожидаемой API. Рекомендуется, чтобы ваши объекты типа наследовались от существующего типа или от ActiveRecord::Type::Value.

class MoneyType < ActiveRecord::Type::Integer
  def cast(value)
    if !value.kind_of?(Numeric) && value.include?('$')
      price_in_dollars = 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

Для получения более подробной информации о создании пользовательских типов см. документацию по ActiveModel::Type::Value. Для получения более подробной информации об регистрации ваших типов для ссылки по символу, см. ActiveRecord::Type.register. Вы также можете передать объект типа напрямую вместо символа.

Запросы

Когда вызывается ActiveRecord::Base.where, он будет использовать тип, определенный классом модели, для преобразования значения в SQL, вызывая serialize на вашем объекте типа. Например:

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

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

  # value will be the result of +deserialize+ 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_bitcoins.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

Отслеживание изменений

Тип атрибута получает возможность изменить способ выполнения отслеживания изменений. Методы changed? и changed_in_place? будут вызваны из ActiveModel::Dirty. См. документацию по этим методам в ActiveModel::Type::Value для получения дополнительной информации.

define_attribute( name, cast_type, default: NO_DEFAULT_PROVIDED, user_provided_default: true ) Показать исходный код
# File activerecord/lib/active_record/attributes.rb, line 253
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

Это низкоуровневый API, расположенный под attribute. Он принимает только объекты типа и выполняет свою работу сразу же, вместо ожидания загрузки схемы. Автоматическое определение схемы и ClassMethods#attribute оба используют это под капотом. Хотя этот метод предоставляется для использования авторами плагинов, код приложения, вероятно, должен использовать ClassMethods#attribute.

name Название определяемого атрибута. Ожидается String.

cast_type Объект типа для использования с этим атрибутом.

default Значение по умолчанию, используемое, когда значение не предоставлено. Если этот параметр не передан, используется предыдущее значение по умолчанию (если таковое имеется). В противном случае значение по умолчанию будет nil. Также можно передать лямбда-выражение, которое будет вызываться каждый раз, когда требуется новое значение.

user_provided_default Указывает, должно ли значение по умолчанию быть преобразовано с помощью cast или deserialize.

© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API