hanami-cli 2.1.1 → 2.2.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +29 -13
  3. data/.rubocop.yml +2 -0
  4. data/CHANGELOG.md +28 -0
  5. data/Gemfile +6 -1
  6. data/README.md +13 -7
  7. data/docker-compose.yml +14 -0
  8. data/hanami-cli.gemspec +2 -2
  9. data/lib/hanami/cli/command.rb +2 -2
  10. data/lib/hanami/cli/commands/app/command.rb +2 -16
  11. data/lib/hanami/cli/commands/app/db/command.rb +148 -0
  12. data/lib/hanami/cli/commands/app/db/create.rb +21 -11
  13. data/lib/hanami/cli/commands/app/db/drop.rb +21 -10
  14. data/lib/hanami/cli/commands/app/db/migrate.rb +50 -12
  15. data/lib/hanami/cli/commands/app/db/prepare.rb +68 -0
  16. data/lib/hanami/cli/commands/app/db/seed.rb +22 -21
  17. data/lib/hanami/cli/commands/app/db/structure/dump.rb +32 -7
  18. data/lib/hanami/cli/commands/app/db/structure/load.rb +54 -0
  19. data/lib/hanami/cli/commands/app/db/utils/database.rb +100 -75
  20. data/lib/hanami/cli/commands/app/db/utils/mysql.rb +59 -6
  21. data/lib/hanami/cli/commands/app/db/utils/postgres.rb +42 -21
  22. data/lib/hanami/cli/commands/app/db/utils/sqlite.rb +58 -10
  23. data/lib/hanami/cli/commands/app/db/version.rb +14 -8
  24. data/lib/hanami/cli/commands/app/generate/action.rb +4 -3
  25. data/lib/hanami/cli/commands/app/generate/command.rb +60 -0
  26. data/lib/hanami/cli/commands/app/generate/component.rb +49 -0
  27. data/lib/hanami/cli/commands/app/generate/migration.rb +47 -0
  28. data/lib/hanami/cli/commands/app/generate/operation.rb +26 -0
  29. data/lib/hanami/cli/commands/app/generate/part.rb +1 -1
  30. data/lib/hanami/cli/commands/app/generate/relation.rb +29 -0
  31. data/lib/hanami/cli/commands/app/generate/repo.rb +42 -0
  32. data/lib/hanami/cli/commands/app/generate/slice.rb +20 -3
  33. data/lib/hanami/cli/commands/app/generate/struct.rb +27 -0
  34. data/lib/hanami/cli/commands/app/install.rb +1 -1
  35. data/lib/hanami/cli/commands/app/middleware.rb +1 -1
  36. data/lib/hanami/cli/commands/app/server.rb +2 -2
  37. data/lib/hanami/cli/commands/app.rb +21 -2
  38. data/lib/hanami/cli/commands/gem/new.rb +78 -14
  39. data/lib/hanami/cli/errors.rb +28 -0
  40. data/lib/hanami/cli/files.rb +22 -0
  41. data/lib/hanami/cli/generators/app/action_context.rb +5 -13
  42. data/lib/hanami/cli/generators/app/component/component.erb +8 -0
  43. data/lib/hanami/cli/generators/app/component/slice_component.erb +8 -0
  44. data/lib/hanami/cli/generators/app/component.rb +61 -0
  45. data/lib/hanami/cli/generators/app/component_context.rb +82 -0
  46. data/lib/hanami/cli/generators/app/migration.rb +66 -0
  47. data/lib/hanami/cli/generators/app/operation.rb +49 -0
  48. data/lib/hanami/cli/generators/app/part_context.rb +5 -21
  49. data/lib/hanami/cli/generators/app/relation.rb +45 -0
  50. data/lib/hanami/cli/generators/app/repo.rb +41 -0
  51. data/lib/hanami/cli/generators/app/ruby_file_writer.rb +151 -0
  52. data/lib/hanami/cli/generators/app/slice/{entities.erb → operation.erb} +1 -3
  53. data/lib/hanami/cli/generators/app/slice/relation.erb +8 -0
  54. data/lib/hanami/cli/generators/app/slice/{slice.erb → repo.erb} +3 -1
  55. data/lib/hanami/cli/generators/app/slice/struct.erb +8 -0
  56. data/lib/hanami/cli/generators/app/slice.rb +14 -6
  57. data/lib/hanami/cli/generators/app/slice_context.rb +9 -2
  58. data/lib/hanami/cli/generators/app/struct.rb +40 -0
  59. data/lib/hanami/cli/generators/app/view_context.rb +4 -16
  60. data/lib/hanami/cli/generators/constants.rb +39 -0
  61. data/lib/hanami/cli/generators/context.rb +48 -0
  62. data/lib/hanami/cli/generators/gem/app/action.erb +3 -0
  63. data/lib/hanami/cli/generators/gem/app/env.erb +4 -0
  64. data/lib/hanami/cli/generators/gem/app/gemfile.erb +11 -0
  65. data/lib/hanami/cli/generators/gem/app/gitignore.erb +4 -1
  66. data/lib/hanami/cli/generators/gem/app/operation.erb +13 -0
  67. data/lib/hanami/cli/generators/gem/app/relation.erb +10 -0
  68. data/lib/hanami/cli/generators/gem/app/repo.erb +10 -0
  69. data/lib/hanami/cli/generators/gem/app/struct.erb +10 -0
  70. data/lib/hanami/cli/generators/gem/app.rb +19 -0
  71. data/lib/hanami/cli/ruby_file_generator.rb +123 -0
  72. data/lib/hanami/cli/version.rb +1 -1
  73. metadata +39 -17
  74. data/lib/hanami/cli/commands/app/db/create_migration.rb +0 -32
  75. data/lib/hanami/cli/commands/app/db/reset.rb +0 -28
  76. data/lib/hanami/cli/commands/app/db/rollback.rb +0 -81
  77. data/lib/hanami/cli/commands/app/db/sample_data.rb +0 -42
  78. data/lib/hanami/cli/commands/app/db/setup.rb +0 -26
  79. data/lib/hanami/cli/commands/app/db/utils/database_config.rb +0 -60
  80. data/lib/hanami/cli/generators/app/slice/repository.erb +0 -10
