Skip to content

Commit

Permalink
Add db prepare command (#176)
Browse files Browse the repository at this point in the history
Add a `db prepare` command for the new db layer.

This command will invoke `db create` and `db structure load` (if the database did not already exist), and then `db migrate` and `db seed`.

Getting this to work and be well tested required some internal changes:

- Update the `db` commands that it calls to otherwise exit themselves using an injected `command_exit` proc, which defaults to `method(:exit)`. This then allows the `db prepare` command to inject an alternative proc that prevents those subcommands from exiting the whole process, allow it to catch the failures and handle them on its own.
- Allow `DB::Command#databases` to receive a real `slice:` object as part of its args, so that `db prepare` can pass this along directly to all its subcommands.
  • Loading branch information
timriley authored Jun 16, 2024
1 parent f3b7f22 commit fb0fea8
Show file tree
Hide file tree
Showing 15 changed files with 461 additions and 67 deletions.
2 changes: 2 additions & 0 deletions lib/hanami/cli/commands/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ def self.extended(base)
if Hanami.bundled?("hanami-db")
register "db" do |db|
db.register "create", DB::Create
db.register "drop", DB::Drop
db.register "migrate", DB::Migrate
db.register "structure dump", DB::Structure::Dump
db.register "structure load", DB::Structure::Load
db.register "seed", DB::Seed
db.register "prepare", DB::Prepare
db.register "version", DB::Version
end
end
Expand Down
16 changes: 14 additions & 2 deletions lib/hanami/cli/commands/app/db/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ def initialize(
@system_call = system_call
end

def run_command(klass, ...)
klass.new(
out: out,
inflector: fs,
fs: fs,
system_call: system_call,
).call(...)
end

private

def databases(app: false, slice: nil)
Expand All @@ -45,9 +54,12 @@ def database_for_app
end

def database_for_slice(slice)
slice = inflector.underscore(Shellwords.shellescape(slice)).to_sym
unless slice.is_a?(Class) && slice < Hanami::Slice
slice_name = inflector.underscore(Shellwords.shellescape(slice)).to_sym
slice = app.slices[slice_name]
end

build_database(app.slices[slice])
build_database(slice)
end

def all_databases
Expand Down
4 changes: 2 additions & 2 deletions lib/hanami/cli/commands/app/db/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module DB
class Create < DB::Command
desc "Create databases"

def call(app: false, slice: nil, **)
def call(app: false, slice: nil, command_exit: method(:exit), **)
exit_codes = []

databases(app: app, slice: slice).each do |database|
Expand All @@ -25,7 +25,7 @@ def call(app: false, slice: nil, **)
end

exit_codes.each do |code|
break exit code if code > 0
break command_exit.(code) if code > 0
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/hanami/cli/commands/app/db/migrate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ class Migrate < DB::Command
option :dump, required: false, type: :boolean, default: true,
desc: "Dump the database structure after migrating"

def call(target: nil, app: false, slice: nil, dump: true, **)
def call(target: nil, app: false, slice: nil, dump: true, command_exit: method(:exit), **)
databases(app: app, slice: slice).each do |database|
migrate_database(database, target: target)
end

run_command(Structure::Dump, app: app, slice: slice) if dump
run_command(Structure::Dump, app: app, slice: slice, command_exit: command_exit) if dump
end

private
Expand Down
42 changes: 42 additions & 0 deletions lib/hanami/cli/commands/app/db/prepare.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

module Hanami
module CLI
module Commands
module App
module DB
# @api private
class Prepare < DB::Command
desc "Prepare databases"

def call(app: false, slice: nil, **)
exit_codes = []

databases(app: app, slice: slice).each do |database|
command_exit = -> code { throw :command_exited, code }
command_args = {slice: database.slice, command_exit: command_exit}

exit_code = catch :command_exited do
unless database.exists?
run_command(DB::Create, **command_args)
run_command(DB::Structure::Load, **command_args)
end

run_command(DB::Migrate, **command_args)
run_command(DB::Seed, **command_args)
nil
end

exit_codes << exit_code if exit_code
end

exit_codes.each do |code|
break exit code if code > 0
end
end
end
end
end
end
end
end
26 changes: 0 additions & 26 deletions lib/hanami/cli/commands/app/db/setup.rb

This file was deleted.

4 changes: 2 additions & 2 deletions lib/hanami/cli/commands/app/db/structure/dump.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class Dump < DB::Command
desc "Dumps database structure to config/db/structure.sql file"

# @api private
def call(app: false, slice: nil, **)
def call(app: false, slice: nil, command_exit: method(:exit), **)
exit_codes = []

databases(app: app, slice: slice).each do |database|
Expand All @@ -39,7 +39,7 @@ def call(app: false, slice: nil, **)
end

exit_codes.each do |code|
break exit code if code > 0
break command_exit.(code) if code > 0
end
end
end
Expand Down
14 changes: 10 additions & 4 deletions lib/hanami/cli/commands/app/db/structure/load.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,21 @@ module DB
module Structure
# @api private
class Load < DB::Command
STRUCTURE_PATH = File.join("config", "db", "structure.sql").freeze
private_constant :STRUCTURE_PATH

desc "Loads database from config/db/structure.sql file"

# @api private
def call(app: false, slice: nil, **)
def call(app: false, slice: nil, command_exit: method(:exit), **)
# TODO: handle exit_codes here

databases(app: app, slice: slice).each do |database|
slice_root = database.slice.root.relative_path_from(database.slice.app.root)
structure_path = slice_root.join("config", "db", "structure.sql")
structure_path = database.slice.root.join(STRUCTURE_PATH)
next unless structure_path.exist?

measure("#{database.name} structure loaded from #{structure_path}") do
relative_structure_path = structure_path.relative_path_from(database.slice.app.root)
measure("#{database.name} structure loaded from #{relative_structure_path}") do
database.exec_load_command.tap do |result|
unless result.successful?
out.puts result.err
Expand Down
8 changes: 6 additions & 2 deletions lib/hanami/cli/commands/app/db/utils/database.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,15 @@ def connection
gateway.connection
end

def create_command
def exec_create_command
raise Hanami::CLI::NotImplementedError
end

def drop_command
def exec_drop_command
raise Hanami::CLI::NotImplementedError
end

def exists?
raise Hanami::CLI::NotImplementedError
end

Expand Down
2 changes: 1 addition & 1 deletion lib/hanami/cli/commands/app/db/utils/postgres.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def exec_drop_command
system_call.call("dropdb #{escaped_name}", env: cli_env_vars)
end

private def exists?
def exists?
result = system_call.call("psql -t -A -c '\\list #{escaped_name}'", env: cli_env_vars)
result.successful? && result.out.include?("#{name}|") # start_with?
end
Expand Down
17 changes: 17 additions & 0 deletions spec/support/matchers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module RSpec
module Support
module Matchers
def include_in_order(*strings)
re = Regexp.new(
strings.map { Regexp.escape(_1) }.join(".+"),
Regexp::MULTILINE | Regexp::EXTENDED
)
match(re)
end
end
end
end

RSpec.configure do |config|
config.include RSpec::Support::Matchers
end
16 changes: 8 additions & 8 deletions spec/unit/hanami/cli/commands/app/db/migrate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class Posts < Hanami::DB::Relation

expect(Hanami.app["relations.posts"].to_a).to eq []

expect(dump_command).to have_received(:call).with(app: false, slice: nil)
expect(dump_command).to have_received(:call).with(hash_including(app: false, slice: nil))
expect(dump_command).to have_received(:call).exactly(1).time
end

Expand Down Expand Up @@ -175,7 +175,7 @@ class Posts < Hanami::DB::Relation

expect(Admin::Slice["relations.posts"].to_a).to eq []

expect(dump_command).to have_received(:call).with(app: false, slice: "admin")
expect(dump_command).to have_received(:call).with(hash_including(app: false, slice: "admin"))
expect(dump_command).to have_received(:call).exactly(1).time
end
end
Expand Down Expand Up @@ -203,7 +203,7 @@ class Posts < Hanami::DB::Relation
end
RUBY

write "slices/main/config/db/migrate/20240602201330_create_users.rb", <<~RUBY
write "slices/main/config/db/migrate/20240602201330_create_comments.rb", <<~RUBY
ROM::SQL.migration do
change do
create_table :comments do
Expand Down Expand Up @@ -239,7 +239,7 @@ class Comments < Hanami::DB::Relation
expect(Hanami.app["relations.posts"].to_a).to eq []
expect(Main::Slice["relations.comments"].to_a).to eq []

expect(dump_command).to have_received(:call).with(app: false, slice: nil)
expect(dump_command).to have_received(:call).with(hash_including(app: false, slice: nil))
expect(dump_command).to have_received(:call).exactly(1).time
end

Expand All @@ -252,7 +252,7 @@ class Comments < Hanami::DB::Relation
expect(Hanami.app["relations.posts"].to_a).to eq []
expect { Main::Slice["relations.comments"].to_a }.to raise_error Sequel::Error

expect(dump_command).to have_received(:call).with(app: true, slice: nil)
expect(dump_command).to have_received(:call).with(hash_including(app: true, slice: nil))
expect(dump_command).to have_received(:call).exactly(1).time
end

Expand All @@ -265,7 +265,7 @@ class Comments < Hanami::DB::Relation
expect(Main::Slice["relations.comments"].to_a).to eq []
expect { Hanami.app["relations.posts"].to_a }.to raise_error Sequel::Error

expect(dump_command).to have_received(:call).with(app: false, slice: "main")
expect(dump_command).to have_received(:call).with(hash_including(app: false, slice: "main"))
expect(dump_command).to have_received(:call).exactly(1).time
end
end
Expand Down Expand Up @@ -329,7 +329,7 @@ class Comments < Hanami::DB::Relation
expect(Admin::Slice["relations.posts"].to_a).to eq []
expect(Main::Slice["relations.comments"].to_a).to eq []

expect(dump_command).to have_received(:call).with(app: false, slice: nil)
expect(dump_command).to have_received(:call).with(hash_including(app: false, slice: nil))
expect(dump_command).to have_received(:call).exactly(1).time
end

Expand All @@ -342,7 +342,7 @@ class Comments < Hanami::DB::Relation
expect(Admin::Slice["relations.posts"].to_a).to eq []
expect { Main::Slice["relations.comments"].to_a }.to raise_error Sequel::Error

expect(dump_command).to have_received(:call).with(app: false, slice: "admin")
expect(dump_command).to have_received(:call).with(hash_including(app: false, slice: "admin"))
expect(dump_command).to have_received(:call).exactly(1).time
end
end
Expand Down
Loading

0 comments on commit fb0fea8

Please sign in to comment.