ActiveRecord without Rails
I needed a quick and simple database to use with a plain old ruby project, taking ActiveRecord on it's own is pretty easy.
I was building a test pack that needed to be able to pick a sample from one of several 100K samples to match an external stub. The stubs published their data in a huge JSON file and I needed to select one randomly based of a variable set of criteria. ActiveRecord to the rescue.
First thing to do, add ActiveRecord to the Gemfile and, since I'm going to use sqlite, I'll add that too. I'll also add rake because I want to use rake tasks to manage bringing the database up / down.
# Gemfile
gem 'activerecord', '~> 6.0'
gem 'sqlite3', '~> 1.4'
gem 'rake', '~> 13.0'
... and then require it in my code
require 'active_record'
I need to configure ActiveRecord to tell it which database driver to use and where the database is. I could hardcode a hash, but let's have a YAML file to hold that config just in case I change my mind about which database to use later.
# config/database.yml
---
adapter: sqlite3
database: db/identities.db
Now I want to create a Rakefile with tasks for creation, migration etc.
# Rakefile
require "bundler/setup"
Bundler.require
require "json"
require "yaml"
namespace :db do
db_config = YAML::load(File.open("config/database.yml"))
desc "Create the database"
task :create do
ActiveRecord::Base.establish_connection(db_config)
puts "Database created"
end
desc "Migrate the database"
task :migrate => :create do
ActiveRecord::Base.establish_connection(db_config)
ActiveRecord::MigrationContext.new("db/migrate/", ActiveRecord::SchemaMigration).migrate
puts "Database migrated"
end
desc "Drop the database"
task :drop do
File.delete(db_config["database"]) if File.exist?(db_config["database"])
puts "Database deleted"
end
desc "Reset the database"
task reset: [:drop, :create, :migrate]
desc "Create a db/schema.rb file"
task :schema do
ActiveRecord::Base.establish_connection(db_config)
require "active_record/schema_dumper"
filename = "db/schema.rb"
File.open(filename, "w:utf-8") do |file|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
end
puts 'Schema dumped'
end
desc 'Populate the database'
task :seed => :migrate do
ActiveRecord::Base.establish_connection(db_config)
load 'db/seed.rb' if File.exist?('db/seed.rb')
end
end
namespace :g do
desc "Generate migration"
task :migration do
name = ARGV[1] || raise("Specify name: rake g:migration your_migration")
timestamp = Time.now.strftime("%Y%m%d%H%M%S")
path = File.expand_path("../db/migrate/#{timestamp}_#{name}.rb", __FILE__)
migration_class = name.split("_").map(&:capitalize).join
File.open(path, "w") do |file|
file.write <<~EOF
class #{migration_class} < ActiveRecord::Migration[6.0]
def change
end
end
EOF
end
puts "Migration #{path} created"
abort # needed stop other tasks
end
end
Getting the migrations to work was a bit tricky. The way they work has changed quite a bit between major versions of ActiveRecord. The above is good for v6.
This Rakefile expects a db
directory, just like rails. Migrations will live in db/migrate
and there's a very basic generator included. After a bundle install
I can run bundle exec rake g:migration books
and a migration template will be autogenerated.
Let's create a table.
# db/migrate/20201022121743_books.rb
class Books < ActiveRecord::Migration[6.0]
def change
create_table :books do |t|
t.string :title
t.string :author
end
end
end
and run a bundle exec db:migrate
to create the table.
Now I need some model classes. I put these in a models
folder and also create a parent class to follow the Rails pattern.
# models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
# models/book.rb
class Book < ApplicationRecord
end
I make sure my source does a require_relative
on the two source files above and I can then use all of the power of ActiveRecord, including using scopes, magic finders etc.
Member discussion