Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run migrations/snippets in Rails console #129

Merged
merged 1 commit into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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