@@ -1,38 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../../app/command"
4
- require_relative "structure/dump"
5
-
6
3
  module Hanami
7
4
  module CLI
8
5
  module Commands
9
6
  module App
10
7
  module DB
11
8
  # @api private
12
- class Seed < App::Command
13
- FILE_PATH = "db/seeds.rb"
9
+ class Seed < DB::Command
10
+ SEEDS_PATH = "config/db/seeds.rb"
11
+ private_constant :SEEDS_PATH
14
12
 
15
13
  desc "Load seed data"
16
14
 
17
- # @api private
18
- def call(**)
19
- if has_file?
20
- measure "seed data loaded from #{FILE_PATH}" do
21
- load full_file_path
22
- end
23
- else
24
- out.puts "=> #{FILE_PATH} not found"
25
- end
26
- end
15
+ def call(app: false, slice: nil, **)
16
+ # We use `databases` below to discover the databases throughout the app and slices. It
17
+ # yields every database, so in a slice with multiple gateways, we'll see multiple
18
+ # databases for the slice.
19
+ #
20
+ # Since `db seed` is intended to run over whole slices only (not per-gateway), keep
21
+ # track of the seeded slices here, so we can avoid seeding a slice multiple times.
22
+ seeded_slices = []
27
23
 
28
- private
24
+ databases(app: app, slice: slice).each do |database|
25
+ next if seeded_slices.include?(database.slice)
29
26
 
30
- def full_file_path
31
- File.join(app.root, FILE_PATH)
32
- end
27
+ seeds_path = database.slice.root.join(SEEDS_PATH)
28
+ next unless seeds_path.file?
29
+
30
+ relative_seeds_path = seeds_path.relative_path_from(database.slice.app.root)
31
+ measure "seed data loaded from #{relative_seeds_path}" do
32
+ load seeds_path.to_s
33
+ end
33
34
 
