# module ActiveRecord::Attributes::ClassMethods

### Public Instance Methods

attribute(name, cast_type = Type::Value.new, **options) click to toggle source

Defines an attribute with a type on this model. It will override the type of existing attributes if needed. This allows control over how values are converted to and from SQL when assigned to a model. It also changes the behavior of values passed to ActiveRecord::Base.where. This will let you use your domain objects across much of Active Record, without having to rely on implementation details or monkey patching.

`name` The name of the methods to define attribute methods for, and the column which this will persist to.

`cast_type` A symbol such as `:string` or `:integer`, or a type object to be used for this attribute. See the examples below for more information about providing custom type objects.

#### Options¶ ↑

The following options are accepted:

`default` The default value to use when no value is provided. If this option is not passed, the previous default value (if any) will be used. Otherwise, the default will be `nil`.

`array` (PostgreSQL only) specifies that the type should be an array (see the examples below).

`range` (PostgreSQL only) specifies that the type should be a range (see the examples below).

#### Examples¶ ↑

The type detected by Active Record can be overridden.

    # 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

A default can also be provided.

    # 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

Attributes do not need to be backed by a database column.

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

#### Creating Custom Types¶ ↑

Users may also define their own custom types, as long as they respond to the methods defined on the value type. The method `deserialize` or `cast` will be called on your type object, with raw input from the database or from your controllers. See ActiveModel::Type::Value for the expected API. It is recommended that your type objects inherit from an existing type, or from 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

For more details on creating custom types, see the documentation for ActiveModel::Type::Value. For more details on registering your types to be referenced by a symbol, see ActiveRecord::Type.register. You can also pass a type object directly, in place of a symbol.

#### Querying¶ ↑

When ActiveRecord::Base.where is called, it will use the type defined by the model class to convert the value to SQL, calling `serialize` on your type object. For example:

    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

#### Dirty Tracking¶ ↑

The type of an attribute is given the opportunity to change how dirty tracking is performed. The methods `changed?` and `changed_in_place?` will be called from ActiveModel::Dirty. See the documentation for those methods in ActiveModel::Type::Value for more details.

    # File lib/active_record/attributes.rb, line 194
    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 ) click to toggle source

This is the low level API which sits beneath `attribute`. It only accepts type objects, and will do its work immediately instead of waiting for the schema to load. Automatic schema detection and #attribute both call this under the hood. While this method is provided so it can be used by plugin authors, application code should probably use #attribute.

`name` The name of the attribute being defined. Expected to be a `String`.

`cast_type` The type object to use for this attribute.

`default` The default value to use when no value is provided. If this option is not passed, the previous default value (if any) will be used. Otherwise, the default will be `nil`. A proc can also be passed, and will be called once each time a new value is needed.

`user_provided_default` Whether the default value should be cast using `cast` or `deserialize`.

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

### Private Instance Methods

define_default_attribute(name, value, type, from_user:) click to toggle source

    # File lib/active_record/attributes.rb, line 248
    def define_default_attribute(name, value, type, from_user))
      if value == NO_DEFAULT_PROVIDED
        default_attribute = _default_attributes[name].with_type(type)
      elsif from_user
        default_attribute = Attribute::UserProvidedDefault.new(
          name,
          value,
          type,
          _default_attributes.fetch(name.to_s) { nil },
        )
      else
        default_attribute = Attribute.from_database(name, value, type)
      end
      _default_attributes[name] = default_attribute
    end