Skip to content

Commit

Permalink
Run migrations/snippets in Rails console
Browse files Browse the repository at this point in the history
  • Loading branch information
m-darbinyan committed Feb 4, 2025
1 parent b05c8ec commit 7f39e83
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 1 deletion.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,48 @@ Run the task with custom paths:
rake actual_db_schema:diff_schema_with_migrations[path/to/custom_schema.rb, path/to/custom_migrations]
```

## Console Migrations

Sometimes, it's necessary to modify the database without creating migration files. This can be useful for fixing a corrupted schema, conducting experiments (such as adding and removing indexes), or quickly adjusting the schema in development. This gem allows you to run the same commands used in migrations directly in the Rails console.

By default, Console Migrations is disabled. You can enable it in two ways:

### 1. Using Environment Variable

Set the environment variable `ACTUAL_DB_SCHEMA_CONSOLE_MIGRATIONS_ENABLED` to `true`:

```sh
export ACTUAL_DB_SCHEMA_CONSOLE_MIGRATIONS_ENABLED=true
```

### 2. Using Initializer

Add the following line to your initializer file (`config/initializers/actual_db_schema.rb`):

```ruby
config.console_migrations_enabled = true
```

### Usage

Once enabled, you can run migration commands directly in the Rails console:

```ruby
# Create a new table
create_table :posts do |t|
t.string :title
end

# Add a column
add_column :users, :age, :integer

# Remove an index
remove_index :users, :email

