basecamp / basecamp/activerecord-tenanted

Database task roadmap (epic)

Open
#222 5 comments 5 reactions 0 assignees View on GitHub
Dominant language
Ruby
Stars
617
Forks
29
PR merge metrics
No merged PRs in 30d

Description

This gem only implements the bare minimum database rake tasks to get started:

- db:migrate (implicit create)
- db:drop
- linking of everything to db:prepare

There are many more rake tasks that this gem will need to implement. This issue is meant to track the overall work, since I imagine the list will not get completed in a single PR (and may never get completed, let's be honest).

An open question for me is whether Rails could be presenting rake tasks so that gems can more easily override them. I've got a branch (TODO) that wraps up the tasks in this gem in PORO, and that's a pattern that I think might be more easily extensible if it was adopted upstream.

Here are the full set of database rake tasks implemented by Rails as of ~v8.0, along with some notes on how they're implemented and my thoughts on whether we need to implement it or not.

- [~] `db:_dump`
- forwards to "db:schema:dump"
- [~] `db:_dump:__dbname__`
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- forwards to "db:schema:dump:__dbname__"
- [ ] `db:abort_if_pending_migrations`
- uses DatabaseTasks.with_temporary_pool_for_each to iterate over db configs
- [ ] `db:abort_if_pending_migrations:__dbname__`
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- implemented with ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: Rails.env, name: name)
- [ ] `db:charset`
- only seems to work against "primary"
- error caused by nil returned from configs_for(env_name: env_name, name: db_name)
- maybe we don't need to support this immediately?
- [ ] `db:check_protected_environments`
- loops over configs_for(env_name: environment)
- we probably do not want or need to check all of the tenant databases. maybe check just the first tenant?
- [ ] `db:collation`
- only seems to work against "primary"
- error caused by nil returned from configs_for(env_name: env_name, name: db_name)
- maybe we don't need to support this immediately?
- [ ] `db:create`
- forwards to ActiveRecord::Tasks::DatabaseTasks.create_current
- [ ] `db:create:all`
- forwards to ActiveRecord::Tasks::DatabaseTasks.create_all
- [ ] `db:create:__dbname__`
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- implemented with ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name)
- [ ] `db:drop`
- forwards to "db:drop:_unsafe"
- [ ] `db:drop:_unsafe`
- forwards to ActiveRecord::Tasks::DatabaseTasks.drop_current
- [ ] `db:drop:all`
- forwards to ActiveRecord::Tasks::DatabaseTasks.drop_all
- [ ] `db:drop:__dbname__`
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- implemented with ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name)
- [~] `db:encryption:init`
- orthogonal to tenanting
- [ ] `db:environment:set`
- uses ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool which raises NoTenantError if tenanted is primary
- [~] `db:fixtures:identify`
- orthogonal to tenanting
- [ ] `db:fixtures:load`
- I think the current behavior is the desired behavior: use ARTENANT (fallback to default tenant) as the target database
- [ ] `db:forward` with support for STEP
- **migration**
- calls ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool.migration_context.forward(step)
- [~] `db:load_config`
- orthogonal to tenanting
- [ ] `db:migrate` with support for VERSION and SCOPE
- **migration**
- calls ActiveRecord::Tasks::DatabaseTasks.migrate_all
- [ ] `db:migrate:down` with support for VERSION
- **migration**
- calls ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool.migration_context.run(:down, target_version)
- [ ] `db:migrate:down:__dbname__`
- **migration**
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- implemented with DatabaseTasks.with_temporary_pool_for_each(env: Rails.env, name: name)
- calls pool.migration_context.run(:down, ActiveRecord::Tasks::DatabaseTasks.target_version)
- [ ] `db:migrate:__dbname__`
- **migration**
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- implemented with ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: Rails.env, name: name)
- calls ActiveRecord::Tasks::DatabaseTasks.migrate
- [ ] `db:migrate:redo` with support for STEP and VERSION
- **migration**
- forwards to either:
- "db:migrate:down" + "db:migrate:up"
- "db:rollback" + "db:migrate"
- [ ] `db:migrate:redo:__dbname__`
- **migration**
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- forwards to either:
- "db:migrate:down:__dbname__" + "db:migrate:up:__dbname__"
- "db:rollback:__dbname__" + "db:migrate:__dbname__"
- [~] `db:migrate:reset`
- invokes "db:drop", "db:create", "db:schema:dump", "db:migrate"
- [ ] `db:migrate:status`
- implemented with ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each
- calls ActiveRecord::Tasks::DatabaseTasks.migrate_status
- [ ] `db:migrate:status:__dbname__`
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- implemented with ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: Rails.env, name: name)
- calls ActiveRecord::Tasks::DatabaseTasks.migrate_status
- [ ] `db:migrate:up` with support for VERSION
- **migration**
- calls ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool.migration_context.run(:up, target_version)
- [ ] `db:migrate:up:__dbname__`
- **migration**
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- implemented with DatabaseTasks.with_temporary_pool_for_each(env: Rails.env, name: name)
- calls pool.migration_context.run(:up, ActiveRecord::Tasks::DatabaseTasks.target_version)
- [ ] `db:prepare`
- forwards to ActiveRecord::Tasks::DatabaseTasks.prepare_all
- [ ] `db:purge` (see Known Issues below)
- forwards to ActiveRecord::Tasks::DatabaseTasks.purge_current
- [ ] `db:purge:all` (see Known Issues below)
- forwards to ActiveRecord::Tasks::DatabaseTasks.purge_all
- [ ] `db:reset`
- forwards to "db:drop", "db:create", "db:schema:dump", "db:migrate"
- [ ] `db:reset:all`
- forwards to "db:drop", "db:setup"
- [ ] `db:reset:__dbname__`
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- forwards to "db:drop:__dbname__", "db:setup:__dbname__"
- [ ] `db:rollback` with support for STEP
- **migration**
- calls ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool.migration_context.rollback(step)
- [ ] `db:rollback:__dbname__`
- **migration**
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- implemented with ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: Rails.env, name: name)
- calls pool.migration_context.rollback(step)
- [ ] `db:schema:cache:clear`
- iterates with ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each
- we can probably skip implementing this since the gem needs the schema cache to operate, but whatever, maybe it's useful to force a re-dump
- [ ] `db:schema:cache:dump`
- iterates with ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each
- we can probably skip implementing this since the gem automatically dumps the schema cache for tenanted databases
- [ ] `db:schema:dump`
- forwards to ActiveRecord::Tasks::DatabaseTasks.dump_all
- I think we can skip this? Check that the gem always dumps the schema.
- [ ] `db:schema:dump:__dbname__`
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- iterates with ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name)
- calls ActiveRecord::Tasks::DatabaseTasks.dump_schema
- I think we can skip this? Check that the gem always dumps the schema.
- [ ] `db:schema:load`
- forwards to ActiveRecord::Tasks::DatabaseTasks.load_schema_current(nil, ENV["SCHEMA"])
- [ ] `db:schema:load:__dbname__`
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- implemented with ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name)
- calls ActiveRecord::Tasks::DatabaseTasks.load_schema
- I think we can skip this since the schema is automatically applied at tenant creation time
- [ ] `db:seed`
- forwards to ActiveRecord::Tasks::DatabaseTasks.load_seed
- I think the current behavior is the desired behavior: use ARTENANT (fallback to default tenant) as the target database
- [ ] `db:seed:replant`
- forwards to :load_config, :truncate_all, :seed
- [ ] `db:setup`
- forwards to "db:create", :environment, "db:schema:load", :seed
- [ ] `db:setup:all`
- forwards to "db:create", :environment, "db:schema:load", :seed
- [ ] `db:setup:__dbname__`
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- forwards to "db:create:__dbname__", :environment, "db:schema:load:__dbname__", "db:seed"
- [ ] `db:test:load_schema`
- implemented with ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: "test")
- calls ActiveRecord::Tasks::DatabaseTasks.load_schema
- [ ] `db:test:load_schema:__dbname__`
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- implemented with ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: "test", name: name)
- calls ActiveRecord::Tasks::DatabaseTasks.load_schema
- [ ] `db:test:prepare`
- forwards to "db:test:load_schema"
- [ ] `db:test:prepare:__dbname__`
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- forwards to "db:test:load_schema:__dbname__"
- [ ] `db:test:purge`
- implemented with ActiveRecord::Base.configurations.configs_for(env_name: "test")
- calls ActiveRecord::Tasks::DatabaseTasks.purge(db_config)
- [ ] `db:test:purge:__dbname__`
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- implemented with ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: "test", name: name)
- calls ActiveRecord::Tasks::DatabaseTasks.purge(db_config)
- [ ] `db:truncate_all`
- forwards to ActiveRecord::Tasks::DatabaseTasks.truncate_all
- [ ] `db:version`
- implemented with ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: Rails.env)
- [ ] `db:version:__dbname__`
- created by ActiveRecord::Tasks::DatabaseTasks.for_each(databases)
- implemented with ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name)

Contributor guide

Open the contributing guide

Research direction

Start with the existing database rake task entry points listed in the issue and compare them with the corresponding ActiveRecord::Tasks::DatabaseTasks methods in Rails. The unchecked tasks span migrations, schemas, setup, testing, and tenant-specific variants, while several behaviors remain open questions. Done is not defined because this epic needs to be split into focused tasks with agreed scope.

Written by the indexing model from the issue text.

Assessment

Tech stack
rails, ruby
Domain
backend, databases, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.