Skip to content

Instantly share code, notes, and snippets.

@schickling
Last active November 5, 2024 09:14
Show Gist options
  • Save schickling/6762581 to your computer and use it in GitHub Desktop.
Save schickling/6762581 to your computer and use it in GitHub Desktop.
Activerecord without Rails
# config/database.yml
host: 'localhost'
adapter: 'postgresql'
encoding: utf-8
database: 'test'
require "active_record"
namespace :db do
db_config = YAML::load(File.open('config/database.yml'))
db_config_admin = db_config.merge({'database' => 'postgres', 'schema_search_path' => 'public'})
desc "Create the database"
task :create do
ActiveRecord::Base.establish_connection(db_config_admin)
ActiveRecord::Base.connection.create_database(db_config["database"])
puts "Database created."
end
desc "Migrate the database"
task :migrate do
ActiveRecord::Base.establish_connection(db_config)
ActiveRecord::Migrator.migrate("db/migrate/")
Rake::Task["db:schema"].invoke
puts "Database migrated."
end
desc "Drop the database"
task :drop do
ActiveRecord::Base.establish_connection(db_config_admin)
ActiveRecord::Base.connection.drop_database(db_config["database"])
puts "Database deleted."
end
desc "Reset the database"
task :reset => [:drop, :create, :migrate]
desc 'Create a db/schema.rb file that is portable against any DB supported by AR'
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
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
def self.up
end
def self.down
end
end
EOF
end
puts "Migration #{path} created"
abort # needed stop other tasks
end
end

Usage

$ rake db:create # create the db
$ rake db:migrate # run migrations
$ rake db:drop # delete the db
$ rake db:reset # combination of the upper three
$ rake db:schema # creates a schema file of the current database
$ rake g:migration your_migration # generates a new migration file
@jdblack
Copy link

jdblack commented Jan 2, 2017

I made a slightly larger version of this in https://github.com/jdblack/active_record_example . It includes a couple trivial modules and a small script to show how to use Schickling's work.

@gaotongfei
Copy link

great gist! thanks for your work.

@kenta-s
Copy link

kenta-s commented May 21, 2018

if you use activerecord 5.2, you may run into an error below

$ rake db:migrate
rake aborted!
NoMethodError: undefined method `migrate' for ActiveRecord::Migrator:Class

you should replace

ActiveRecord::Migrator.migrate("db/migrate/")

with

ActiveRecord::MigrationContext.new("db/migrate/").migrate

https://gist.github.com/schickling/6762581#file-rakefile-L18

@Agowan
Copy link

Agowan commented May 30, 2018

If rollback is needed. (Based on activerecord 5.2)

  desc "Migrate the database"
  task :rollback do
    ActiveRecord::Base.establish_connection(db_config)
    ActiveRecord::MigrationContext.new("db/migrate/").rollback
    Rake::Task["db:schema"].invoke
    puts "Last migration has been reverted."
  end

@joehorsnell
Copy link

joehorsnell commented Aug 22, 2018

A few suggestions, as pointed out by my colleague @owst:

  • The generated migration should have instance up/down methods (or even a single change method), rather than singleton class methods
  • Calling abort (to prevent the migration name argument from being run as a Rake task) causes the process to exit non-zero, indicating an error, which is not the case, so it should just be exit
  • It's simpler to just use File.write with a string, rather than the block form, ie:
  File.write(path, <<~MIGRATION_BODY)
    # contents of HEREDOC
  MIGRATION_BODY

Edit: Obviously I've used the Ruby 2.3+ squiggly HEREDOC there, but no real need to.

@ammancilla
Copy link

ammancilla commented Sep 4, 2018

Rollback (activerecord 5.1)

  desc 'Rollback the database'
  task :rollback, [:steps] do |_task, args|
    steps = (args[:steps] || 1).to_i

    ActiveRecord::Base.establish_connection(DATABASE_CONFIG)
    ActiveRecord::Migrator.rollback(['db/migrate'], steps)

    Rake::Task['db:schema'].invoke
  end

@FranklinYu
Copy link

@vkill
Copy link

vkill commented Dec 22, 2018

Maybe The DatabaseTasks is the best plan. Example

@euclid1990
Copy link

euclid1990 commented Jan 4, 2019

I have made minimal example/setup using Rails migration and active record outside Rails
https://github.com/euclid1990/rails-migration
(Support Rails >= 5.2)
🔢

@captainhusaynpenguin
Copy link

anybody interested to update this for ActiveRecord 6.0.1?

currently the error I'm getting is: NoMethodError: undefined method migrate' for ActiveRecord::Migrator:Class`

@thegene
Copy link

thegene commented Dec 30, 2019

@captainhusaynpinguin yeah it's a little more complicated. you need an instance of ActiveRecord::MigrationContext which needs to be initialized with the path to the migration directory and ActiveRecord::SchemaMigration which will need to have its table created. I would up getting it to work using something like this:

  def schema_migrations
    ActiveRecord::SchemaMigration.tap do |sm|
      sm.create_table
    end
  end

  def migration_context
    ActiveRecord::MigrationContext.new(migration_directory, schema_migrations)
  end

  def migrate
    migration_context.migrate
  end

