hanami-cli 2.2.0.beta1 → 2.2.0.rc1

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +13 -2
  3. data/.rubocop.yml +2 -0
  4. data/CHANGELOG.md +38 -2
  5. data/Gemfile +3 -0
  6. data/README.md +2 -0
  7. data/docker-compose.yml +6 -1
  8. data/lib/hanami/cli/command.rb +1 -1
  9. data/lib/hanami/cli/commands/app/command.rb +9 -3
  10. data/lib/hanami/cli/commands/app/console.rb +1 -5
  11. data/lib/hanami/cli/commands/app/db/command.rb +139 -31
  12. data/lib/hanami/cli/commands/app/db/create.rb +6 -2
  13. data/lib/hanami/cli/commands/app/db/drop.rb +6 -2
  14. data/lib/hanami/cli/commands/app/db/migrate.rb +49 -8
  15. data/lib/hanami/cli/commands/app/db/prepare.rb +36 -8
  16. data/lib/hanami/cli/commands/app/db/seed.rb +17 -1
  17. data/lib/hanami/cli/commands/app/db/structure/dump.rb +11 -6
  18. data/lib/hanami/cli/commands/app/db/structure/load.rb +9 -5
  19. data/lib/hanami/cli/commands/app/db/utils/database.rb +44 -14
  20. data/lib/hanami/cli/commands/app/db/utils/mysql.rb +57 -4
  21. data/lib/hanami/cli/commands/app/db/utils/postgres.rb +13 -8
  22. data/lib/hanami/cli/commands/app/db/version.rb +6 -3
  23. data/lib/hanami/cli/commands/app/generate/action.rb +12 -2
  24. data/lib/hanami/cli/commands/app/generate/command.rb +13 -2
  25. data/lib/hanami/cli/commands/app/generate/migration.rb +20 -0
  26. data/lib/hanami/cli/commands/app/generate/relation.rb +0 -6
  27. data/lib/hanami/cli/commands/app/generate/repo.rb +3 -7
  28. data/lib/hanami/cli/commands/app/generate/slice.rb +16 -3
  29. data/lib/hanami/cli/commands/app.rb +21 -9
  30. data/lib/hanami/cli/files.rb +22 -0
  31. data/lib/hanami/cli/generators/app/action.rb +27 -15
  32. data/lib/hanami/cli/generators/app/migration.rb +8 -9
  33. data/lib/hanami/cli/generators/app/operation.rb +5 -4
  34. data/lib/hanami/cli/generators/app/relation.rb +5 -4
  35. data/lib/hanami/cli/generators/app/repo.rb +5 -4
  36. data/lib/hanami/cli/generators/app/ruby_file_writer.rb +39 -37
  37. data/lib/hanami/cli/generators/app/slice.rb +5 -3
  38. data/lib/hanami/cli/generators/app/slice_context.rb +6 -0
  39. data/lib/hanami/cli/generators/app/struct.rb +5 -4
  40. data/lib/hanami/cli/generators/context.rb +4 -4
  41. data/lib/hanami/cli/generators/gem/app/gitignore.erb +3 -0
  42. data/lib/hanami/cli/generators/gem/app/operation.erb +0 -4
  43. data/lib/hanami/cli/generators/gem/app/seeds.erb +15 -0
  44. data/lib/hanami/cli/generators/gem/app.rb +3 -2
  45. data/lib/hanami/cli/repl/irb.rb +2 -2
  46. data/lib/hanami/cli/version.rb +1 -1
  47. metadata +4 -4
  48. data/lib/hanami/cli/generators/app/slice/entities.erb +0 -9
@@ -11,13 +11,15 @@ module Hanami
11
11
  class Dump < DB::Command
12
12
  desc "Dumps database structure to config/db/structure.sql file"
13
13
 
14
+ option :gateway, required: false, desc: "Use database for gateway"
15
+
14
16
  # @api private
15
- def call(app: false, slice: nil, command_exit: method(:exit), **)
17
+ def call(app: false, slice: nil, gateway: nil, command_exit: method(:exit), **)
16
18
  exit_codes = []
17
19
 
