Rails Custom Type — as a single value or as an array of custom type values
By: Kevin Liebholz
Tags: Postgres, Ruby on Rails, Database, Ruby

Currency: a Single Value Attribute of the Custom Type

Describing the Type’s Value

For simplicity, let’s define a currency type in which we store, unsurprisingly, a single currency (I know there is the money gem, it’s just for illustration). But why would we need that? In this case, we want certain behaviors implemented within a currency. We want a currency to be able to show itself in a string format, ISO 4217 code, the symbol (e.g. €) and we also want to be able to parse a currency string back to its currency class and also see all possible currencies. To do this, we describe a module `Currency`and add the single currencies as classes into it where we describe the mentioned methods:

The single classes, e.g. `Currency::EUR` are the values that we want to save to the database and want to get back from the database. So, having a model with attributes of this class, the `Payment`model, we can do

Payment.last.currency.iso

=> ‘EUR’

This means the value we’re getting from the currency attribute must be like

Payment.last.currency

=> Currency::EUR

So it needs to be automatically constantized when we retrieve the value from the database. Let’s see how we can do this.

Creating the Currency Type

For this, Rails gives us the class ‘ActiveRecord::Type::Value’ to inherit from which hands us the 4 methods we need to create our custom type.

type : defines how we actually save it in the database.

: defines how we actually save it in the database. cast : translates the user input to the object. This method gets called, once we assign the currency value (‘Currency::EUR’) to the object’s currency attribute.

: translates the user input to the object. This method gets called, once we assign the currency value (‘Currency::EUR’) to the object’s currency attribute. serialize : translates the value to something the database can understand (in this case a string). This method gets called, once we save the object to the database.

: translates the value to something the database can understand (in this case a string). This method gets called, once we save the object to the database. deserialize: translates the database understandable string to the currency value when we instantiate a Payment from a database record.

Each of these methods expects a parameter ‘value’. As we never really call any of those methods explicitly (Rails does that for us), we only need to know what these parameters are. And this changes depending on the method. This parameter will be either the value we assign to the object’s attribute (cast), the object attribute’s actual value (serialize), or the string from the database (deserialize).

Within the database, we want the currency to be saved as a string (e.g. ‘eur’), but if we read the value of a Payment object, we want it to be a currency class like ‘Currency::EUR’. So, we can do:

First, we say that we want it to be a string within the database by saying ‘string’ within the type method. If you recall the migration, that’s actually the type of the database column!

On assignment to a Payment object, we give a string to the currency attribute (just like it would be from a form) and it needs to be converted to a currency class. Only once we save the object, the serialize method converts the class to a string (which we defined within the class) and saves it as such. To check that, you could use something like Postico to read your database. If we now want to read that Payment from the database, the string will be deserialized using the `deserialize` method so we can access the currency class again. Try it in irb:

To have a check that this actually works the way we want, we can also add some specs:

As we now have everything needed for the single value custom type, let’s have a look at how it is done for an array.