Rails + Storing User Settings — 2016 Edition
Using Postgres 9.4+, Storext & jsonb with Rails
--
I wrote about this problem back in 2013. And conveniently, tech has gotten much better since then. So, it is time for an update 😀.
JSONB + Storext is currently my favorite method for storing user settings in a Rails app. It’s super flexible, like NoSQL, but behaves exactly like the standard Rails attributes that you already know and love.
(if you want to know more about why I think this is best, skip to the end, I explain there)
How to do it
What you’ll need
- Postgres 9.4+ (is possible with lower versions, if you use hstore)
- Rails 4.x+
1. Add the column
First, you’ll need to add a new jsonb column to your users table. This is where you’ll store all of your user’s settings. We use JSON so that we can fit all of our settings in a single column.
# db/migrate/xxx_add_settings_to_user.rb
class AddSettingsToUser < ActiveRecord::Migration
def change
add_column :users, :settings, :jsonb, null: false, default: ‘{}’
end
end
Alternative column naming ideas: preferences, notification_settings, email_settings
Remember to run the migration. rake db:migrate
2. Install Storext
Your settings are stored as json in Postgres, but we want them to act like regular ActiveRecord columns. Storext sits on top of ActiveRecord::Store and adds some nice extra features (such as type coercion) to your json values.
Add gem 'storext' in your Gemfile and run bundle install.
3. Add your settings
Now the fun part. Defining your settings. These are done with Storext’s store_attributes method.
class User < ActiveRecord::Base
include Storext.model # "settings" matches what we named the database column
store_attributes :settings do
subscribed_to_newsletter Boolean, default: false
time_zone String
theme String, default: 'dark'
send_highlights_browser_push Boolean, default: true
send_mention_email Boolean, default: true
end
end
All of your standard Rails validations (except for uniqueness) will work.
4. You’re done!
You’ll now be able to use your new settings just like you would any other ActiveRecord attribute. 🌟
More info
I love this solution because it’s super flexible. You can add and remove settings easily without tedious (or even downtime) migrations. They are also indexable and queryable if needed.
When I stumbled upon Storext, I got really excited. It gives us some really nice features, such as default values and type coercion. Coercion comes in incredibly useful for managing user settings. If a Rails form submits the value “1” for a checkbox instead of a true, Storext will store it as a boolean as expected. This behavior matches what Rails does internally when setting normal attributes.
Why not a separate table?
User settings tend to become a core piece of every application. If they are retrieved on every request, you’re now making an extra query with each request. Using a separate table makes sense when you have to have individual columns for each setting. But since we’re using jsonb, that’s not an issue. It’s just a single column.
Why jsonb instead of hstore?
Hstore is only for storing text. If you’re going to store boolean or integer values, you start having to coerce the values on read. This works, but is a little messy. The boolean/integer value you see in Rails is different than what’s stored in the database. If you connect this database to a different application that’s missing that application logic… well, you have to write the coercion again in that other application.
Note: If you’re going to be querying off of your settings values, be sure to add a gin index.
For more information, I recommend reading “Hstore vs. JSON — Which to Use in Postgres” by Craig Kerstiens.
If you found this helpful, you can 💥join my email list here💥. I send out an email each time I publish something newIf you have a question, respond to this post or reach out on Twitter @mscccc.