34
- def has_file?
35
- File.exist?(full_file_path)
35
+ seeded_slices << database.slice
36
+ end
36
37
  end
37
38
  end
38
39
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../../../app/command"
4
-
5
3
  module Hanami
6
4
  module CLI
7
5
  module Commands
@@ -10,13 +8,40 @@ module Hanami
10
8
  # @api private
11
9
  module Structure
12
10
  # @api private
13
- class Dump < App::Command
14
- desc "Dumps database structure to db/structure.sql file"
11
+ class Dump < DB::Command
12
+ desc "Dumps database structure to config/db/structure.sql file"
13
+
14
+ option :gateway, required: false, desc: "Use database for gateway"
15
15
 
16
16
  # @api private
17
- def call(*)
18
- measure("#{database.name} structure dumped to db/structure.sql") do
19
- database.dump_command
17
+ def call(app: false, slice: nil, gateway: nil, command_exit: method(:exit), **)
18
+ exit_codes = []
19
+
20
+ databases(app: app, slice: slice, gateway: gateway).each do |database|
21
+ relative_structure_path = database.structure_file
22
+ .relative_path_from(database.slice.app.root)
23
+
24
+ measure("#{database.name} structure dumped to #{relative_structure_path}") do
25
+ catch :dump_failed do
26
+ result = database.exec_dump_command
27
+ exit_codes << result.exit_code if result.respond_to?(:exit_code)
28
+
29
+ unless result.successful?
30
+ out.puts result.err
31
+ throw :dump_failed, false
32
+ end
33
+
34
+ File.open(database.structure_file, "a") do |f|
35
+ f.puts "#{database.schema_migrations_sql_dump}\n"
36
+ end
37
+
38
+ true
39
+ end
40
+ end
41
+ end
42
+
43
+ exit_codes.each do |code|
44
+ break command_exit.(code) if code > 0
20
45
  end
21
46
  end
22
47
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module CLI
5
+ module Commands
6
+ module App
7
+ module DB
8
+ # @api private
9
+ module Structure
10
+ # @api private
11
+ class Load < DB::Command
12
+ STRUCTURE_PATH = File.join("config", "db", "structure.sql").freeze
13
+ private_constant :STRUCTURE_PATH
14
+
15
+ desc "Loads database from config/db/structure.sql file"
16
+
17
+ option :gateway, required: false, desc: "Use database for gateway"
18
+
19
+ # @api private
20
+ def call(app: false, slice: nil, gateway: nil, command_exit: method(:exit), **)
21
+ exit_codes = []
22
+
23
+ databases(app: app, slice: slice, gateway: gateway).each do |database|
24
+ next unless database.structure_file.exist?
25
+
26
+ relative_structure_path = database.structure_file
27
+ .relative_path_from(database.slice.app.root)
28
+
29
+ measure("#{database.name} structure loaded from #{relative_structure_path}") do
30
+ catch :load_failed do
31
+ result = database.exec_load_command
32
+ exit_codes << result.exit_code if result.respond_to?(:exit_code)
33
+
34
+ unless result.successful?
35
+ out.puts result.err
36
+ throw :load_failed, false
37
+ end
38
+
39
+ true
40
+ end
41
+ end
42
+ end
43
+
44
+ exit_codes.each do |code|
45
+ break command_exit.(code) if code > 0
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "database_config"
3
+ require "uri"
4
4
 
5
5
  module Hanami
6
6
  module CLI
@@ -9,15 +9,11 @@ module Hanami
9
9
  module DB
10
10
  module Utils
11
11
  # @api private
12
+ # @since 2.2.0
12
13
  class Database
