---
title: "Rails 5 Active Record attributes API"
description:
  "Rails 5 introduces Active Record attributes API to type cast values to
  appropriate Ruby types"
canonical_url: "https://www.bigbinary.com/blog/rails-5-attributes-api"
markdown_url: "https://www.bigbinary.com/blog/rails-5-attributes-api.md"
---

# Rails 5 Active Record attributes API

Rails 5 introduces Active Record attributes API to type cast values to
appropriate Ruby types

- Author: Abhay Nikam
- Published: December 11, 2018
- Categories: Rails 5, Rails

Rails 5 was a major release with a lot of new features like Action Cable, API
Applications, etc. Active Record attribute API was also one of the features of
Rails 5 release which did not receive much attention.

Active Record attributes API is used by Rails internally for a long time. In
Rails 5 release, attributes API was made public and allowed support for custom
types.

#### What is attribute API?

Attribute API converts the attribute value to an appropriate Ruby type. Here is
how the syntax looks like.

```ruby
attribute(name, cast_type, options)
```

The first argument is the name of the attribute and the second argument is the
cast type. Cast type can be `string`, `integer` or custom type object.

```ruby
# db/schema.rb

create_table :movie_tickets, force: true do |t|
  t.float :price
end

# without attribute API

class MovieTicket < ActiveRecord::Base
end

movie_ticket = MovieTicket.new(price: 145.40)
movie_ticket.save!

movie_ticket.price   # => Float(145.40)

# with attribute API

class MovieTicket < ActiveRecord::Base
  attribute :price, :integer
end

movie_ticket.price   # => 145
```

Before using attribute API, movie ticket price was a float value, but after
applying attribute on price, the price value was typecast as integer.

The database still stores the price as float and this conversion happens only in
Ruby land.

Now, we will typecast movie `release_date` from `datetime` to `date` type.

```ruby
# db/schema.rb

create_table :movies, force: true do |t|
  t.datetime :release_date
end

class Movie < ActiveRecord::Base
  attribute :release_date, :date
end

movie.release_date # => Thu, 01 Mar 2018

```

We can also add default value for an attribute.

```ruby
# db/schema.rb

create_table :movies, force: true do |t|
  t.string :license_number, :string
end

class Movie < ActiveRecord::Base
  attribute :license_number,
            :string,
            default: "IN00#{Date.current.strftime('%Y%m%d')}00#{rand(100)}"
end

# without attribute API with default value on license number

Movie.new.license_number  # => nil

# with attribute API with default value on license number

Movie.new.license_number  # => "IN00201805250068"
```

#### Custom Types

Let's say we want the people to rate a movie in percentage. Traditionally, we
would do something like this.

```ruby
class MovieRating < ActiveRecord::Base

  TOTAL_STARS = 5

  before_save :convert_percent_rating_to_stars

  def convert_percent_rating_to_stars
    rating_in_percentage = value.gsub(/\%/, '').to_f

    self.rating = (rating_in_percentage * TOTAL_STARS) / 100
  end
end
```

With attributes API we can create a custom type which will be responsible to
cast to percentage rating to number of stars.

We have to define the `cast` method in the custom type class which casts the
given value to the expected output.

```ruby
# db/schema.rb

create_table :movie_ratings, force: true do |t|
  t.integer :rating
end

# app/types/star_rating_type.rb

class StarRatingType < ActiveRecord::Type::Integer
  TOTAL_STARS = 5

  def cast(value)
    if value.present? && !value.kind_of?(Integer)
      rating_in_percentage = value.gsub(/\%/, '').to_i

      star_rating = (rating_in_percentage * TOTAL_STARS) / 100
      super(star_rating)
    else
      super
    end
  end
end

# config/initializers/types.rb

ActiveRecord::Type.register(:star_rating, StarRatingType)

# app/models/movie.rb

class MovieRating < ActiveRecord::Base
  attribute :rating, :star_rating
end

```

#### Querying

The attributes API also supports `where` clause. Query will be converted to SQL
by calling `serialize` method on the type object.

```ruby
class StarRatingType < ActiveRecord::Type::Integer
  TOTAL_STARS = 5

  def serialize(value)
    if value.present? && !value.kind_of?(Integer)
      rating_in_percentage = value.gsub(/\%/, '').to_i

      star_rating = (rating_in_percentage * TOTAL_STARS) / 100
      super(star_rating)
    else
      super
    end
  end
end


# Add new movie rating with rating as 25.6%.
# So the movie rating in star will be 1 of 5 stars.
movie_rating = MovieRating.new(rating: "25.6%")
movie_rating.save!

movie_rating.rating   # => 1

# Querying with rating in percentage 25.6%
MovieRating.where(rating: "25.6%")

# => #<ActiveRecord::Relation [#<MovieRating id: 1000, rating: 1 ... >]>
```

## Links

- [Human page](https://www.bigbinary.com/blog/rails-5-attributes-api)