# Rename a column
rename_column :users, :username, :handle
```

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
Expand Down
1 change: 1 addition & 0 deletions lib/actual_db_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
require_relative "actual_db_schema/patches/migration_context"
require_relative "actual_db_schema/git_hooks"
require_relative "actual_db_schema/multi_tenant"
require_relative "actual_db_schema/railtie"
require_relative "actual_db_schema/schema_diff"
require_relative "actual_db_schema/schema_parser"

Expand Down
4 changes: 3 additions & 1 deletion lib/actual_db_schema/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
module ActualDbSchema
# Manages the configuration settings for the gem.
class Configuration
attr_accessor :enabled, :auto_rollback_disabled, :ui_enabled, :git_hooks_enabled, :multi_tenant_schemas
attr_accessor :enabled, :auto_rollback_disabled, :ui_enabled, :git_hooks_enabled, :multi_tenant_schemas,
:console_migrations_enabled

def initialize
@enabled = Rails.env.development?
@auto_rollback_disabled = ENV["ACTUAL_DB_SCHEMA_AUTO_ROLLBACK_DISABLED"].present?
@ui_enabled = Rails.env.development? || ENV["ACTUAL_DB_SCHEMA_UI_ENABLED"].present?
@git_hooks_enabled = ENV["ACTUAL_DB_SCHEMA_GIT_HOOKS_ENABLED"].present?
@multi_tenant_schemas = nil
@console_migrations_enabled = ENV["ACTUAL_DB_SCHEMA_CONSOLE_MIGRATIONS_ENABLED"].present?
end

def [](key)
Expand Down
47 changes: 47 additions & 0 deletions lib/actual_db_schema/console_migrations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

module ActualDbSchema
# Provides methods for executing schema modification commands directly in the Rails console.
module ConsoleMigrations
extend self

SCHEMA_METHODS = %i[
create_table
create_join_table
drop_table
change_table
add_column
remove_column
change_column
change_column_null
change_column_default
rename_column
add_index
remove_index
rename_index
add_timestamps
remove_timestamps
reversible
add_reference
remove_reference
add_foreign_key
remove_foreign_key
].freeze

SCHEMA_METHODS.each do |method_name|
define_method(method_name) do |*args, **kwargs, &block|
if kwargs.any?
migration_instance.public_send(method_name, *args, **kwargs, &block)
else
migration_instance.public_send(method_name, *args, &block)
end
end
end

private

def migration_instance
@migration_instance ||= Class.new(ActiveRecord::Migration[ActiveRecord::Migration.current_version]) {}.new
end
end
end
15 changes: 15 additions & 0 deletions lib/actual_db_schema/railtie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module ActualDbSchema
# Integrates the ConsoleMigrations module into the Rails console.
class Railtie < ::Rails::Railtie
console do
require_relative "console_migrations"

if ActualDbSchema.config[:console_migrations_enabled]
TOPLEVEL_BINDING.receiver.extend(ActualDbSchema::ConsoleMigrations)
puts "[ActualDbSchema] ConsoleMigrations enabled. You can now use migration methods directly at the console."
end
end
end
end
4 changes: 4 additions & 0 deletions lib/generators/actual_db_schema/templates/actual_db_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@

# If your application leverages multiple schemas for multi-tenancy, define the active schemas.
# config.multi_tenant_schemas = -> { ["public", "tenant1", "tenant2"] }

# Enable console migrations
# config.console_migrations_enabled = true
config.console_migrations_enabled = ENV["ACTUAL_DB_SCHEMA_CONSOLE_MIGRATIONS_ENABLED"].present?
end
95 changes: 95 additions & 0 deletions test/rake_task_console_migrations_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# frozen_string_literal: true

require "test_helper"
require_relative "../lib/actual_db_schema/console_migrations"

describe "console migrations" do
let(:utils) { TestUtils.new }

before do
extend ActualDbSchema::ConsoleMigrations

utils.reset_database_yml(TestingState.db_config["primary"])
ActiveRecord::Base.configurations = { "test" => TestingState.db_config["primary"] }
ActiveRecord::Tasks::DatabaseTasks.database_configuration = { "test" => TestingState.db_config["primary"] }
ActiveRecord::Base.establish_connection(**TestingState.db_config["primary"])
utils.cleanup

utils.define_migration_file("20250124084321_create_users.rb", <<~RUBY)
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name
t.string :middle_name
t.timestamps
end
add_index :users, :name, name: "index_users_on_name", unique: true
end
end
RUBY
utils.run_migrations
end

after do
utils.define_migration_file("20250124084323_drop_users.rb", <<~RUBY)
class DropUsers < ActiveRecord::Migration[6.0]
def change
drop_table :users
end
end
RUBY
utils.run_migrations
end

it "adds a column to a table" do
add_column :users, :email, :string
assert ActiveRecord::Base.connection.column_exists?(:users, :email)
end

it "removes a column from a table" do
remove_column :users, :middle_name
refute ActiveRecord::Base.connection.column_exists?(:users, :middle_name)
end

it "creates and drops a table" do
refute ActiveRecord::Base.connection.table_exists?(:categories)
create_table :categories do |t|
t.string :title
t.timestamps
end
assert ActiveRecord::Base.connection.table_exists?(:categories)

drop_table :categories
refute ActiveRecord::Base.connection.table_exists?(:categories)
end

it "changes column type" do
change_column :users, :middle_name, :text
assert_equal :text, ActiveRecord::Base.connection.columns(:users).find { |c| c.name == "middle_name" }.type
end

it "renames a column" do
rename_column :users, :name, :full_name
assert ActiveRecord::Base.connection.column_exists?(:users, :full_name)
refute ActiveRecord::Base.connection.column_exists?(:users, :name)
end

it "adds and removes an index" do
add_index :users, :middle_name, name: "index_users_on_middle_name", unique: true
assert ActiveRecord::Base.connection.index_exists?(:users, :middle_name, name: "index_users_on_middle_name")

remove_index :users, name: "index_users_on_middle_name"
refute ActiveRecord::Base.connection.index_exists?(:users, :middle_name, name: "index_users_on_middle_name")
end

it "adds and removes timestamps" do
remove_timestamps :users
refute ActiveRecord::Base.connection.column_exists?(:users, :created_at)
refute ActiveRecord::Base.connection.column_exists?(:users, :updated_at)

add_timestamps :users
assert ActiveRecord::Base.connection.column_exists?(:users, :created_at)
assert ActiveRecord::Base.connection.column_exists?(:users, :updated_at)
end
end

0 comments on commit 7f39e83

Please sign in to comment.