20.09.2016

Custom Attributes in Ruby on Rails 5 ActiveRecord

Article by
author
Robert Krajewski
7 min read

Lets assume the predefined ActiveRecord like integer or string attributes are no enough for you.
For example you would like to have a money format, roman numeral, custom time format etc. The Rails 5 provide excellent interface just for this purpose.

To demonstrate this feature we will build a model of time tracker with a custom time entries attribute. Our goal is to be able to provide time with the following format: ad bh cm, where a, b and c are integers, and d, h, m means days, hours and minutes respectively (regular expression: ((\d)+d)?\s*((\d)+h)?\s*((\d)+m)?). Eg. 3d 5h 1m means 3 days, 5 hours, 1 minute.

Lets start with a simple model TimeEntry with only one integer field: spent when we would like to store the spent time in minutes. The basic usage of custom attributes gives you power to change the attribute type or add default value eg:

1
2
3
4
# app/models/time_entry.rb
class TimeEntry < ApplicationRecord
attribute :spent, :integer, default: 0
end

Changing attribute type in our case doesn’t make any sense but think about different usages. E.g. changing the amount of completed task from :decimalto :integer where you can image the task could be partially done.

But the power of the new interface shines when the attribute should be truly custom as in our example.
Lets add a custom attribute to our class:

1
2
3
4
# app/models/time_entry.rb
class TimeEntry < ApplicationRecord
attribute :spent, :integer, default: 0
end

Now the attribute spent has a :project_time type. We have to define this attribute:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# app/models/attributes/time_entry.rb
class ProjectTime < ActiveRecord::Type::Integer
def deserialize(value)
if value.is_a?(String)
to_minutes(value)
else
super
end
end
 
def serialize(value)
to_minutes(value)
end
 
private
 
def to_minutes(time)
time_sum = 0
time.split(' ').each do |time_part|
value = time_part.to_i
type = time_part[-1,1]
case type
when 'm'
when 'h'
value *= 60
when 'd'
value *= 8*60
else
value *= 60
end
time_sum += value
end
time_sum
end
end

Our attribute class must derive from one of the ActiveRecord types and implement method cast(value) which transforms provided value to derived Active Record type. In our case we perform transformation only when the provided value is a String otherwise the default integer casting is performed. Private method to_minutes converts formatted time to an integer representing spent minutes. I assumed that 1d = 8h = 480m. E.g. result of to_minutes('1d 1h 1m') = 541

The one final and obligatory step is to connect new :project_time type with respective class ProjectType:

1
2
# config/initializers/type.rb
ActiveRecord::Type.register(:project_time, ProjectTime)

Now you can create time entries like that:

1
2
entry = TimeEntry.create(spent: '1d 1h 1m')
entry.spent #=> 541

But now thank to serialize method we are also able to search:

1
2
3
TimeEntry.where(spent: '1d 1h 1m').count #=> 1
TimeEntry.where(spent: '9h 1m').count #=> 1
TimeEntry.where(spent: '1d 1h 2m').count #=> 0

You can investigate this mechanism deeper on the API documentation page:
http://edgeapi.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html

author

Robert Krajewski

Co-founder and CEO of Ideamotive - custom software development company. Entrepreneur, who with passion spreads digital revolution all around the internet. Mentor and advisor at startup accelerators. Loves to learn and discover new business models.

Did you enjoy the read?

We highly recommend to check out our other articles. If you are looking for a career opportunity, feel free to browse our job offers. Do you want to have your digital project estimated? Just click on a button below.

Estimate project Browse job offers
im-logo We are IDEAMOTIVE

We are software developement house located in the hearth of Europe - Warsaw, Poland. Our main areas of expertise include Ruby on Rails, React and React Native.

Recommended & popular articles

Pros And Cons Of IT Nearshoring And Offshoring

Ideamotive Weekly Digest #1

Ruby on Rails vs Django – Which One To Pick For Your Web Project?

Consult Your product with our experts

Get an estimate or contact us