ActiveRecord without Rails

By Robert Kaufman, February 24, 2015 14:53

It’s possible to use ActiveRecord is a standalone ORM library in a non-Rails Ruby project. It can be tricky to figure out, though, so this article aims to help.

As of this writing, the latest version of ActiveRecord is 4.2.0, so that is what I will be targeting. I can’t guarantee that everything will still work the same for any other version. You can browse the source code here:

https://github.com/rails/rails/tree/v4.2.0/activerecord

There are two steps to getting ActiveRecord set up. The first is configuring it to connect to your existing database, which is fairly easy. The second step is to get Rake tasks (including migrations) set up, which I found to be rather tricky.

I’ve created a simple Ruby project as a demo of what’s written here. Check it out here.

1. Add activerecord to your Gemfile (and run bundle install).

Here’s a sample Gemfile.

2. Configure ActiveRecord’s connection to your database.

For this step, I’ll follow Rails’ lead and make a file called config/database.yml, which will define settings for several different environments. Note that the settings in my project are specific to my personal computer, so you’ll need to adjust them.

Then I’ll make a file called lib/ar_without_rails/database.rb, which, when loaded, will read the configuration file, and connect ActiveRecord to the database based on the DB_ENV environment variable.

3. Create a model class, which inherits from ActiveRecord::Base.

Here's the simplest possible model. Now you’re all done, as long as you’re okay with creating your database and tables manually.

4. Set up rake tasks

Maybe if we just load the ActiveRecord databases.rake file, things will work. Here’s what that would look like:

include ActiveRecord::Tasks
DatabaseTasks.database_configuration = YAML.load_file('config/database.yml')
load 'active_record/railties/databases.rake'

When we try to run bundle exec rake db:create, we get an error, and we can see from the stack trace we can see that Ruby ran in to a mysterious undefined constant called Rails. I looked in to this, and found that a lot of the tasks defined in databases.rake could be run successfully outside of Rails, but the few that won’t work are critical. Furthermore, I don’t know of any way to “inherit” from this file and override just some of the tasks, so we’re stuck with basically copy-and-pasting it all in to our own project.

Here is an annotated version of a Rakefile that will work:

# Initialize bundler
require 'bundler/setup'
Bundler.require :default, :test

# Load project files
$: << File.dirname(__FILE__) + '/lib'
require 'ar_without_rails'

# ActiveRecord rake tasks
include ActiveRecord::Tasks

namespace :db do
  task :load_config do
    DatabaseTasks.database_configuration = YAML.load_file('config/database.yml')
    DatabaseTasks.env = ENV['DB_ENV'] || 'development'
    ActiveRecord::Base.configurations = DatabaseTasks.database_configuration
  end

  desc 'Create database for given DB_ENV (default development)'
  task create: :load_config do
    # ActiveRecord depends on this environment variable
    ENV['RAILS_ENV'] = DatabaseTasks.env
    DatabaseTasks.create_current DatabaseTasks.env
  end

The create and drop task reveals a bit of odd behavior. If the RAILS_ENV environment variable is not set, then the create_current method will create both test and development databases, regardless of what arguments we pass to it.

  desc 'Migrate database for given DB_ENV (default development)'
  task migrate: :load_config do
    config = DatabaseTasks.database_configuration
    env = DatabaseTasks.env
    ActiveRecord::Base.establish_connection config[env]

    DatabaseTasks.migrate
  end

  desc 'Drop, create, and migrate database for a given DB_ENV (default development)'
  task :reset => [:drop, :create, :migrate]
end

There we have it. You might notice that this Rakefile is incomplete. I only added the tasks I needed, but it should not be too difficult to adapt other tasks from databases.rake if you need them.