18
- databases(app: app, slice: slice).each do |database|
19
- structure_path = database.slice.root.join("config", "db", "structure.sql")
20
- relative_structure_path = structure_path.relative_path_from(database.slice.app.root)
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)
21
23
 
22
24
  measure("#{database.name} structure dumped to #{relative_structure_path}") do
23
25
  catch :dump_failed do
@@ -29,8 +31,11 @@ module Hanami
29
31
  throw :dump_failed, false
30
32
  end
31
33
 
32
- File.open(structure_path, "a") do |f|
33
- f.puts "#{database.schema_migrations_sql_dump}\n"
34
+ migrations_sql = database.schema_migrations_sql_dump
35
+ if migrations_sql
36
+ File.open(database.structure_file, "a") do |f|
37
+ f.puts "#{migrations_sql}\n"
38
+ end
34
39
  end
35
40
 
36
41
  true
@@ -14,15 +14,17 @@ module Hanami
14
14
 
15
15
  desc "Loads database from config/db/structure.sql file"
16
16
 
17
+ option :gateway, required: false, desc: "Use database for gateway"
18
+
17
19
  # @api private
18
- def call(app: false, slice: nil, command_exit: method(:exit), **)
20
+ def call(app: false, slice: nil, gateway: nil, command_exit: method(:exit), **)
19
21
  exit_codes = []
20
22
 
21
- databases(app: app, slice: slice).each do |database|
22
- structure_path = database.slice.root.join(STRUCTURE_PATH)
23
- next unless structure_path.exist?
23
+ databases(app: app, slice: slice, gateway: gateway).each do |database|
24
+ next unless database.structure_file.exist?
24
25
 
25
- relative_structure_path = structure_path.relative_path_from(database.slice.app.root)
26
+ relative_structure_path = database.structure_file
27
+ .relative_path_from(database.slice.app.root)
26
28
 
27
29
  measure("#{database.name} structure loaded from #{relative_structure_path}") do
28
30
  catch :load_failed do
@@ -42,6 +44,8 @@ module Hanami
42
44
  exit_codes.each do |code|
43
45
  break command_exit.(code) if code > 0
44
46
  end
47
+
48
+ re_run_development_command_in_test
45
49
  end
46
50
  end
47
51
  end
@@ -11,9 +11,6 @@ module Hanami
11
11
  # @api private
12
12
  # @since 2.2.0
13
13
  class Database
14
- MIGRATIONS_DIR = "config/db/migrate"
15
- private_constant :MIGRATIONS_DIR
16
-
17
14
  DATABASE_CLASS_RESOLVER = Hash.new { |_, key|
18
15
  raise "#{key} is not a supported db scheme"
19
16
  }.update(
@@ -29,27 +26,40 @@ module Hanami
29
26
  require_relative("postgres")
30
27
  Postgres
31
28
  },
32
- "mysql" => -> {
29
+ "mysql2" => -> {
33
30
  require_relative("mysql")
34
31
  Mysql
35
32
  }
36
33
  ).freeze
37
34
 
38
- def self.[](slice, system_call:)
35
+ def self.database_class(database_url)
36
+ database_scheme = URI(database_url).scheme
37
+ DATABASE_CLASS_RESOLVER[database_scheme].call
38
+ end
39
+
40
+ def self.from_slice(slice:, system_call:)
39
41
  provider = slice.container.providers[:db]
40
- raise "this is not a db slice" unless provider
42
+ raise "No :db provider for #{slice}" unless provider
41
43
 
42
- database_scheme = provider.source.database_url.then { URI(_1).scheme }
43
- database_class = DATABASE_CLASS_RESOLVER[database_scheme].call
44
- database_class.new(slice: slice, system_call: system_call)
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
+ )
50
+
51
+ [gateway_name, database]
52
+ }.to_h
45
53
  end
46
54
 
47
55
  attr_reader :slice
56
+ attr_reader :gateway_name
48
57
 
49
58
  attr_reader :system_call
50
59
 
51
- def initialize(slice:, system_call:)
60
+ def initialize(slice:, gateway_name:, system_call:)
52
61
  @slice = slice
62
+ @gateway_name = gateway_name
53
63
  @system_call = system_call
54
64
  end
55
65
 
