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 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..41d635da 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) + out.puts "Route (#{route}) already exists, skipping..." + 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) + out.puts "Route (#{route}) already exists, skipping..." + else + fs.inject_line_at_block_bottom(file, slice_matcher, route) + end + 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..5afbe216 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,31 @@ def error_output = err.string.chomp end end + context "when route already exists in route file" do + it "does not add a duplicate route" 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 + + 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 + context "with existing action file" do let(:file_path) { "app/actions/#{namespace}/#{action}.rb" } @@ -1252,6 +1277,35 @@ 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) + + 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 + it "raises error if slice is nonexistent" do expect { subject.call(slice: "foo", name: action_name)