You could also do the create_table separately before, but I found this to be a little nicer to work with.

@Rhoxio
Copy link

Rhoxio commented Mar 19, 2020

Most of this works in the newest version of ActiveRecord, but a few things have to be in for the db:migrate task to work without silently failing. I am using ActiveRecord outside of Rails in this context.

ActiveRecord::Migrator.migrate("db/migrate/") should be replaced with ActiveRecord::Tasks::DatabaseTasks.migrate assuming you are wanting to use the ActiveRecord default directory ('db/migrate'). This fixes NoMethodError: undefined method migrate' for ActiveRecord::Migrator:Class without having to customize anything. It was failing silently before, but this seems to have solved the issue on my end.

I didn't have to mess with MigrationContext at all.

Also, g:migration should have the version number attached to the generated model. You need the version number regardless of the version you are actually using (but it defaults to 4.2). Using activerecord (6.0.2.1) for example...
Before: class #{migration_class} < ActiveRecord::Migration
After: class #{migration_class} < ActiveRecord::Migration[6.0]

https://gist.github.com/Rhoxio/ee9a855088c53d447f2eb888bd9d09a4 if you need a static reference.

@jovanmaric
Copy link

jovanmaric commented Mar 27, 2020

The following worked for me when using a db/migrate directory like in rails. ActiveRecord::Tasks::DatabaseTasks is an option too, but the default values have to be changed as it defaults to requiring the Rails object.

def establish_connection
  ActiveRecord::Base.establish_connection(
    {
      adapter: 'postgresql',
      encoding: 'unicode',
      host: 'localhost',
      port: 5432,
      pool: 10,
      username: 'postgres',
      password: 'postgres',
      database: 'your_database_name'
    }
  )
end

def clear_all_connections
  ActiveRecord::Base.clear_all_connections!
end

def generate_schema
  ActiveRecord::Base.connection_pool.with_connection do |connection|
    File.open('db/schema.rb', 'w') do |file|
      ActiveRecord::SchemaDumper.dump(connection, file)
    end
  end
end

def migrate_db
  establish_connection

  ActiveRecord::Base.connection_pool.with_connection do |connection|
    connection.migration_context.migrate
  end

  generate_schema

  clear_all_connections
end

@diegodurs
Copy link

diegodurs commented Jul 15, 2020

This two fixes made it for me with active record 6.0:

namespace :db do
  # ...
 desc "Migrate the database"
  task :migrate do
    ActiveRecord::Base.establish_connection(db_config)
    ActiveRecord::Base.connection.migration_context.migrate

    Rake::Task["db:schema"].invoke
    puts "Database migrated."
  end

  desc "Rollback the last migration"
  task :rollback do
    step = ENV["STEP"] ? ENV["STEP"].to_i : 1

    ActiveRecord::Base.establish_connection(db_config)
    ActiveRecord::Base.connection.migration_context.rollback(step)

    Rake::Task["db:schema"].invoke
    puts "Database rollback."
  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 self.up
  end
  def self.down
  end
end
      EOF
    end

    puts "Migration #{path} created"
    abort # needed stop other tasks
  end
end

@lain0
Copy link

lain0 commented Apr 3, 2021

is it better to load Rake tasks instead? like in this gist:

require 'bundler/setup'

require 'active_record'

include ActiveRecord::Tasks

db_dir = File.expand_path('../db', __FILE__)
config_dir = File.expand_path('../config', __FILE__)

DatabaseTasks.env = 'development'
DatabaseTasks.db_dir = db_dir
DatabaseTasks.database_configuration = YAML.load(File.read(File.join(config_dir, 'database.yml')))
DatabaseTasks.migrations_paths = File.join(db_dir, 'migrate')

task :environment do
  ActiveRecord::Base.establish_connection(
    {
      adapter: 'postgresql',
      encoding: 'unicode',
      host: 'localhost',
      port: 5432,
      pool: 10,
      username: 'postgres',
      password: 'postgres',
      database: 'your_database_name'
    }
  )
  # require_relative './config/environment'
  # ActiveRecord::Base.configurations = DatabaseTasks.database_configuration
  # ActiveRecord::Base.establish_connection DatabaseTasks.env
end

load 'active_record/railties/databases.rake'

@Shigawire
Copy link

@Iaian0 came here to confirm. Loading the default tasks from ActiveRecord works just fine:

load 'active_record/railties/databases.rake'

@mwitkowskiaudi
Copy link

awesome...thank you for this 👍

@krisleech
Copy link

It seems to need a Rails constant with ActiveRecord 7.x:

rake db:create
-- create_table(:issues_issues, {:id=>:uuid, :force=>true})
   -> 0.0034s
uninitialized constant primary::Rails

        @root ||= Rails.root
                  ^^^^^

@lain0
Copy link

lain0 commented Sep 2, 2024

With rails.7.2.1 (activerecord 7.2.0) it works like as with rails6 just fine

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment