Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ and this project adheres to [Break Versioning](https://www.taoensso.com/break-ve
For example, `hanami generate action books.index` in an app named `Books` generates `Books::Actions::Books::Index`, whose `Books::Action` superclass previously resolved to the nested `Books::Actions::Books` module rather than the top-level app class. The superclass is now written as `::Books::Action` so it resolves correctly.
- Avoid loading user config when running Postgres CLI commands, to ensure command output is in the expected format. (@mddelk in #437)
- Decode percent-encoded DB user and password for CLI tools (@aaronmallen #457)
- Fix `generate action --slice` failing to find the slice in `config/routes.rb` when the slice is declared with parentheses, such as `slice(:main, at: "/") do`, or as a single-line block, such as `slice(:main, at: "/") { root to: "home.show" }`. Single-line slice blocks are expanded into `do ... end` blocks so the new route can be added. (@anyingiit in #464, fixes hanami/hanami#1284)

### Security

Expand Down
36 changes: 35 additions & 1 deletion lib/hanami/cli/generators/app/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ def call(key:, namespace:, base_path:, url_path:, http_method:, skip_view:, skip
PATH_SEPARATOR = "/"
private_constant :PATH_SEPARATOR

# Matches a block written on a single line with braces, such as
# `slice(:main, at: "/") { root to: "home.show" }`.
SINGLE_LINE_BLOCK = /
\A(?<indent>[[:blank:]]*)
(?<call>.*?\))[[:blank:]]*
\{[[:blank:]]*(?<body>.*?)[[:blank:]]*\}
[[:blank:]]*\z
/x
private_constant :SINGLE_LINE_BLOCK

attr_reader :fs, :inflector, :stdout, :view_generator

# @api private
Expand Down Expand Up @@ -200,13 +210,37 @@ def add_route_to_file(file:, route:)
end

def add_route_to_block(file:, namespace:, route:)
slice_matcher = /slice[[:space:]]*:#{namespace}/
slice_matcher = /slice[[:space:]]*\(?[[:space:]]*:#{namespace}/
if fs.block_contains?(file, slice_matcher, route)
stdout.puts "Route (#{route}) already exists, skipping..."
elsif (content = expand_single_line_block(fs.read(file), slice_matcher, route))
fs.write(file, content)
else
fs.inject_line_at_block_bottom(file, slice_matcher, route)
end
end

# Routes cannot be appended to a single-line block like `slice(:main, at: "/") { ... }`,
# so rewrite it as a multi-line `do ... end` block that includes the new route.
#
# Returns nil if the block matching the given matcher is not a single-line block.
def expand_single_line_block(content, matcher, route)
lines = content.lines
index = lines.index { |line| line.match?(matcher) }
return unless index

match = SINGLE_LINE_BLOCK.match(lines[index].chomp)
return unless match

indent = match[:indent]
block = ["#{indent}#{match[:call]} do\n"]
block << "#{indent} #{match[:body]}\n" unless match[:body].empty?
block << "#{indent} #{route}\n"
block << "#{indent}end\n"

lines[index] = block.join
lines.join
end
end
end
end
Expand Down
105 changes: 105 additions & 0 deletions spec/unit/hanami/cli/commands/app/generate/action_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,111 @@ class Routes < Hanami::Routes
end
end

it "appends routes within the proper slice block when slice arguments use parentheses" 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
root to: "home.index"
end

slice( :api, at: "/api" ) do
root to: "home.index"
end
end
end
CODE
fs.write("config/routes.rb", routes_contents)

expected = <<~CODE
# frozen_string_literal: true

require "hanami/routes"

module #{app}
class Routes < Hanami::Routes
root { "Hello from Hanami" }

slice(:#{slice}, at: "/#{slice}") do
root to: "home.index"
get "/users", to: "users.index"
end

slice( :api, at: "/api" ) do
root to: "home.index"
get "/users/:id", to: "users.show"
end
end
end
CODE

subject.call(slice: slice, name: "users.index")
subject.call(slice: "api", name: "users.show")

expect(fs.read("config/routes.rb")).to eq(expected)
end
end

it "expands single-line slice blocks to append routes" 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(:api, at: "/api") { root to: "home.index" }
slice(:#{slice}, at: "/") { root to: "home.show" }
end
end
CODE
fs.write("config/routes.rb", routes_contents)

expected = <<~CODE
# frozen_string_literal: true

require "hanami/routes"

module #{app}
class Routes < Hanami::Routes
root { "Hello from Hanami" }

slice(:api, at: "/api") do
root to: "home.index"
get "/users/:id", to: "users.show"
end
slice(:#{slice}, at: "/") do
root to: "home.show"
post "/subscribe", to: "home.subscribe"
end
end
end
CODE

subject.call(slice: slice, name: "home.subscribe", url_path: "/subscribe", http_method: "post")
subject.call(slice: "api", name: "users.show")

expect(fs.read("config/routes.rb")).to eq(expected)
expect(output).to include("Updated config/routes.rb")
end
end

it "does not add a duplicate route to the slice block" do
within_application_directory do
prepare_slice!
Expand Down