@@ -58,7 +68,7 @@ module Hanami
58
68
  end
59
69
 
60
70
  def database_url
61
- slice.container.providers[:db].source.database_url
71
+ slice.container.providers[:db].source.database_urls.fetch(gateway_name)
62
72
  end
63
73
 
64
74
  def database_uri
@@ -66,7 +76,7 @@ module Hanami
66
76
  end
67
77
 
68
78
  def gateway
69
- slice["db.config"].gateways[:default]
79
+ slice["db.config"].gateways[gateway_name]
70
80
  end
71
81
 
72
82
  def connection
@@ -127,8 +137,20 @@ module Hanami
127
137
  sequel_migrator.applied_migrations
128
138
  end
129
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
+
130
148
  def migrations_path
131
- slice.root.join(MIGRATIONS_DIR)
149
+ if gateway_name == :default
150
+ db_config_path.join("migrate")
151
+ else
152
+ db_config_path.join("#{gateway_name}_migrate")
153
+ end
132
154
  end
133
155
 
134
156
  def migrations_dir?
@@ -136,10 +158,18 @@ module Hanami
136
158
  end
137
159
 
138
160
  def structure_file
139
- slice.root.join("config/db/structure.sql")
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
140
168
  end
141
169
 
142
170
  def schema_migrations_sql_dump
171
+ return unless migrations_dir?
172
+
143
173
  sql = +"INSERT INTO schema_migrations (filename) VALUES\n"
144
174
  sql << applied_migrations.map { |v| "('#{v}')" }.join(",\n")
145
175
  sql << ";"
@@ -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
37
+ # @since 2.2.0
19
38
  def exec_dump_command
20
- raise Hanami::CLI::NotImplementedError
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
46
+ # @since 2.2.0
24
47
  def exec_load_command
25
- raise Hanami::CLI::NotImplementedError
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
@@ -54,6 +54,19 @@ module Hanami
54
54
  )
55
55
  end
56
56
 
57
+ def schema_migrations_sql_dump
58
+ migrations_sql = super
59
+ return unless migrations_sql
60
+
61
+ search_path = gateway.connection
62
+ .fetch("SHOW search_path").to_a.first
63
+ .fetch(:search_path)
64
+
65
+ +"SET search_path TO #{search_path};\n\n" << migrations_sql
66
+ end
67
+
68
+ private
69
+
57
70
  def escaped_name
58
71
  Shellwords.escape(name)
59
72
  end
@@ -66,14 +79,6 @@ module Hanami
66
79
  vars["PGPASSWORD"] = database_uri.password.to_s if database_uri.password
67
80
  end
68
81
  end
69
-
70
- def schema_migrations_sql_dump
71
- search_path = slice["db.gateway"].connection
72
- .fetch("SHOW search_path").to_a.first
73
- .fetch(:search_path)
74
-
75
- +"SET search_path TO #{search_path};\n\n" << super
76
- end
77
82
  end
78
83
  end
79
84
  end
@@ -9,11 +9,14 @@ module Hanami
9
9
  class Version < DB::Command
10
10
  desc "Print schema version"
11
11
 
12
+ option :gateway, required: false, desc: "Use database for gateway"
13
+
12
14
  # @api private
13
- def call(app: false, slice: nil, **)
14
- databases(app: app, slice: slice).each do |database|
15
+ def call(app: false, slice: nil, gateway: nil, **)
16
+ databases(app: app, slice: slice, gateway: gateway).each do |database|
15
17
  unless database.migrations_dir?
16
- out.puts "=> Cannot find version for slice #{database.slice.slice_name.to_s.inspect}: missing config/db/migrate/ dir"
18
+ relative_migrations_path = database.migrations_path.relative_path_from(database.slice.app.root)
19
+ out.puts "=> Cannot find version for database #{database.name}: no migrations directory at #{relative_migrations_path}/"
17
20
  return
18
21
  end
19
22
 
@@ -25,6 +25,9 @@ module Hanami
25
25
  DEFAULT_SKIP_TESTS = false
26
26
  private_constant :DEFAULT_SKIP_TESTS
27
27
 
