Using Postgres, like, for Real
writting by Hsing-Hui, Ada student. Read the original post on her blog.
The vast majority of Rails apps we build are hosted on Heroku, which uses PostgreSQL to store database tables. Since starting a new project by running rails new
defaults to using SQLite3, the way kind of hacked around this problem was by simply using SQLite in development and testing, and postgreSQL in production, like so:
(in Gemfile)
group :development, :test do
gem "sqlite3"
end
group :production do
gem "pg"
end
This is clearly not ideal–it’s usually best practice to use the same kind of database in development as you will in production. So, to setup your Rails app to use postgres for real, simply require the gem ‘pg’ in your Gemfile without specifying a group. Then, in your config/database.yml
file, change the adapter and database lines like so:
development:
adapter: postgresql
database: appname_development
pool: 5
timeout: 5000
host: localhost
test:
adapter: postgresql
database: appname_test
pool: 5
timeout: 5000
host: localhost
You don’t need to do anything with the production database, since Heroku sets all that up for you. Once yourdatabase.yml
file is updated, run rake db:create
in your command line, migrate if necessary, and boom! Your app is now configured to postgreSQL.
Reminder: After you deploy your app to Heroku, don’t forget to run heroku run rake db:migrate
!
So now that I’m using PostgreSQL, what is it good for?
While postgreSQL lives in the world of relational databases, you can also get the benefits of a schema-less database through a nifty feature called HStore. Before jumping straight into a technical explanation of Hstore, let’s take a few steps back and look at how we usually work with model data in Rails.
Suppose you have a user model in your application, and you want to give a user the option of receiving emails from your application. In your schema, you might have a column named “Email Preference” set to a boolean. But suppose there are many different kinds of emails a user could receive, such as registration confirmation, or event updates, or new events. One way to do it using our traditional relational database schema is to have another model named “Preferences” that belongs to a user. In other words, you’d have something like this in your app/models/person.rb
and app/models/preference.rb
files:
class User < ActiveRecord::Base
has_one :preference
end
class Preference < ActiveRecord::Base
belongs_to :user
end
Then you’d have the following migrations:
class AddColumnsToUser < ActiveRecord::Migration
def change
add_column :users, :name, :string
add_column :users, :email, :string
end
end
class AddColumnsToPreferences < ActiveRecord::Migration
def change
add_column :preferences, :registration, :boolean
add_column :preferences, :new_event, :boolean
add_column :preferences, :user_id, :integer
end
end
So now, to get a Person object’s language preference, you could call .preference.language
on a person object. That’s all well and good, but suppose you want to add another field to a person’s preferences? That requires adding another migration, changing the schema, etc. etc…
With Hstore, however, you can eliminate the need for generating a whole other table associated to the User model. Basically, hstore allows you to store data in a table column as a hash. Better yet, you can just add key-value pairs dynamically, without having to plan them out in your database schema first. In other words, suppose one user only wants emails for new events. Then that user’s preferences would be stored as the hash { new_event: true }
. But suppose another user wants to receive emails for new events as well as promos. Then his/her preferences can be stored as the hash { new_event: true, promos: true }
.
Setting up Hstore
Assuming you’ve followed the steps above for setting up postgreSQL, setting up your app to use hstore is easy:
- In your
application.rb
file, add the following line:config.active_record.schema_format = :sql
- In your terminal, generate a migration the usual way:
rails g migration add_hstore
Then, in the migration file, enable the hstore extension:
class AddHstore < ActiveRecord::Migration def up execute 'CREATE EXTENSION hstore' end def down execute 'DROP EXTENSION hstore' end end
- Finally, add a column in your model that specifies hstore as the column type:
class AddHstoreColumnToUsers < ActiveRecord::Migration def change add_column :users, :preferences, :hstore end end
To access the keys in the preferences hash, you can set them through a method called store_accessor in your model:
class User < ActiveRecord::Base
store_accessor :preferences, :registration, :new_event, :event_update, :promos
end
This allows you to simply call any of the keys as you would an attribute on the user object. Easy!