Using Hstore with Rails 4
I have a big crush on Hstore and its new native support in Rails 4.
If you aren’t familiar with HStore. It basically gives you a schema-less key/value datastore in your PostgreSQL DB. This allows you to store the equivalent of a hash in a database column.
How to do it…
First, you need to enable the hstore extension in the PostgreSQL database. You can do this with a migration.
class AddHstore < ActiveRecord::Migration
def up
enable_extension :hstore
end
def down
disable_extension :hstore
end
end
Next, since hstore is now a natively recognized datatype in Rails, you can add an hstore column to any existing model. Here I’m adding a “settings” column to my user model.
class AddSettingsToUser < ActiveRecord::Migration
def up
add_column :users, :settings, :hstore
end
def down
remove_column :users, :settings
end
end
Finally, you can define accessors for your hstore keys in your model. Validations work just like they would for any other column in your model.
class User < ActiveRecord::Base
# setup hstore
store_accessor :settings, :favorite_color, :time_zone
# can even run typical validations on hstore fields
validates :favorite_color,
inclusion: { in: %w{blue, gold, red} }
validates_inclusion_of :time_zone,
in: ActiveSupport::TimeZone.zones_map { |m| m.name },
message: 'is not a valid Time Zone'
end
Handling data types
One thing to look out for is storing booleans in hstore. Hstore will convert booleans to strings. A quick solution to this is overwriting your getter method to convert them back to a boolean on read. Here’s an example:
# convert string to boolean. hstore only stores strings
def name_of_some_hstore_key
return (super == 'true') if %w{true false}.include? super
super
end
What’s it useful for?
Hstore is really useful for saving attributes on models.
If you store settings for your users, you’d typically do this in a separate model (or on the User model). Each setting would be an additional column. Instead of adding columns each time you want to create a setting, you could instead use a single HStore column. It’s much more flexible and doesn’t require migrations each time we want to store something new.