28
+ DEFAULT_SKIP_ROUTE = false
29
+ private_constant :DEFAULT_SKIP_ROUTE
30
+
28
31
  argument :name, required: true, desc: "Action name"
29
32
  option :url, required: false, type: :string, desc: "Action URL"
30
33
  option :http, required: false, type: :string, desc: "Action HTTP method"
@@ -41,6 +44,12 @@ module Hanami
41
44
  type: :flag,
42
45
  default: DEFAULT_SKIP_TESTS,
43
46
  desc: "Skip test generation"
47
+ option \
48
+ :skip_route,
49
+ required: false,
50
+ type: :flag,
51
+ default: DEFAULT_SKIP_ROUTE,
52
+ desc: "Skip route generation"
44
53
  option :slice, required: false, desc: "Slice name"
45
54
 
46
55
  # rubocop:disable Layout/LineLength
@@ -83,7 +92,8 @@ module Hanami
83
92
  http: nil,
84
93
  format: DEFAULT_FORMAT,
85
94
  skip_view: DEFAULT_SKIP_VIEW,
86
- skip_tests: DEFAULT_SKIP_TESTS, # rubocop:disable Lint/UnusedMethodArgument
95
+ skip_tests: DEFAULT_SKIP_TESTS, # rubocop:disable Lint/UnusedMethodArgument,
96
+ skip_route: DEFAULT_SKIP_ROUTE,
87
97
  slice: nil,
88
98
  context: nil,
89
99
  **
@@ -96,7 +106,7 @@ module Hanami
96
106
  raise InvalidActionNameError.new(name)
97
107
  end
98
108
 
99
- generator.call(app.namespace, controller, action, url, http, format, skip_view, slice, context: context)
109
+ generator.call(app.namespace, controller, action, url, http, format, skip_view, skip_route, slice, context: context)
100
110
  end
101
111
 
102
112
  # rubocop:enable Metrics/ParameterLists
@@ -38,8 +38,19 @@ module Hanami
38
38
  # @since 2.2.0
39
39
  # @api private
40
40
  def call(name:, slice: nil, **)
41
- normalized_slice = inflector.underscore(slice) if slice
42
- generator.call(app.namespace, name, normalized_slice)
41
+ if slice
42
+ generator.call(
43
+ key: name,
44
+ namespace: slice,
45
+ base_path: fs.join("slices", inflector.underscore(slice))
46
+ )
47
+ else
48
+ generator.call(
49
+ key: name,
50
+ namespace: app.namespace,
51
+ base_path: "app"
52
+ )
53
+ end
43
54
  end
44
55
  end
45
56
  end
@@ -9,16 +9,36 @@ module Hanami
9
9
  # @api private
10
10
  class Migration < Command
11
11
  argument :name, required: true, desc: "Migration name"
12
+ option :gateway, desc: "Generate migration for gateway"
12
13
 
13
14
  example [
14
15
  %(create_posts),
15
16
  %(add_published_at_to_posts),
16
17
  %(create_users --slice=admin),
18
+ %(create_comments --slice=admin --gateway=extra),
17
19
  ]
18
20
 
19
21
  def generator_class
20
22
  Generators::App::Migration
21
23
  end
24
+
25
+ def call(name:, slice: nil, gateway: nil)
26
+ if slice
27
+ generator.call(
28
+ key: name,
29
+ namespace: slice,
30
+ base_path: fs.join("slices", inflector.underscore(slice)),
31
+ gateway: gateway
32
+ )
33
+ else
34
+ generator.call(
35
+ key: name,
36
+ namespace: app.namespace,
37
+ base_path: "app",
38
+ gateway: gateway
39
+ )
40
+ end
41
+ end
22
42
  end
23
43
  end
24
44
  end
@@ -21,12 +21,6 @@ module Hanami
21
21
  def generator_class
22
22
  Generators::App::Relation
23
23
  end
24
-
25
- # @since 2.2.0
26
- # @api private
27
- def call(name:, slice: nil, **opts)
28
- super(name: name, slice: slice, **opts)
29
- end
30
24
  end
31
25
  end
32
26
  end
@@ -30,13 +30,9 @@ module Hanami
30
30
 
31
31
  # @since 2.2.0