13
- # @api private
14
- attr_reader :app
15
-
16
- # @api private
17
- attr_reader :config
18
-
19
- # @api private
20
- SCHEME_MAP = {
14
+ DATABASE_CLASS_RESOLVER = Hash.new { |_, key|
15
+ raise "#{key} is not a supported db scheme"
16
+ }.update(
21
17
  "sqlite" => -> {
22
18
  require_relative("sqlite")
23
19
  Sqlite
@@ -30,88 +26,83 @@ module Hanami
30
26
  require_relative("postgres")
31
27
  Postgres
32
28
  },
33
- "mysql" => -> {
29
+ "mysql2" => -> {
34
30
  require_relative("mysql")
35
31
  Mysql
36
32
  }
37
- }.freeze
38
-
39
- # @api private
40
- def self.[](app)
41
- database_url =
42
- if app.key?(:settings) && app[:settings].respond_to?(:database_url)
43
- app[:settings].database_url
44
- else
45
- ENV.fetch("DATABASE_URL")
46
- end
33
+ ).freeze
47
34
 
48
- config = DatabaseConfig.new(database_url)
35
+ def self.database_class(database_url)
36
+ database_scheme = URI(database_url).scheme
37
+ DATABASE_CLASS_RESOLVER[database_scheme].call
38
+ end
49
39
 
50
- resolver = SCHEME_MAP.fetch(config.db_type) do
51
- raise "#{config.db_type} is not a supported db scheme"
52
- end
40
+ def self.from_slice(slice:, system_call:)
41
+ provider = slice.container.providers[:db]
42
+ raise "No :db provider for #{slice}" unless provider
53
43
 
54
- klass = resolver.()
44
+ provider.source.database_urls.map { |(gateway_name, database_url)|
45
+ database = database_class(database_url).new(
46
+ slice: slice,
47
+ gateway_name: gateway_name,
48
+ system_call: system_call
49
+ )
55
50
 
56
- klass.new(app: app, config: config)
51
+ [gateway_name, database]
52
+ }.to_h
57
53
  end
58
54
 
59
- # @api private
60
- def initialize(app:, config:)
61
- @app = app
62
- @config = config
55
+ attr_reader :slice
56
+ attr_reader :gateway_name
57
+
58
+ attr_reader :system_call
59
+
60
+ def initialize(slice:, gateway_name:, system_call:)
61
+ @slice = slice
62
+ @gateway_name = gateway_name
63
+ @system_call = system_call
63
64
  end
64
65
 
65
- # @api private
66
- def create_command
67
- raise Hanami::CLI::NotImplementedError
66
+ def name
67
+ database_uri.path.sub(%r{^/}, "")
68
68
  end
69
69
 
70
- # @api private
71
- def drop_command
72
- raise Hanami::CLI::NotImplementedError
70
+ def database_url
71
+ slice.container.providers[:db].source.database_urls.fetch(gateway_name)
73
72
  end
74
73
 
75
- # @api private
76
- def dump_command
77
- raise Hanami::CLI::NotImplementedError
74
+ def database_uri
75
+ @database_uri ||= URI(database_url)
78
76
  end
79
77
 
80
- # @api private
81
- def load_command
82
- raise Hanami::CLI::NotImplementedError
78
+ def gateway
79
+ slice["db.config"].gateways[gateway_name]
83
80
  end
84
81
 
85
- # @api private
86
- def root_path
87
- app.root
82
+ def connection
83
+ gateway.connection
88
84
  end
89
85
 
90
- # @api private
91
- def rom_config
92
- @rom_config ||=
93
- begin
94
- app.prepare(:persistence)
95
- app.container["persistence.config"]
96
- end
86
+ def exec_create_command
87
+ raise Hanami::CLI::NotImplementedError
97
88
  end
98
89
 
99
- # @api private
100
- def name
101
- config.db_name
90
+ def exec_drop_command
91
+ raise Hanami::CLI::NotImplementedError
102
92
  end
103
93
 
104
- # @api private
105
- def gateway
106
- rom_config.gateways[:default]
94
+ def exists?
95
+ raise Hanami::CLI::NotImplementedError
107
96
  end
108
97
 
109
- # @api private
110
- def connection
111
- gateway.connection
98
+ def exec_dump_command
99
+ raise Hanami::CLI::NotImplementedError
100
+ end
101
+
102
+ def exec_load_command
103
+ raise Hanami::CLI::NotImplementedError
112
104
  end
113
105
 
114
- # @api private
115
106
  def run_migrations(**options)
116
107
  require "rom/sql"
117
108
  ROM::SQL.with_gateway(gateway) do
@@ -119,24 +110,19 @@ module Hanami
119
110
  end
120
111
  end
121
112
 
122
- # @api private
123
113
  def migrator
124
- @migrator ||=
125
- begin
126
- require "rom/sql"
127
- ROM::SQL::Migration::Migrator.new(connection, path: File.join(root_path, "db/migrate"))
128
- end
129
- end
114
+ @migrator ||= begin
115
+ slice.prepare :db
130
116
 
131
- # @api private
132
- def applied_migrations
133
- sequel_migrator.applied_migrations
117
+ require "rom/sql"
118
+ ROM::SQL::Migration::Migrator.new(connection, path: migrations_path)
119
+ end
134
120
  end
135
121
 
136
- private
137
-
138
122
  def sequel_migrator
139
123
  @sequel_migrator ||= begin
124
+ slice.prepare :db
125
+
140
126
  require "sequel"
141
127
  Sequel.extension :migration
142
128
 
@@ -147,8 +133,47 @@ module Hanami
147
133
  end
148
134
  end
149
135
 
136
+ def applied_migrations
137
+ sequel_migrator.applied_migrations
138
+ end
139
+
140
+ def db_config_path
141
+ slice.root.join("config", "db")
142
+ end
143
+
144
+ def db_config_dir?
145
+ db_config_path.directory?
146
+ end
147
+
150
148
  def migrations_path
151
- File.join(root_path, "db/migrate")
149
+ if gateway_name == :default
150
+ db_config_path.join("migrate")
151
+ else
152
+ db_config_path.join("#{gateway_name}_migrate")
153
+ end
154
+ end
155
+
156
+ def migrations_dir?
157
+ migrations_path.directory?
158
+ end
159
+
160
+ def structure_file
161
+ path = slice.root.join("config", "db")
162
+
163
+ if gateway_name == :default
164
+ path.join("structure.sql")
165
+ else
166
+ path.join("#{gateway_name}_structure.sql")
167
+ end
168
+ end
169
+
170
+ def schema_migrations_sql_dump
171
+ return unless migrations_dir?
172
+
173
+ sql = +"INSERT INTO schema_migrations (filename) VALUES\n"
174
+ sql << applied_migrations.map { |v| "('#{v}')" }.join(",\n")
175
+ sql << ";"
176
+ sql
152
177
  end
153
178
  end
154
179
  end
@@ -11,18 +11,71 @@ module Hanami
11
11
  # @api private
12
12
  class Mysql < Database
13
13
  # @api private
14
- def create_command
15
- raise Hanami::CLI::NotImplementedError
14
+ def exec_create_command
15
+ return true if exists?
16
+
17
+ exec_cli("mysql", %(-e "CREATE DATABASE #{escaped_name}"))
18
+ end
19
+
20
+ # @api private
21
+ # @since 2.2.0
22
+ def exec_drop_command
23
+ return true unless exists?
24
+
25
+ exec_cli("mysql", %(-e "DROP DATABASE #{escaped_name}"))
26
+ end
27
+
28
+ # @api private
29
+ # @since 2.2.0
30
+ def exists?
31
+ result = exec_cli("mysql", %(-e "SHOW DATABASES LIKE '#{name}'" --batch))
32
+
33
+ result.successful? && result.out != ""
16
34
  end
17
35
 
18
36
  # @api private
19
- def dump_command
20
- raise Hanami::CLI::NotImplementedError
37
+ # @since 2.2.0
38
+ def exec_dump_command
39
+ exec_cli(
40
+ "mysqldump",
41
+ "--no-data --routines --skip-comments --result-file=#{structure_file} #{escaped_name}"
42
+ )
21
43
  end
22
44
 
23
45
  # @api private
24
- def load_command
25
- raise Hanami::CLI::NotImplementedError
46
+ # @since 2.2.0
47
+ def exec_load_command
48
+ exec_cli(
49
+ "mysql",
50
+ %(--execute "SET FOREIGN_KEY_CHECKS = 0; SOURCE #{structure_file}; SET FOREIGN_KEY_CHECKS = 1" --database #{escaped_name})
51
+ )
52
+ end
53
+
54
+ private
55
+
56
+ def escaped_name
57
+ Shellwords.escape(name)
58
+ end
59
+
60
+ def exec_cli(cli_name, cli_args)
61
+ system_call.call(
62
+ "#{cli_name} #{cli_options} #{cli_args}",
63
+ env: cli_env_vars
64
+ )
65
+ end
66
+
67
+ def cli_options
68
+ [].tap { |opts|
69
+ opts << "--host=#{Shellwords.escape(database_uri.host)}" if database_uri.host
70
+ opts << "--port=#{Shellwords.escape(database_uri.port)}" if database_uri.port
71
+ opts << "--user=#{Shellwords.escape(database_uri.user)}" if database_uri.user
72
+ }.join(" ")
73
+ end
74
+
75
+ def cli_env_vars
76
+ @cli_env_vars ||= {}.tap do |vars|
77
+ vars["MYSQL_PWD"] = database_uri.password.to_s if database_uri.password
78
+ end
26
79
  end
27
80
  end
28
81
  end
@@ -11,50 +11,71 @@ module Hanami
11
11
  module DB
12
12
  module Utils
13
13
  # @api private
14
+ # @since 2.2.0
14
15
  class Postgres < Database
15
16
  # @api private
16
- def create_command
17
- existing_stdout, status = Open3.capture2(cli_env_vars, "psql -t -c '\\l #{escaped_name}'")
17
+ # @since 2.2.0
18
+ def exec_create_command
19
+ return true if exists?
18
20
 
19
- return true if status.success? && existing_stdout.include?(escaped_name)
20
-
21
- system(cli_env_vars, "createdb #{escaped_name}")
21
+ system_call.call("createdb #{escaped_name}", env: cli_env_vars)
22
22
  end
23
23
 
24
24
  # @api private
25
- def drop_command
26
- system(cli_env_vars, "dropdb #{escaped_name}")
25
+ # @since 2.2.0
26
+ def exec_drop_command
27
+ return true unless exists?
28
+
29
+ system_call.call("dropdb #{escaped_name}", env: cli_env_vars)
27
30
  end
28
31
 
29
32
  # @api private
30
- def dump_command
31
- system(cli_env_vars, "pg_dump --schema-only --no-owner #{escaped_name} > #{dump_file}")
33
+ # @since 2.2.0
34
+ def exists?
35
+ result = system_call.call("psql -t -A -c '\\list #{escaped_name}'", env: cli_env_vars)
36
+ result.successful? && result.out.include?("#{name}|") # start_with?
32
37
  end
33
38
 
34
39
  # @api private
35
- def load_command
36
- raise "Not Implemented Yet"
40
+ # @since 2.2.0
41
+ def exec_dump_command
42
+ system_call.call(
43
+ "pg_dump --schema-only --no-privileges --no-owner --file #{structure_file} #{escaped_name}",
44
+ env: cli_env_vars
45
+ )
37
46
  end
38
47
 
39
48
  # @api private
49
+ # @since 2.2.0
50
+ def exec_load_command
51
+ system_call.call(
52
+ "psql --set ON_ERROR_STOP=1 --quiet --no-psqlrc --output #{File::NULL} --file #{structure_file} #{escaped_name}",
53
+ env: cli_env_vars
54
+ )
55
+ end
56
+
57
+ def schema_migrations_sql_dump
58
+ search_path = slice["db.gateway"].connection
59
+ .fetch("SHOW search_path").to_a.first
60
+ .fetch(:search_path)
61
+
62
+ +"SET search_path TO #{search_path};\n\n" << super
63
+ end
64
+
65
+ private
66
+
40
67
  def escaped_name
41
68
  Shellwords.escape(name)
42
69
  end
43
70
 
44
- # @api private
45
71
  def cli_env_vars
46
72
  @cli_env_vars ||= {}.tap do |vars|
47
- vars["PGHOST"] = config.host.to_s
48
- vars["PGPORT"] = config.port.to_s if config.port
49
- vars["PGUSER"] = config.user.to_s if config.user
50
- vars["PGPASSWORD"] = config.pass.to_s if config.pass
73
+ vars["PGHOST"] = database_uri.host.to_s if database_uri.host
74
+ vars["PGPORT"] = database_uri.port.to_s if database_uri.port
75
+ vars["PGUSER"] = database_uri.user.to_s if database_uri.user
76
+ vars["PGPASSWORD"] = database_uri.password.to_s if database_uri.password
51
77
  end
52
78
  end
53
-
54
- # @api private
55
- def dump_file
56
- "#{root_path}/db/structure.sql"
57
- end
58
79
  end
59
80
  end
60
81
  end
@@ -9,32 +9,80 @@ module Hanami
9
9
  module DB
10
10
  module Utils
11
11
  # @api private
12
+ # @since 2.2.0
12
13
  class Sqlite < Database
13
14
  # @api private
14
- def create_command
15
- rom_config
16
- true
15
+ # @since 2.2.0
16
+ Failure = Struct.new(:err) do
17
+ def successful?
18
+ false
19
+ end
20
+
21
+ def exit_code
22
+ 1
23
+ end
24
+ end
25
+
26
+ # @api private
27
+ # @since 2.2.0
28
+ def exec_create_command
29
+ return true if exists?
30
+
31
+ FileUtils.mkdir_p(File.dirname(file_path))
32
+
33
+ system_call.call(%(sqlite3 #{file_path} "VACUUM;"))
17
34
  end
18
35
 
19
36
  # @api private
20
- def drop_command
21
- file_path.unlink
37
+ # @since 2.2.0
38
+ def exec_drop_command
39
+ begin
40
+ File.unlink(file_path) if exists?
41
+ rescue => e
42
+ # Mimic a system_call result
43
+ return Failure.new(e.message)
44
+ end
45
+
22
46
  true
23
47
  end
24
48
 
25
49
  # @api private
26
- def dump_command
27
- raise Hanami::CLI::NotImplementedError
50
+ # @since 2.2.0
51
+ def exists?
52
+ File.exist?(file_path)
53
+ end
54
+
55
+ # @api private
56
+ # @since 2.2.0
57
+ def exec_dump_command
58
+ system_call.call(%(sqlite3 #{file_path} ".schema --indent --nosys" > #{structure_file}))
28
59
  end
29
60
 
30
61
  # @api private
31
- def load_command
32
- raise Hanami::CLI::NotImplementedError
62
+ # @since 2.2.0
63
+ def exec_load_command
64
+ system_call.call("sqlite3 #{file_path} < #{structure_file}")
33
65
  end
34
66
 
35
67
  # @api private
68
+ # @since 2.2.0
69
+ def name
70
+ # Sequel expects sqlite:// URIs to operate the same as file:// URIs: 2 slashes for
71
+ # a relative path, 3 for an absolute path. In the case of 2 slashes, the first part
72
+ # of the path is considered by Ruby's `URI` as the `#host`.
73
+ @name ||= "#{database_uri.host}#{database_uri.path}"
74
+ end
75
+
76
+ private
77
+
36
78
  def file_path
37
- @file_path ||= Pathname("#{root_path}#{config.uri.path}").realpath
79
+ @file_path ||= begin
80
+ if File.absolute_path?(name)
81
+ name
82
+ else
83
+ slice.app.root.join(name).to_s
84
+ end
85
+ end
38
86
  end
39
87
  end
40
88
  end