Spec-Zone.ru › Ruby on Rails 5.0

модуль ActiveRecord::Attributes::ClassMethods

Публичные методы экземпляра

attribute(name, cast_type, **options) Показать исходный код
# File activerecord/lib/active_record/attributes.rb, line 194
def attribute(name, cast_type, **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

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

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

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

Параметры

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

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

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

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

Примеры

Тип, обнаруженный 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.new(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
  }

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

Пользователи также могут определять свои собственные пользовательские типы, если они отвечают на методы, определенные для типа значения. Метод deserialize или cast будет вызываться для вашего объекта типа с необработанным вводом из базы данных или из ваших контроллеров. См. ActiveRecord::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

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

Запросы

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

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 +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. Более подробную информацию об этих методах см. в документации для ActiveRecord::Type::Value.

define_attribute( name, cast_type, default: NO_DEFAULT_PROVIDED, user_provided_default: true ) Показать исходный код
# File activerecord/lib/active_record/attributes.rb, line 222
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. Он принимает только объекты типа и выполнит свою работу немедленно, вместо ожидания загрузки схемы. Автоматическое обнаружение схемы и #attribute оба обращаются к этому под капотом. Хотя этот метод предоставляется для использования разработчиками плагинов, код приложения, скорее всего, должен использовать #attribute.

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

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

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

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

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

Spec-Zone.ru

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