32
32
  # @api private
33
- def call(name:, slice: nil, **opts)
34
- normalized_name = if name.end_with?("_repo")
35
- name
36
- else
37
- "#{inflector.singularize(name)}_repo"
38
- end
39
- super(name: normalized_name, slice: slice, **opts)
33
+ def call(name:, **opts)
34
+ name = "#{inflector.singularize(name)}_repo" unless name.end_with?("_repo")
35
+ super
40
36
  end
41
37
  end
42
38
  end
@@ -21,13 +21,25 @@ module Hanami
21
21
  SKIP_DB_DEFAULT = false
22
22
  private_constant :SKIP_DB_DEFAULT
23
23
 
24
+ # @since 2.2.0
25
+ # @api private
26
+ DEFAULT_SKIP_ROUTE = false
27
+ private_constant :DEFAULT_SKIP_ROUTE
28
+
24
29
  # @since 2.2.0
25
30
  # @api private
26
31
  option :skip_db,
27
- type: :boolean,
32
+ type: :flag,
28
33
  required: false,
29
34
  default: SKIP_DB_DEFAULT,
30
35
  desc: "Skip database"
36
+ # @since 2.2.0
37
+ # @api private
38
+ option :skip_route,
39
+ type: :flag,
40
+ required: false,
41
+ default: DEFAULT_SKIP_ROUTE,
42
+ desc: "Skip route generation"
31
43
 
