From d9e30c44eed8a607a804bbb08d4df3d9b6144a8b Mon Sep 17 00:00:00 2001 From: Jane Sandberg Date: Fri, 5 Jun 2026 09:52:25 -0700 Subject: [PATCH 1/3] Do not create duplicate routes when running the action generator repeatedly Closes #146 --- lib/hanami/cli/commands/app/command.rb | 2 +- lib/hanami/cli/errors.rb | 8 +++ lib/hanami/cli/files.rb | 7 +++ lib/hanami/cli/generators/app/action.rb | 25 ++++++-- .../cli/commands/app/generate/action_spec.rb | 57 +++++++++++++++++++ 5 files changed, 94 insertions(+), 5 deletions(-) diff --git a/lib/hanami/cli/commands/app/command.rb b/lib/hanami/cli/commands/app/command.rb index 2d0da9be..44781dac 100644 --- a/lib/hanami/cli/commands/app/command.rb +++ b/lib/hanami/cli/commands/app/command.rb @@ -44,7 +44,7 @@ def call(*args, **opts) Hanami::Env.load super - rescue FileAlreadyExistsError => exception + rescue FileAlreadyExistsError, RouteAlreadyExistsError => exception err.puts(exception.message) exit(1) end diff --git a/lib/hanami/cli/errors.rb b/lib/hanami/cli/errors.rb index 4205c563..d3f4fd26 100644 --- a/lib/hanami/cli/errors.rb +++ b/lib/hanami/cli/errors.rb @@ -147,5 +147,13 @@ def initialize(name) TEXT end end + + # @since 3.0.0 + # @api public + class RouteAlreadyExistsError < Error + def initialize(route) + super("Route #{route} already exists") + end + end end end diff --git a/lib/hanami/cli/files.rb b/lib/hanami/cli/files.rb index 14061809..d3a77236 100644 --- a/lib/hanami/cli/files.rb +++ b/lib/hanami/cli/files.rb @@ -60,6 +60,13 @@ def touch(path) created(path) end + def block_contains?(path, target, contents) + content = adapter.read(path) + class_start = content.index(target) + class_end = content.index(/(?:^|\s)#{CLOSE_BLOCK}(?:$|\s)/, class_start) + content[class_start..class_end].include? contents + end + private attr_reader :out diff --git a/lib/hanami/cli/generators/app/action.rb b/lib/hanami/cli/generators/app/action.rb index e448213e..10c6c920 100644 --- a/lib/hanami/cli/generators/app/action.rb +++ b/lib/hanami/cli/generators/app/action.rb @@ -85,15 +85,14 @@ def insert_route(key:, namespace:, url_path:, http_method:) route = route_definition(key:, url_path:, http_method:) if namespace == Hanami.app.namespace - fs.inject_line_at_class_bottom(routes_location, "class Routes", route) + add_route_to_file(file: routes_location, route:) else slice_routes = fs.join("slices", namespace, "config", "routes.rb") if fs.exist?(slice_routes) - fs.inject_line_at_class_bottom(slice_routes, "class Routes", route) + add_route_to_file(file: slice_routes, route:) else - slice_matcher = /slice[[:space:]]*:#{namespace}/ - fs.inject_line_at_block_bottom(routes_location, slice_matcher, route) + add_route_to_block(file: routes_location, namespace:, route:) end end end @@ -190,6 +189,24 @@ def route_http(action, http_method) result end + + def add_route_to_file(file:, route:) + target_class = "class Routes" + if fs.block_contains?(file, target_class, route) + raise RouteAlreadyExistsError.new(route) + else + fs.inject_line_at_class_bottom(file, "class Routes", route) + end + end + + def add_route_to_block(file:, namespace:, route:) + slice_matcher = /slice[[:space:]]*:#{namespace}/ + if fs.block_contains?(file, slice_matcher, route) + raise RouteAlreadyExistsError.new(route) + end + + fs.inject_line_at_block_bottom(file, slice_matcher, route) + end end end end diff --git a/spec/unit/hanami/cli/commands/app/generate/action_spec.rb b/spec/unit/hanami/cli/commands/app/generate/action_spec.rb index 5f315f42..66f27cc0 100644 --- a/spec/unit/hanami/cli/commands/app/generate/action_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/action_spec.rb @@ -40,6 +40,32 @@ def error_output = err.string.chomp end end + context "when route already exists in route file" do + it "exits with error message" do + expect do + within_application_directory do + routes_contents = <<~CODE + # frozen_string_literal: true + + require "hanami/routes" + + module #{app} + class Routes < Hanami::Routes + root { "Hello from Hanami" } + get "/users", to: "users.index" + end + end + CODE + fs.write("config/routes.rb", routes_contents) + generate_action + end + end.to raise_error SystemExit do |exception| + expect(exception.status).to eq 1 + expect(error_output).to eq 'Route get "/users", to: "users.index" already exists' + end + end + end + context "with existing action file" do let(:file_path) { "app/actions/#{namespace}/#{action}.rb" } @@ -1252,6 +1278,37 @@ class Routes < Hanami::Routes end end + it "does not add a duplicate route to the slice block" do + within_application_directory do + prepare_slice! + fs.mkdir("slices/api") + + routes_contents = <<~CODE + # frozen_string_literal: true + + require "hanami/routes" + + module #{app} + class Routes < Hanami::Routes + root { "Hello from Hanami" } + + slice :#{slice}, at: "/#{slice}" do + get "/home", to: "home.index" + end + end + end + CODE + fs.write("config/routes.rb", routes_contents) + + expect do + subject.call(slice:, name: "home.index") + end.to raise_error SystemExit do |exception| + expect(exception.status).to eq 1 + expect(error_output).to eq 'Route get "/home", to: "home.index" already exists' + end + end + end + it "raises error if slice is nonexistent" do expect { subject.call(slice: "foo", name: action_name) From eff36ba0a6d5d9a3d99255c3e323858efab8533c Mon Sep 17 00:00:00 2001 From: Jane Sandberg Date: Sun, 7 Jun 2026 20:05:40 -0700 Subject: [PATCH 2/3] Do not exit when route already exists --- lib/hanami/cli/commands/app/command.rb | 2 +- lib/hanami/cli/errors.rb | 8 ---- lib/hanami/cli/generators/app/action.rb | 8 ++-- .../cli/commands/app/generate/action_spec.rb | 45 +++++++++---------- 4 files changed, 26 insertions(+), 37 deletions(-) diff --git a/lib/hanami/cli/commands/app/command.rb b/lib/hanami/cli/commands/app/command.rb index 44781dac..2d0da9be 100644 --- a/lib/hanami/cli/commands/app/command.rb +++ b/lib/hanami/cli/commands/app/command.rb @@ -44,7 +44,7 @@ def call(*args, **opts) Hanami::Env.load super - rescue FileAlreadyExistsError, RouteAlreadyExistsError => exception + rescue FileAlreadyExistsError => exception err.puts(exception.message) exit(1) end diff --git a/lib/hanami/cli/errors.rb b/lib/hanami/cli/errors.rb index d3f4fd26..4205c563 100644 --- a/lib/hanami/cli/errors.rb +++ b/lib/hanami/cli/errors.rb @@ -147,13 +147,5 @@ def initialize(name) TEXT end end - - # @since 3.0.0 - # @api public - class RouteAlreadyExistsError < Error - def initialize(route) - super("Route #{route} already exists") - end - end end end diff --git a/lib/hanami/cli/generators/app/action.rb b/lib/hanami/cli/generators/app/action.rb index 10c6c920..41d635da 100644 --- a/lib/hanami/cli/generators/app/action.rb +++ b/lib/hanami/cli/generators/app/action.rb @@ -193,7 +193,7 @@ def route_http(action, http_method) def add_route_to_file(file:, route:) target_class = "class Routes" if fs.block_contains?(file, target_class, route) - raise RouteAlreadyExistsError.new(route) + out.puts "Route (#{route}) already exists, skipping..." else fs.inject_line_at_class_bottom(file, "class Routes", route) end @@ -202,10 +202,10 @@ def add_route_to_file(file:, route:) def add_route_to_block(file:, namespace:, route:) slice_matcher = /slice[[:space:]]*:#{namespace}/ if fs.block_contains?(file, slice_matcher, route) - raise RouteAlreadyExistsError.new(route) + out.puts "Route (#{route}) already exists, skipping..." + else + fs.inject_line_at_block_bottom(file, slice_matcher, route) end - - fs.inject_line_at_block_bottom(file, slice_matcher, route) end end end diff --git a/spec/unit/hanami/cli/commands/app/generate/action_spec.rb b/spec/unit/hanami/cli/commands/app/generate/action_spec.rb index 66f27cc0..5afbe216 100644 --- a/spec/unit/hanami/cli/commands/app/generate/action_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/action_spec.rb @@ -41,27 +41,26 @@ def error_output = err.string.chomp end context "when route already exists in route file" do - it "exits with error message" do - expect do - within_application_directory do - routes_contents = <<~CODE - # frozen_string_literal: true + it "does not add a duplicate route" do + within_application_directory do + routes_contents = <<~CODE + # frozen_string_literal: true - require "hanami/routes" + require "hanami/routes" - module #{app} - class Routes < Hanami::Routes - root { "Hello from Hanami" } - get "/users", to: "users.index" - end + module #{app} + class Routes < Hanami::Routes + root { "Hello from Hanami" } + get "/users", to: "users.index" end - CODE - fs.write("config/routes.rb", routes_contents) - generate_action - end - end.to raise_error SystemExit do |exception| - expect(exception.status).to eq 1 - expect(error_output).to eq 'Route get "/users", to: "users.index" already exists' + end + CODE + fs.write("config/routes.rb", routes_contents) + + generate_action + + expect(fs.read("config/routes.rb")).to eq routes_contents + expect(output).to include 'Route (get "/users", to: "users.index") already exists, skipping...' end end end @@ -1300,12 +1299,10 @@ class Routes < Hanami::Routes CODE fs.write("config/routes.rb", routes_contents) - expect do - subject.call(slice:, name: "home.index") - end.to raise_error SystemExit do |exception| - expect(exception.status).to eq 1 - expect(error_output).to eq 'Route get "/home", to: "home.index" already exists' - end + subject.call(slice:, name: "home.index") + + expect(fs.read("config/routes.rb")).to eq routes_contents + expect(output).to include 'Route (get "/home", to: "home.index") already exists, skipping...' end end From f13d29e14fbb64025482123dcbeda330455b441b Mon Sep 17 00:00:00 2001 From: Tim Riley Date: Tue, 9 Jun 2026 23:27:09 +1000 Subject: [PATCH 3/3] Add CHANGELOG note --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81317c92..485a00e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Break Versioning](https://www.taoensso.com/break-ve ### Fixed - Don't overwrite libpq ENV vars with empty strings (@StantonMatt in #414). +- Skip generating duplicate routes when running `generate action`. (@sandbergja in #417) ### Security