32
44
  example [
33
45
  "admin # Admin slice (/admin URL prefix)",
@@ -50,7 +62,8 @@ module Hanami
50
62
  def call(
51
63
  name:,
52
64
  url: nil,
53
- skip_db: SKIP_DB_DEFAULT
65
+ skip_db: SKIP_DB_DEFAULT,
66
+ skip_route: DEFAULT_SKIP_ROUTE
54
67
  )
55
68
  require "hanami/setup"
56
69
 
@@ -58,7 +71,7 @@ module Hanami
58
71
  name = inflector.underscore(Shellwords.shellescape(name))
59
72
  url = sanitize_url_prefix(name, url)
60
73
 
61
- generator.call(app, name, url, skip_db: skip_db)
74
+ generator.call(app, name, url, skip_db: skip_db, skip_route: skip_route)
62
75
  end
63
76
 
64
77
  private
@@ -41,16 +41,28 @@ module Hanami
41
41
  end
42
42
 
43
43
  register "generate", aliases: ["g"] do |prefix|
44
- prefix.register "action", Generate::Action
45
- prefix.register "component", Generate::Component
46
- prefix.register "migration", Generate::Migration
47
- prefix.register "operation", Generate::Operation
48
- prefix.register "part", Generate::Part
49
- prefix.register "relation", Generate::Relation
50
- prefix.register "repo", Generate::Repo
51
44
  prefix.register "slice", Generate::Slice
52
- prefix.register "struct", Generate::Struct
53
- prefix.register "view", Generate::View
45
+ prefix.register "component", Generate::Component
46
+
47
+ if Hanami.bundled?("hanami-controller")
48
+ prefix.register "action", Generate::Action
49
+ end
50
+
51
+ if Hanami.bundled?("dry-operation")
52
+ prefix.register "operation", Generate::Operation
53
+ end
54
+
55
+ if Hanami.bundled?("hanami-view")
56
+ prefix.register "view", Generate::View
57
+ prefix.register "part", Generate::Part
58
+ end
59
+
60
+ if Hanami.bundled?("hanami-db")
61
+ prefix.register "migration", Generate::Migration
62
+ prefix.register "relation", Generate::Relation
63
+ prefix.register "repo", Generate::Repo
64
+ prefix.register "struct", Generate::Struct
65
+ end
54
66
  end
55
67
  end
56
68
  end
@@ -18,7 +18,11 @@ module Hanami
18
18
  # @api private
19
19
  def write(path, *content)
20
20
  already_exists = exist?(path)
21
+
21
22
  super
23
+
24
+ delete_keepfiles(path) unless already_exists
25
+
22
26
  if already_exists
23
27
  updated(path)
24
28
  else
@@ -46,6 +50,24 @@ module Hanami
46
50
 
47
51
  attr_reader :out
48
52
 
53
+ # Removes .keep files in any directories leading up to the given path.
54
+ #
55
+ # Does not attempt to remove `.keep` files in the following scenarios:
56
+ # - When the given path is a `.keep` file itself.
57
+ # - When the given path is absolute, since ascending up this path may lead to removal of
58
+ # files outside the Hanami project directory.
59
+ def delete_keepfiles(path)
60
+ path = Pathname(path)
61
+
62
+ return if path.absolute?
63
+ return if path.relative_path_from(path.dirname).to_s == ".keep"
64
+
65
+ path.dirname.ascend do |part|
66
+ keepfile = (part + ".keep").to_path
67
+ delete(keepfile) if exist?(keepfile)
68
+ end
69
+ end
70
+
49
71
  def updated(path)
50
72
  out.puts "Updated #{path}"
51
73
  end
@@ -21,12 +21,12 @@ module Hanami
21
21
 
22
22
  # @since 2.0.0
23
23
  # @api private
24
- def call(app, controller, action, url, http, format, skip_view, slice, context: nil)
24
+ def call(app, controller, action, url, http, format, skip_view, skip_route, slice, context: nil)
25
25
  context ||= ActionContext.new(inflector, app, slice, controller, action)
26
26
  if slice
27
- generate_for_slice(controller, action, url, http, format, skip_view, slice, context)
27
+ generate_for_slice(controller, action, url, http, format, skip_view, skip_route, slice, context)
28
28
  else
29
- generate_for_app(controller, action, url, http, format, skip_view, context)
29
+ generate_for_app(controller, action, url, http, format, skip_view, skip_route, context)
30
30
  end
31
31
  end
32
32
 
@@ -72,15 +72,17 @@ module Hanami
72
72
  attr_reader :inflector
73
73
 
74
74
  # rubocop:disable Metrics/AbcSize
75
- def generate_for_slice(controller, action, url, http, format, skip_view, slice, context)
75
+ def generate_for_slice(controller, action, url, http, format, skip_view, skip_route, slice, context)
76
76
  slice_directory = fs.join("slices", slice)
77
77
  raise MissingSliceError.new(slice) unless fs.directory?(slice_directory)
78
78
 
79
- fs.inject_line_at_block_bottom(
80
- fs.join("config", "routes.rb"),
81
- slice_matcher(slice),
82
- route(controller, action, url, http)
83
- )
79
+ if generate_route?(skip_route)
80
+ fs.inject_line_at_block_bottom(
81
+ fs.join("config", "routes.rb"),
82
+ slice_matcher(slice),
83
+ route(controller, action, url, http)
84
+ )
85
+ end
84
86
 
85
87
  fs.mkdir(directory = fs.join(slice_directory, "actions", controller))
86
88
  fs.write(fs.join(directory, "#{action}.rb"), t("slice_action.erb", context))
@@ -95,12 +97,14 @@ module Hanami
95
97
  end
96
98
  end
97
99
 
98
- def generate_for_app(controller, action, url, http, format, skip_view, context)
99
- fs.inject_line_at_class_bottom(
100
- fs.join("config", "routes.rb"),
101
- "class Routes",
102
- route(controller, action, url, http)
103
- )
100
+ def generate_for_app(controller, action, url, http, format, skip_view, skip_route, context)
101
+ if generate_route?(skip_route)
102
+ fs.inject_line_at_class_bottom(
103
+ fs.join("config", "routes.rb"),
104
+ "class Routes",
105
+ route(controller, action, url, http)
106
+ )
107
+ end
104
108
 
105
109
  fs.mkdir(directory = fs.join("app", "actions", controller))
106
110
  fs.write(fs.join(directory, "#{action}.rb"), t("action.erb", context))
@@ -137,6 +141,14 @@ module Hanami
137
141
  true
138
142
  end
139
143
 
144
+ # @api private
145
+ # @since 2.2.0
146
+ def generate_route?(skip_route)
147
+ return false if skip_route
148
+
149
+ true
150
+ end
151
+
140
152
  # @api private
141
153
  # @since 2.1.0
142
154
  def generate_restful_view?(view, directory)