From d3e5e78be22450d3cfb2e7e6b14d947ade43e5d9 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Fri, 14 Jun 2024 16:01:35 -0600 Subject: [PATCH 01/25] Add dry-operation to default Gemfile --- lib/hanami/cli/generators/gem/app/gemfile.erb | 1 + spec/unit/hanami/cli/commands/gem/new_spec.rb | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/hanami/cli/generators/gem/app/gemfile.erb b/lib/hanami/cli/generators/gem/app/gemfile.erb index 3a43f0c0..771359f4 100644 --- a/lib/hanami/cli/generators/gem/app/gemfile.erb +++ b/lib/hanami/cli/generators/gem/app/gemfile.erb @@ -12,6 +12,7 @@ source "https://rubygems.org" <%= hanami_gem("view") %> gem "dry-types", "~> 1.0", ">= 1.6.1" +gem "dry-operation", github: "dry-rb/dry-operation" gem "puma" gem "rake" diff --git a/spec/unit/hanami/cli/commands/gem/new_spec.rb b/spec/unit/hanami/cli/commands/gem/new_spec.rb index c5d78723..06d34389 100644 --- a/spec/unit/hanami/cli/commands/gem/new_spec.rb +++ b/spec/unit/hanami/cli/commands/gem/new_spec.rb @@ -128,6 +128,7 @@ gem "hanami-view", "#{hanami_version}" gem "dry-types", "~> 1.0", ">= 1.6.1" + gem "dry-operation", github: "dry-rb/dry-operation" gem "puma" gem "rake" @@ -476,6 +477,7 @@ module Types gem "hanami-view", github: "hanami/view", branch: "main" gem "dry-types", "~> 1.0", ">= 1.6.1" + gem "dry-operation", github: "dry-rb/dry-operation" gem "puma" gem "rake" From 888c388d8e75d07c7a30905fafff9b38a0e73dc7 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Fri, 14 Jun 2024 16:12:33 -0600 Subject: [PATCH 02/25] Add base Operation class, based on dry-operation --- lib/hanami/cli/generators/gem/app.rb | 2 ++ lib/hanami/cli/generators/gem/app/operation.erb | 9 +++++++++ spec/unit/hanami/cli/commands/gem/new_spec.rb | 15 +++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 lib/hanami/cli/generators/gem/app/operation.erb diff --git a/lib/hanami/cli/generators/gem/app.rb b/lib/hanami/cli/generators/gem/app.rb index a2b27fd5..440de26b 100644 --- a/lib/hanami/cli/generators/gem/app.rb +++ b/lib/hanami/cli/generators/gem/app.rb @@ -68,6 +68,8 @@ def generate_app(app, context) # rubocop:disable Metrics/AbcSize fs.write("app/assets/images/favicon.ico", file("favicon.ico")) end + fs.write("app/operation.rb", t("operation.erb", context)) + fs.write("public/404.html", file("404.html")) fs.write("public/500.html", file("500.html")) end diff --git a/lib/hanami/cli/generators/gem/app/operation.erb b/lib/hanami/cli/generators/gem/app/operation.erb new file mode 100644 index 00000000..d0449f2c --- /dev/null +++ b/lib/hanami/cli/generators/gem/app/operation.erb @@ -0,0 +1,9 @@ +# auto_register: false +# frozen_string_literal: true + +require "dry/operation" + +module <%= camelized_app_name %> + class Operation < Dry::Operation + end +end diff --git a/spec/unit/hanami/cli/commands/gem/new_spec.rb b/spec/unit/hanami/cli/commands/gem/new_spec.rb index 06d34389..e476107c 100644 --- a/spec/unit/hanami/cli/commands/gem/new_spec.rb +++ b/spec/unit/hanami/cli/commands/gem/new_spec.rb @@ -436,6 +436,21 @@ module Types expect(fs.read("lib/#{app}/types.rb")).to eq(types) expect(output).to include("Created lib/bookshelf/types.rb") + # app/operation.rb + action = <<~EXPECTED + # auto_register: false + # frozen_string_literal: true + + require "dry/operation" + + module #{inflector.camelize(app)} + class Operation < Dry::Operation + end + end + EXPECTED + expect(fs.read("app/operation.rb")).to eq(action) + expect(output).to include("Created app/operation.rb") + # public/ error pages expect(fs.read("public/404.html")).to include %(The page you were looking for doesn’t exist (404)) expect(fs.read("public/500.html")).to include %(We’re sorry, but something went wrong (500)) From 24d3d6be2f37fc3bc9e78396a5a25a52c35c9f74 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Fri, 14 Jun 2024 17:10:47 -0600 Subject: [PATCH 03/25] Fix view spec --- spec/unit/hanami/cli/commands/app/generate/view_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/unit/hanami/cli/commands/app/generate/view_spec.rb b/spec/unit/hanami/cli/commands/app/generate/view_spec.rb index 33909bf1..4abf30ad 100644 --- a/spec/unit/hanami/cli/commands/app/generate/view_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/view_spec.rb @@ -53,7 +53,7 @@ class Index < Test::View EXPECTED expect(fs.read("app/templates/users/index.html.erb")).to eq(template_file) - expect(output).to include("Created app/views/users/index.rb") + expect(output).to include("Created app/templates/users/index.html.erb") end end @@ -88,7 +88,7 @@ class Index < Test::View EXPECTED expect(fs.read("app/templates/special/users/index.html.erb")).to eq(template_file) - expect(output).to include("Created app/views/special/users/index.rb") + expect(output).to include("Created app/templates/special/users/index.html.erb") end end end @@ -124,7 +124,7 @@ class Index < Main::View EXPECTED expect(fs.read("slices/main/templates/users/index.html.erb")).to eq(template_file) - expect(output).to include("Created slices/main/views/users/index.rb") + expect(output).to include("Created slices/main/templates/users/index.html.erb") end end end From bc2670af3f65a64713a3cdeb88b104512335ac01 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Fri, 14 Jun 2024 18:18:42 -0600 Subject: [PATCH 04/25] Add Operation generators --- .../cli/commands/app/generate/operation.rb | 50 ++++++++++ lib/hanami/cli/generators/app/operation.rb | 65 +++++++++++++ .../app/operation/app_operation.erb | 10 ++ .../app/operation/slice_operation.erb | 10 ++ .../cli/generators/app/operation_context.rb | 83 +++++++++++++++++ lib/hanami/cli/generators/app/slice.rb | 1 + .../cli/generators/app/slice/operation.erb | 7 ++ .../commands/app/generate/operation_spec.rb | 91 +++++++++++++++++++ .../cli/commands/app/generate/slice_spec.rb | 12 +++ 9 files changed, 329 insertions(+) create mode 100644 lib/hanami/cli/commands/app/generate/operation.rb create mode 100644 lib/hanami/cli/generators/app/operation.rb create mode 100644 lib/hanami/cli/generators/app/operation/app_operation.erb create mode 100644 lib/hanami/cli/generators/app/operation/slice_operation.erb create mode 100644 lib/hanami/cli/generators/app/operation_context.rb create mode 100644 lib/hanami/cli/generators/app/slice/operation.erb create mode 100644 spec/unit/hanami/cli/commands/app/generate/operation_spec.rb diff --git a/lib/hanami/cli/commands/app/generate/operation.rb b/lib/hanami/cli/commands/app/generate/operation.rb new file mode 100644 index 00000000..c12ece17 --- /dev/null +++ b/lib/hanami/cli/commands/app/generate/operation.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "dry/inflector" +require "dry/files" +require "shellwords" +require_relative "../../../naming" +require_relative "../../../errors" + +module Hanami + module CLI + module Commands + module App + module Generate + # @since x.x.x + # @api private + class Operation < App::Command + argument :name, required: true, desc: "Operation name" + option :slice, required: false, desc: "Slice name" + + example [ + %(add_book (MyApp::Operations::AddBook)), + %(add_book --slice=admin (Admin::Operations::AddBook)), + ] + attr_reader :generator + private :generator + + # @since x.x.x + # @api private + def initialize( + fs:, inflector:, + generator: Generators::App::Operation.new(fs: fs, inflector: inflector), + **opts + ) + super(fs: fs, inflector: inflector, **opts) + @generator = generator + end + + # @since x.x.x + # @api private + def call(name:, slice: nil, **) + slice = inflector.underscore(Shellwords.shellescape(slice)) if slice + + generator.call(app.namespace, name, slice) + end + end + end + end + end + end +end diff --git a/lib/hanami/cli/generators/app/operation.rb b/lib/hanami/cli/generators/app/operation.rb new file mode 100644 index 00000000..4efc2baa --- /dev/null +++ b/lib/hanami/cli/generators/app/operation.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require "erb" +require "dry/files" +require_relative "../../errors" + +module Hanami + module CLI + module Generators + module App + # @since x.x.x + # @api private + class Operation + # @since x.x.x + # @api private + def initialize(fs:, inflector:) + @fs = fs + @inflector = inflector + end + + # @since x.x.x + # @api private + def call(app, key, slice) + context = OperationContext.new(inflector, app, slice, key) + + if slice + generate_for_slice(context, slice) + else + generate_for_app(context) + end + end + + private + + attr_reader :fs + + attr_reader :inflector + + def generate_for_slice(context, slice) + slice_directory = fs.join("slices", slice) + raise MissingSliceError.new(slice) unless fs.directory?(slice_directory) + + fs.mkdir(directory = fs.join(slice_directory, "operations", context.namespaces)) + fs.write(fs.join(directory, "#{context.name}.rb"), t("slice_operation.erb", context)) + end + + def generate_for_app(context) + fs.mkdir(directory = fs.join("app", "operations", context.namespaces)) + fs.write(fs.join(directory, "#{context.name}.rb"), t("app_operation.erb", context)) + end + + def template(path, context) + require "erb" + + ERB.new( + File.read(__dir__ + "/operation/#{path}") + ).result(context.ctx) + end + + alias_method :t, :template + end + end + end + end +end diff --git a/lib/hanami/cli/generators/app/operation/app_operation.erb b/lib/hanami/cli/generators/app/operation/app_operation.erb new file mode 100644 index 00000000..3ff89a84 --- /dev/null +++ b/lib/hanami/cli/generators/app/operation/app_operation.erb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module <%= camelized_app_name %> + module Operations +<%= module_namespace_declaration %> +<%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_app_name %>::Operation +<%= module_namespace_offset %>end +<%= module_namespace_end %> + end +end diff --git a/lib/hanami/cli/generators/app/operation/slice_operation.erb b/lib/hanami/cli/generators/app/operation/slice_operation.erb new file mode 100644 index 00000000..0cfd006b --- /dev/null +++ b/lib/hanami/cli/generators/app/operation/slice_operation.erb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module <%= camelized_slice_name %> + module Operations +<%= module_namespace_declaration %> +<%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_slice_name %>::Operation +<%= module_namespace_offset %>end +<%= module_namespace_end %> + end +end diff --git a/lib/hanami/cli/generators/app/operation_context.rb b/lib/hanami/cli/generators/app/operation_context.rb new file mode 100644 index 00000000..6cc09622 --- /dev/null +++ b/lib/hanami/cli/generators/app/operation_context.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require_relative "slice_context" +require "dry/files/path" + +module Hanami + module CLI + module Generators + # @since x.x.x + # @api private + module App + # @since x.x.x + # @api private + class OperationContext < SliceContext + # TODO: move these constants somewhere that will let us reuse them + KEY_SEPARATOR = "." + private_constant :KEY_SEPARATOR + + NAMESPACE_SEPARATOR = "::" + private_constant :NAMESPACE_SEPARATOR + + INDENTATION = " " + private_constant :INDENTATION + + OFFSET = INDENTATION * 2 + private_constant :OFFSET + + # @since x.x.x + # @api private + attr_reader :key + + # @since x.x.x + # @api private + def initialize(inflector, app, slice, key) + @key = key + super(inflector, app, slice, nil) + end + + # @since x.x.x + # @api private + def namespaces + @namespaces ||= key.split(KEY_SEPARATOR)[..-2] + end + + # @since x.x.x + # @api private + def name + @name ||= key.split(KEY_SEPARATOR)[-1] + end + + # @api private + # @since x.x.x + # @api private + def camelized_name + inflector.camelize(name) + end + + # @since x.x.x + # @api private + def module_namespace_declaration + namespaces.each_with_index.map { |token, i| + "#{OFFSET}#{INDENTATION * i}module #{inflector.camelize(token)}" + }.join($/) + end + + # @since x.x.x + # @api private + def module_namespace_end + namespaces.each_with_index.map { |_, i| + "#{OFFSET}#{INDENTATION * i}end" + }.reverse.join($/) + end + + # @since x.x.x + # @api private + def module_namespace_offset + "#{OFFSET}#{INDENTATION * namespaces.count}" + end + end + end + end + end +end diff --git a/lib/hanami/cli/generators/app/slice.rb b/lib/hanami/cli/generators/app/slice.rb index 6d1624e3..fcd90861 100644 --- a/lib/hanami/cli/generators/app/slice.rb +++ b/lib/hanami/cli/generators/app/slice.rb @@ -31,6 +31,7 @@ def call(app, slice, url, context: SliceContext.new(inflector, app, slice, url)) fs.write(fs.join(directory, "view.rb"), t("view.erb", context)) fs.write(fs.join(directory, "views", "helpers.rb"), t("helpers.erb", context)) fs.write(fs.join(directory, "templates", "layouts", "app.html.erb"), t("app_layout.erb", context)) + fs.write(fs.join(directory, "operation.rb"), t("operation.erb", context)) if context.bundled_assets? fs.write(fs.join(directory, "assets", "js", "app.js"), t("app_js.erb", context)) diff --git a/lib/hanami/cli/generators/app/slice/operation.erb b/lib/hanami/cli/generators/app/slice/operation.erb new file mode 100644 index 00000000..7501188f --- /dev/null +++ b/lib/hanami/cli/generators/app/slice/operation.erb @@ -0,0 +1,7 @@ +# auto_register: false +# frozen_string_literal: true + +module <%= camelized_slice_name %> + class Operation < <%= camelized_app_name %>::Operation + end +end diff --git a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb new file mode 100644 index 00000000..e6b71295 --- /dev/null +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require "hanami" +require "ostruct" + +RSpec.describe Hanami::CLI::Commands::App::Generate::Operation, :app do + subject { described_class.new(fs: fs, inflector: inflector, generator: generator) } + + let(:out) { StringIO.new } + let(:fs) { Hanami::CLI::Files.new(memory: true, out: out) } + let(:inflector) { Dry::Inflector.new } + let(:generator) { Hanami::CLI::Generators::App::Operation.new(fs: fs, inflector: inflector) } + let(:app) { Hanami.app.namespace } + let(:dir) { inflector.underscore(app) } + + def output + out.rewind && out.read.chomp + end + + context "generating for app" do + it "generates an operation" do + subject.call(name: "add_book") + + # operation + operation_file = <<~EXPECTED + # frozen_string_literal: true + + module Test + module Operations + + class AddBook < Test::Operation + end + + end + end + EXPECTED + + expect(fs.read("app/operations/add_book.rb")).to eq(operation_file) + expect(output).to include("Created app/operations/add_book.rb") + end + + xit "add one for slashes? it does compact module syntax... but why?" + + it "generates a operation in a deep namespace" do + subject.call(name: "external.books.add") + + # view + operation_file = <<~EXPECTED + # frozen_string_literal: true + + module Test + module Operations + module External + module Books + class Add < Test::Operation + end + end + end + end + end + EXPECTED + + expect(fs.read("app/operations/external/books/add.rb")).to eq(operation_file) + expect(output).to include("Created app/operations/external/books/add.rb") + end + end + + context "generating for a slice" do + it "generates a operation in a top-level namespace" do + fs.mkdir("slices/main") + subject.call(name: "add_book", slice: "main") + + # operation + operation_file = <<~EXPECTED + # frozen_string_literal: true + + module Main + module Operations + + class AddBook < Main::Operation + end + + end + end + EXPECTED + + expect(fs.read("slices/main/operations/add_book.rb")).to eq(operation_file) + expect(output).to include("Created slices/main/operations/add_book.rb") + end + end +end diff --git a/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb b/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb index 8bd8e99d..c05825f2 100644 --- a/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb @@ -108,6 +108,18 @@ module Helpers expect(fs.read("slices/#{slice}/views/helpers.rb")).to eq(helpers) expect(output).to include("Created slices/#{slice}/views/helpers.rb") + operation = <<~RUBY + # auto_register: false + # frozen_string_literal: true + + module Admin + class Operation < Bookshelf::Operation + end + end + RUBY + expect(fs.read("slices/#{slice}/operation.rb")).to eq(operation) + expect(output).to include("Created slices/#{slice}/operation.rb") + layout = <<~ERB From 1c37df3cbe6696ae3700601b2599e8a03bb9e6dd Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Fri, 14 Jun 2024 18:59:48 -0600 Subject: [PATCH 05/25] Add empty `call` method definition --- lib/hanami/cli/generators/app/operation/app_operation.erb | 2 ++ lib/hanami/cli/generators/app/operation/slice_operation.erb | 2 ++ .../unit/hanami/cli/commands/app/generate/operation_spec.rb | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/lib/hanami/cli/generators/app/operation/app_operation.erb b/lib/hanami/cli/generators/app/operation/app_operation.erb index 3ff89a84..100ecc68 100644 --- a/lib/hanami/cli/generators/app/operation/app_operation.erb +++ b/lib/hanami/cli/generators/app/operation/app_operation.erb @@ -4,6 +4,8 @@ module <%= camelized_app_name %> module Operations <%= module_namespace_declaration %> <%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_app_name %>::Operation +<%= module_namespace_offset %> def call(input) +<%= module_namespace_offset %> end <%= module_namespace_offset %>end <%= module_namespace_end %> end diff --git a/lib/hanami/cli/generators/app/operation/slice_operation.erb b/lib/hanami/cli/generators/app/operation/slice_operation.erb index 0cfd006b..b3faf784 100644 --- a/lib/hanami/cli/generators/app/operation/slice_operation.erb +++ b/lib/hanami/cli/generators/app/operation/slice_operation.erb @@ -4,6 +4,8 @@ module <%= camelized_slice_name %> module Operations <%= module_namespace_declaration %> <%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_slice_name %>::Operation +<%= module_namespace_offset %> def call(input) +<%= module_namespace_offset %> end <%= module_namespace_offset %>end <%= module_namespace_end %> end diff --git a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb index e6b71295..beb13297 100644 --- a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -29,6 +29,8 @@ module Test module Operations class AddBook < Test::Operation + def call(input) + end end end @@ -53,6 +55,8 @@ module Operations module External module Books class Add < Test::Operation + def call(input) + end end end end @@ -78,6 +82,8 @@ module Main module Operations class AddBook < Main::Operation + def call(input) + end end end From a3f6a037f308faff85cd112980bdadde1a06697a Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Fri, 14 Jun 2024 19:01:06 -0600 Subject: [PATCH 06/25] Remove ostruct --- spec/unit/hanami/cli/commands/app/generate/operation_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb index beb13297..be3f5be6 100644 --- a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "hanami" -require "ostruct" RSpec.describe Hanami::CLI::Commands::App::Generate::Operation, :app do subject { described_class.new(fs: fs, inflector: inflector, generator: generator) } From 649bdcb03a90cf9a34fd703af0ac08afad069bd4 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Mon, 17 Jun 2024 12:55:02 -0600 Subject: [PATCH 07/25] Allow slash separator for generator --- .../cli/generators/app/operation_context.rb | 2 +- .../commands/app/generate/operation_spec.rb | 25 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/hanami/cli/generators/app/operation_context.rb b/lib/hanami/cli/generators/app/operation_context.rb index 6cc09622..72bac616 100644 --- a/lib/hanami/cli/generators/app/operation_context.rb +++ b/lib/hanami/cli/generators/app/operation_context.rb @@ -13,7 +13,7 @@ module App # @api private class OperationContext < SliceContext # TODO: move these constants somewhere that will let us reuse them - KEY_SEPARATOR = "." + KEY_SEPARATOR = %r{\.|/} private_constant :KEY_SEPARATOR NAMESPACE_SEPARATOR = "::" diff --git a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb index be3f5be6..502045ea 100644 --- a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -40,7 +40,30 @@ def call(input) expect(output).to include("Created app/operations/add_book.rb") end - xit "add one for slashes? it does compact module syntax... but why?" + it "add one for slashes? it does compact module syntax... but why?" do + subject.call(name: "external/books/add") + + # operation + operation_file = <<~EXPECTED + # frozen_string_literal: true + + module Test + module Operations + module External + module Books + class Add < Test::Operation + def call(input) + end + end + end + end + end + end + EXPECTED + + expect(fs.read("app/operations/external/books/add.rb")).to eq(operation_file) + expect(output).to include("Created app/operations/external/books/add.rb") + end it "generates a operation in a deep namespace" do subject.call(name: "external.books.add") From f74519fda993ab2979d39ed9e690edb6f637b27e Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Mon, 17 Jun 2024 12:55:42 -0600 Subject: [PATCH 08/25] Allow slash separator for generator --- .../cli/commands/app/generate/operation_spec.rb | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb index 502045ea..6119c818 100644 --- a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -20,7 +20,6 @@ def output it "generates an operation" do subject.call(name: "add_book") - # operation operation_file = <<~EXPECTED # frozen_string_literal: true @@ -40,10 +39,9 @@ def call(input) expect(output).to include("Created app/operations/add_book.rb") end - it "add one for slashes? it does compact module syntax... but why?" do - subject.call(name: "external/books/add") + it "generates a operation in a deep namespace with default separator" do + subject.call(name: "external.books.add") - # operation operation_file = <<~EXPECTED # frozen_string_literal: true @@ -65,10 +63,9 @@ def call(input) expect(output).to include("Created app/operations/external/books/add.rb") end - it "generates a operation in a deep namespace" do - subject.call(name: "external.books.add") + it "generates an operation in a deep namespace with slash separators" do + subject.call(name: "external/books/add") - # view operation_file = <<~EXPECTED # frozen_string_literal: true @@ -96,7 +93,6 @@ def call(input) fs.mkdir("slices/main") subject.call(name: "add_book", slice: "main") - # operation operation_file = <<~EXPECTED # frozen_string_literal: true From 0f9f81494b630db02f019e78ec002b7ec0914412 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Mon, 17 Jun 2024 12:56:39 -0600 Subject: [PATCH 09/25] Rename module to admin --- .../cli/commands/app/generate/operation_spec.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb index 6119c818..e16b0808 100644 --- a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -40,14 +40,14 @@ def call(input) end it "generates a operation in a deep namespace with default separator" do - subject.call(name: "external.books.add") + subject.call(name: "admin.books.add") operation_file = <<~EXPECTED # frozen_string_literal: true module Test module Operations - module External + module Admin module Books class Add < Test::Operation def call(input) @@ -59,19 +59,19 @@ def call(input) end EXPECTED - expect(fs.read("app/operations/external/books/add.rb")).to eq(operation_file) - expect(output).to include("Created app/operations/external/books/add.rb") + expect(fs.read("app/operations/admin/books/add.rb")).to eq(operation_file) + expect(output).to include("Created app/operations/admin/books/add.rb") end it "generates an operation in a deep namespace with slash separators" do - subject.call(name: "external/books/add") + subject.call(name: "admin/books/add") operation_file = <<~EXPECTED # frozen_string_literal: true module Test module Operations - module External + module Admin module Books class Add < Test::Operation def call(input) @@ -83,8 +83,8 @@ def call(input) end EXPECTED - expect(fs.read("app/operations/external/books/add.rb")).to eq(operation_file) - expect(output).to include("Created app/operations/external/books/add.rb") + expect(fs.read("app/operations/admin/books/add.rb")).to eq(operation_file) + expect(output).to include("Created app/operations/admin/books/add.rb") end end From 663abc6193fccdfa0e3bbfd424644bb16fcbd1e6 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Mon, 17 Jun 2024 13:21:52 -0600 Subject: [PATCH 10/25] Remove newlines in generated files By adding new templates for un-nested operations --- lib/hanami/cli/generators/app/operation.rb | 18 ++++++++++++++---- ..._operation.erb => nested_app_operation.erb} | 0 ...peration.erb => nested_slice_operation.erb} | 0 .../app/operation/top_level_app_operation.erb | 10 ++++++++++ .../operation/top_level_slice_operation.erb | 10 ++++++++++ .../commands/app/generate/operation_spec.rb | 4 ---- 6 files changed, 34 insertions(+), 8 deletions(-) rename lib/hanami/cli/generators/app/operation/{app_operation.erb => nested_app_operation.erb} (100%) rename lib/hanami/cli/generators/app/operation/{slice_operation.erb => nested_slice_operation.erb} (100%) create mode 100644 lib/hanami/cli/generators/app/operation/top_level_app_operation.erb create mode 100644 lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb diff --git a/lib/hanami/cli/generators/app/operation.rb b/lib/hanami/cli/generators/app/operation.rb index 4efc2baa..5d56840d 100644 --- a/lib/hanami/cli/generators/app/operation.rb +++ b/lib/hanami/cli/generators/app/operation.rb @@ -40,13 +40,23 @@ def generate_for_slice(context, slice) slice_directory = fs.join("slices", slice) raise MissingSliceError.new(slice) unless fs.directory?(slice_directory) - fs.mkdir(directory = fs.join(slice_directory, "operations", context.namespaces)) - fs.write(fs.join(directory, "#{context.name}.rb"), t("slice_operation.erb", context)) + if context.namespaces.any? + fs.mkdir(directory = fs.join(slice_directory, "operations", context.namespaces)) + fs.write(fs.join(directory, "#{context.name}.rb"), t("nested_slice_operation.erb", context)) + else + fs.mkdir(directory = fs.join(slice_directory, "operations")) + fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_slice_operation.erb", context)) + end end def generate_for_app(context) - fs.mkdir(directory = fs.join("app", "operations", context.namespaces)) - fs.write(fs.join(directory, "#{context.name}.rb"), t("app_operation.erb", context)) + if context.namespaces.any? + fs.mkdir(directory = fs.join("app", "operations", context.namespaces)) + fs.write(fs.join(directory, "#{context.name}.rb"), t("nested_app_operation.erb", context)) + else + fs.mkdir(directory = fs.join("app", "operations")) + fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_app_operation.erb", context)) + end end def template(path, context) diff --git a/lib/hanami/cli/generators/app/operation/app_operation.erb b/lib/hanami/cli/generators/app/operation/nested_app_operation.erb similarity index 100% rename from lib/hanami/cli/generators/app/operation/app_operation.erb rename to lib/hanami/cli/generators/app/operation/nested_app_operation.erb diff --git a/lib/hanami/cli/generators/app/operation/slice_operation.erb b/lib/hanami/cli/generators/app/operation/nested_slice_operation.erb similarity index 100% rename from lib/hanami/cli/generators/app/operation/slice_operation.erb rename to lib/hanami/cli/generators/app/operation/nested_slice_operation.erb diff --git a/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb b/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb new file mode 100644 index 00000000..12776ce2 --- /dev/null +++ b/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module <%= camelized_app_name %> + module Operations +<%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_app_name %>::Operation +<%= module_namespace_offset %> def call(input) +<%= module_namespace_offset %> end +<%= module_namespace_offset %>end + end +end diff --git a/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb b/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb new file mode 100644 index 00000000..a9ec1cd9 --- /dev/null +++ b/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module <%= camelized_slice_name %> + module Operations +<%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_slice_name %>::Operation +<%= module_namespace_offset %> def call(input) +<%= module_namespace_offset %> end +<%= module_namespace_offset %>end + end +end diff --git a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb index e16b0808..c6b7579a 100644 --- a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -25,12 +25,10 @@ def output module Test module Operations - class AddBook < Test::Operation def call(input) end end - end end EXPECTED @@ -98,12 +96,10 @@ def call(input) module Main module Operations - class AddBook < Main::Operation def call(input) end end - end end EXPECTED From 3b72feb279a49538a29ea53c0da8d0ea77fd3222 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Wed, 19 Jun 2024 12:20:06 -0600 Subject: [PATCH 11/25] Remove input as default args --- .../cli/generators/app/operation/nested_app_operation.erb | 2 +- .../generators/app/operation/nested_slice_operation.erb | 2 +- .../generators/app/operation/top_level_app_operation.erb | 2 +- .../app/operation/top_level_slice_operation.erb | 2 +- .../hanami/cli/commands/app/generate/operation_spec.rb | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/hanami/cli/generators/app/operation/nested_app_operation.erb b/lib/hanami/cli/generators/app/operation/nested_app_operation.erb index 100ecc68..208822a0 100644 --- a/lib/hanami/cli/generators/app/operation/nested_app_operation.erb +++ b/lib/hanami/cli/generators/app/operation/nested_app_operation.erb @@ -4,7 +4,7 @@ module <%= camelized_app_name %> module Operations <%= module_namespace_declaration %> <%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_app_name %>::Operation -<%= module_namespace_offset %> def call(input) +<%= module_namespace_offset %> def call <%= module_namespace_offset %> end <%= module_namespace_offset %>end <%= module_namespace_end %> diff --git a/lib/hanami/cli/generators/app/operation/nested_slice_operation.erb b/lib/hanami/cli/generators/app/operation/nested_slice_operation.erb index b3faf784..09a23e1e 100644 --- a/lib/hanami/cli/generators/app/operation/nested_slice_operation.erb +++ b/lib/hanami/cli/generators/app/operation/nested_slice_operation.erb @@ -4,7 +4,7 @@ module <%= camelized_slice_name %> module Operations <%= module_namespace_declaration %> <%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_slice_name %>::Operation -<%= module_namespace_offset %> def call(input) +<%= module_namespace_offset %> def call <%= module_namespace_offset %> end <%= module_namespace_offset %>end <%= module_namespace_end %> diff --git a/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb b/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb index 12776ce2..831fafce 100644 --- a/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb +++ b/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb @@ -3,7 +3,7 @@ module <%= camelized_app_name %> module Operations <%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_app_name %>::Operation -<%= module_namespace_offset %> def call(input) +<%= module_namespace_offset %> def call <%= module_namespace_offset %> end <%= module_namespace_offset %>end end diff --git a/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb b/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb index a9ec1cd9..7671780d 100644 --- a/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb +++ b/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb @@ -3,7 +3,7 @@ module <%= camelized_slice_name %> module Operations <%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_slice_name %>::Operation -<%= module_namespace_offset %> def call(input) +<%= module_namespace_offset %> def call <%= module_namespace_offset %> end <%= module_namespace_offset %>end end diff --git a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb index c6b7579a..ea29c7da 100644 --- a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -26,7 +26,7 @@ def output module Test module Operations class AddBook < Test::Operation - def call(input) + def call end end end @@ -48,7 +48,7 @@ module Operations module Admin module Books class Add < Test::Operation - def call(input) + def call end end end @@ -72,7 +72,7 @@ module Operations module Admin module Books class Add < Test::Operation - def call(input) + def call end end end @@ -97,7 +97,7 @@ def call(input) module Main module Operations class AddBook < Main::Operation - def call(input) + def call end end end From 0f81a5c58ca486d231fd82f724b6c2be8410c3f5 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Wed, 19 Jun 2024 12:35:43 -0600 Subject: [PATCH 12/25] Remove Operations namespace, generate in app/ or slices/SLICE_NAME/ --- lib/hanami/cli/generators/app/operation.rb | 8 ++-- .../app/operation/nested_app_operation.erb | 2 - .../app/operation/nested_slice_operation.erb | 2 - .../app/operation/top_level_app_operation.erb | 2 - .../operation/top_level_slice_operation.erb | 2 - .../cli/generators/app/operation_context.rb | 2 +- .../commands/app/generate/operation_spec.rb | 48 ++++++++----------- 7 files changed, 25 insertions(+), 41 deletions(-) diff --git a/lib/hanami/cli/generators/app/operation.rb b/lib/hanami/cli/generators/app/operation.rb index 5d56840d..559af8c2 100644 --- a/lib/hanami/cli/generators/app/operation.rb +++ b/lib/hanami/cli/generators/app/operation.rb @@ -41,20 +41,20 @@ def generate_for_slice(context, slice) raise MissingSliceError.new(slice) unless fs.directory?(slice_directory) if context.namespaces.any? - fs.mkdir(directory = fs.join(slice_directory, "operations", context.namespaces)) + fs.mkdir(directory = fs.join(slice_directory, context.namespaces)) fs.write(fs.join(directory, "#{context.name}.rb"), t("nested_slice_operation.erb", context)) else - fs.mkdir(directory = fs.join(slice_directory, "operations")) + fs.mkdir(directory = fs.join(slice_directory)) fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_slice_operation.erb", context)) end end def generate_for_app(context) if context.namespaces.any? - fs.mkdir(directory = fs.join("app", "operations", context.namespaces)) + fs.mkdir(directory = fs.join("app", context.namespaces)) fs.write(fs.join(directory, "#{context.name}.rb"), t("nested_app_operation.erb", context)) else - fs.mkdir(directory = fs.join("app", "operations")) + fs.mkdir(directory = fs.join("app")) fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_app_operation.erb", context)) end end diff --git a/lib/hanami/cli/generators/app/operation/nested_app_operation.erb b/lib/hanami/cli/generators/app/operation/nested_app_operation.erb index 208822a0..f0b7a131 100644 --- a/lib/hanami/cli/generators/app/operation/nested_app_operation.erb +++ b/lib/hanami/cli/generators/app/operation/nested_app_operation.erb @@ -1,12 +1,10 @@ # frozen_string_literal: true module <%= camelized_app_name %> - module Operations <%= module_namespace_declaration %> <%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_app_name %>::Operation <%= module_namespace_offset %> def call <%= module_namespace_offset %> end <%= module_namespace_offset %>end <%= module_namespace_end %> - end end diff --git a/lib/hanami/cli/generators/app/operation/nested_slice_operation.erb b/lib/hanami/cli/generators/app/operation/nested_slice_operation.erb index 09a23e1e..a9a448ad 100644 --- a/lib/hanami/cli/generators/app/operation/nested_slice_operation.erb +++ b/lib/hanami/cli/generators/app/operation/nested_slice_operation.erb @@ -1,12 +1,10 @@ # frozen_string_literal: true module <%= camelized_slice_name %> - module Operations <%= module_namespace_declaration %> <%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_slice_name %>::Operation <%= module_namespace_offset %> def call <%= module_namespace_offset %> end <%= module_namespace_offset %>end <%= module_namespace_end %> - end end diff --git a/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb b/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb index 831fafce..0ce59f47 100644 --- a/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb +++ b/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb @@ -1,10 +1,8 @@ # frozen_string_literal: true module <%= camelized_app_name %> - module Operations <%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_app_name %>::Operation <%= module_namespace_offset %> def call <%= module_namespace_offset %> end <%= module_namespace_offset %>end - end end diff --git a/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb b/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb index 7671780d..240448ab 100644 --- a/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb +++ b/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb @@ -1,10 +1,8 @@ # frozen_string_literal: true module <%= camelized_slice_name %> - module Operations <%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_slice_name %>::Operation <%= module_namespace_offset %> def call <%= module_namespace_offset %> end <%= module_namespace_offset %>end - end end diff --git a/lib/hanami/cli/generators/app/operation_context.rb b/lib/hanami/cli/generators/app/operation_context.rb index 72bac616..6e17bdfb 100644 --- a/lib/hanami/cli/generators/app/operation_context.rb +++ b/lib/hanami/cli/generators/app/operation_context.rb @@ -22,7 +22,7 @@ class OperationContext < SliceContext INDENTATION = " " private_constant :INDENTATION - OFFSET = INDENTATION * 2 + OFFSET = INDENTATION private_constant :OFFSET # @since x.x.x diff --git a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb index ea29c7da..be5f8c0d 100644 --- a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -24,17 +24,15 @@ def output # frozen_string_literal: true module Test - module Operations - class AddBook < Test::Operation - def call - end + class AddBook < Test::Operation + def call end end end EXPECTED - expect(fs.read("app/operations/add_book.rb")).to eq(operation_file) - expect(output).to include("Created app/operations/add_book.rb") + expect(fs.read("app/add_book.rb")).to eq(operation_file) + expect(output).to include("Created app/add_book.rb") end it "generates a operation in a deep namespace with default separator" do @@ -44,12 +42,10 @@ def call # frozen_string_literal: true module Test - module Operations - module Admin - module Books - class Add < Test::Operation - def call - end + module Admin + module Books + class Add < Test::Operation + def call end end end @@ -57,8 +53,8 @@ def call end EXPECTED - expect(fs.read("app/operations/admin/books/add.rb")).to eq(operation_file) - expect(output).to include("Created app/operations/admin/books/add.rb") + expect(fs.read("app/admin/books/add.rb")).to eq(operation_file) + expect(output).to include("Created app/admin/books/add.rb") end it "generates an operation in a deep namespace with slash separators" do @@ -68,12 +64,10 @@ def call # frozen_string_literal: true module Test - module Operations - module Admin - module Books - class Add < Test::Operation - def call - end + module Admin + module Books + class Add < Test::Operation + def call end end end @@ -81,8 +75,8 @@ def call end EXPECTED - expect(fs.read("app/operations/admin/books/add.rb")).to eq(operation_file) - expect(output).to include("Created app/operations/admin/books/add.rb") + expect(fs.read("app/admin/books/add.rb")).to eq(operation_file) + expect(output).to include("Created app/admin/books/add.rb") end end @@ -95,17 +89,15 @@ def call # frozen_string_literal: true module Main - module Operations - class AddBook < Main::Operation - def call - end + class AddBook < Main::Operation + def call end end end EXPECTED - expect(fs.read("slices/main/operations/add_book.rb")).to eq(operation_file) - expect(output).to include("Created slices/main/operations/add_book.rb") + expect(fs.read("slices/main/add_book.rb")).to eq(operation_file) + expect(output).to include("Created slices/main/add_book.rb") end end end From a5bd2f30311293c24594d4c84bb0c1033dd055e3 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Wed, 19 Jun 2024 14:16:45 -0600 Subject: [PATCH 13/25] Prevent generating operation without namespace --- lib/hanami/cli/errors.rb | 4 + lib/hanami/cli/generators/app/operation.rb | 20 +++-- ...ed_app_operation.erb => app_operation.erb} | 0 ...lice_operation.erb => slice_operation.erb} | 0 .../app/operation/top_level_app_operation.erb | 8 -- .../operation/top_level_slice_operation.erb | 8 -- .../commands/app/generate/operation_spec.rb | 78 ++++++++++++++++--- 7 files changed, 84 insertions(+), 34 deletions(-) rename lib/hanami/cli/generators/app/operation/{nested_app_operation.erb => app_operation.erb} (100%) rename lib/hanami/cli/generators/app/operation/{nested_slice_operation.erb => slice_operation.erb} (100%) delete mode 100644 lib/hanami/cli/generators/app/operation/top_level_app_operation.erb delete mode 100644 lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb diff --git a/lib/hanami/cli/errors.rb b/lib/hanami/cli/errors.rb index 235ad771..5a69842e 100644 --- a/lib/hanami/cli/errors.rb +++ b/lib/hanami/cli/errors.rb @@ -91,5 +91,9 @@ def initialize(scheme) super("`#{scheme}' is not a supported db scheme") end end + + # @since x.x.x + # @api public + class NameNeedsNamespaceError < Error; end end end diff --git a/lib/hanami/cli/generators/app/operation.rb b/lib/hanami/cli/generators/app/operation.rb index 559af8c2..88c309b8 100644 --- a/lib/hanami/cli/generators/app/operation.rb +++ b/lib/hanami/cli/generators/app/operation.rb @@ -42,20 +42,18 @@ def generate_for_slice(context, slice) if context.namespaces.any? fs.mkdir(directory = fs.join(slice_directory, context.namespaces)) - fs.write(fs.join(directory, "#{context.name}.rb"), t("nested_slice_operation.erb", context)) + fs.write(fs.join(directory, "#{context.name}.rb"), t("slice_operation.erb", context)) else - fs.mkdir(directory = fs.join(slice_directory)) - fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_slice_operation.erb", context)) + print_error_message_about_naming(context.name, slice_directory) end end def generate_for_app(context) if context.namespaces.any? fs.mkdir(directory = fs.join("app", context.namespaces)) - fs.write(fs.join(directory, "#{context.name}.rb"), t("nested_app_operation.erb", context)) + fs.write(fs.join(directory, "#{context.name}.rb"), t("app_operation.erb", context)) else - fs.mkdir(directory = fs.join("app")) - fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_app_operation.erb", context)) + print_error_message_about_naming(context.name, "app") end end @@ -67,6 +65,16 @@ def template(path, context) ).result(context.ctx) end + def print_error_message_about_naming(provided_name, base_location) + raise NameNeedsNamespaceError.new( + "Failed to create operation `#{provided_name}'. " \ + "This would create the operation directly in the `#{base_location}/' folder. " \ + "Instead, you should provide a namespace for the folder where this operation will live. " \ + "NOTE: We recommend giving it a name that's specific to your domain, " \ + "but you can also use `operations.#{provided_name}' in the meantime if you're unsure." + ) + end + alias_method :t, :template end end diff --git a/lib/hanami/cli/generators/app/operation/nested_app_operation.erb b/lib/hanami/cli/generators/app/operation/app_operation.erb similarity index 100% rename from lib/hanami/cli/generators/app/operation/nested_app_operation.erb rename to lib/hanami/cli/generators/app/operation/app_operation.erb diff --git a/lib/hanami/cli/generators/app/operation/nested_slice_operation.erb b/lib/hanami/cli/generators/app/operation/slice_operation.erb similarity index 100% rename from lib/hanami/cli/generators/app/operation/nested_slice_operation.erb rename to lib/hanami/cli/generators/app/operation/slice_operation.erb diff --git a/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb b/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb deleted file mode 100644 index 0ce59f47..00000000 --- a/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -module <%= camelized_app_name %> -<%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_app_name %>::Operation -<%= module_namespace_offset %> def call -<%= module_namespace_offset %> end -<%= module_namespace_offset %>end -end diff --git a/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb b/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb deleted file mode 100644 index 240448ab..00000000 --- a/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -module <%= camelized_slice_name %> -<%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_slice_name %>::Operation -<%= module_namespace_offset %> def call -<%= module_namespace_offset %> end -<%= module_namespace_offset %>end -end diff --git a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb index be5f8c0d..5529d230 100644 --- a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -18,21 +18,23 @@ def output context "generating for app" do it "generates an operation" do - subject.call(name: "add_book") + subject.call(name: "operations/add_book") operation_file = <<~EXPECTED # frozen_string_literal: true module Test - class AddBook < Test::Operation - def call + module Operations + class AddBook < Test::Operation + def call + end end end end EXPECTED - expect(fs.read("app/add_book.rb")).to eq(operation_file) - expect(output).to include("Created app/add_book.rb") + expect(fs.read("app/operations/add_book.rb")).to eq(operation_file) + expect(output).to include("Created app/operations/add_book.rb") end it "generates a operation in a deep namespace with default separator" do @@ -57,7 +59,7 @@ def call expect(output).to include("Created app/admin/books/add.rb") end - it "generates an operation in a deep namespace with slash separators" do + it "generates an operation in a deep namespace with slash separator" do subject.call(name: "admin/books/add") operation_file = <<~EXPECTED @@ -78,26 +80,78 @@ def call expect(fs.read("app/admin/books/add.rb")).to eq(operation_file) expect(output).to include("Created app/admin/books/add.rb") end + + it "outputs an error if trying to generate an operation without a separator" do + expect { + subject.call(name: "add_book") + }.to raise_error(Hanami::CLI::NameNeedsNamespaceError).with_message( + "Failed to create operation `add_book'. " \ + "This would create the operation directly in the `app/' folder. " \ + "Instead, you should provide a namespace for the folder where this operation will live. " \ + "NOTE: We recommend giving it a name that's specific to your domain, " \ + "but you can also use `operations.add_book' in the meantime if you're unsure." + ) + expect(fs.exist?("app/add_book.rb")).to be(false) + end end context "generating for a slice" do - it "generates a operation in a top-level namespace" do + it "generates a operation" do + fs.mkdir("slices/main") + subject.call(name: "operations.add_book", slice: "main") + + operation_file = <<~EXPECTED + # frozen_string_literal: true + + module Main + module Operations + class AddBook < Main::Operation + def call + end + end + end + end + EXPECTED + + expect(fs.read("slices/main/operations/add_book.rb")).to eq(operation_file) + expect(output).to include("Created slices/main/operations/add_book.rb") + end + + it "generates a operation in a deep namespace with default separator" do fs.mkdir("slices/main") - subject.call(name: "add_book", slice: "main") + subject.call(name: "admin.books.add", slice: "main") operation_file = <<~EXPECTED # frozen_string_literal: true module Main - class AddBook < Main::Operation - def call + module Admin + module Books + class Add < Main::Operation + def call + end + end end end end EXPECTED - expect(fs.read("slices/main/add_book.rb")).to eq(operation_file) - expect(output).to include("Created slices/main/add_book.rb") + expect(fs.read("slices/main/admin/books/add.rb")).to eq(operation_file) + expect(output).to include("Created slices/main/admin/books/add.rb") + end + + it "outputs an error if trying to generate an operation without a separator" do + fs.mkdir("slices/main") + expect { + subject.call(name: "add_book", slice: "main") + }.to raise_error(Hanami::CLI::NameNeedsNamespaceError).with_message( + "Failed to create operation `add_book'. " \ + "This would create the operation directly in the `slices/main/' folder. " \ + "Instead, you should provide a namespace for the folder where this operation will live. " \ + "NOTE: We recommend giving it a name that's specific to your domain, " \ + "but you can also use `operations.add_book' in the meantime if you're unsure." + ) + expect(fs.exist?("app/add_book.rb")).to be(false) end end end From eb391ca845404ed25638317392fbc9c3566f96eb Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Thu, 20 Jun 2024 10:20:59 -0600 Subject: [PATCH 14/25] Revert "Prevent generating operation without namespace" This reverts commit a5bd2f30311293c24594d4c84bb0c1033dd055e3. --- lib/hanami/cli/errors.rb | 4 - lib/hanami/cli/generators/app/operation.rb | 20 ++--- ...operation.erb => nested_app_operation.erb} | 0 ...eration.erb => nested_slice_operation.erb} | 0 .../app/operation/top_level_app_operation.erb | 8 ++ .../operation/top_level_slice_operation.erb | 8 ++ .../commands/app/generate/operation_spec.rb | 78 +++---------------- 7 files changed, 34 insertions(+), 84 deletions(-) rename lib/hanami/cli/generators/app/operation/{app_operation.erb => nested_app_operation.erb} (100%) rename lib/hanami/cli/generators/app/operation/{slice_operation.erb => nested_slice_operation.erb} (100%) create mode 100644 lib/hanami/cli/generators/app/operation/top_level_app_operation.erb create mode 100644 lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb diff --git a/lib/hanami/cli/errors.rb b/lib/hanami/cli/errors.rb index 5a69842e..235ad771 100644 --- a/lib/hanami/cli/errors.rb +++ b/lib/hanami/cli/errors.rb @@ -91,9 +91,5 @@ def initialize(scheme) super("`#{scheme}' is not a supported db scheme") end end - - # @since x.x.x - # @api public - class NameNeedsNamespaceError < Error; end end end diff --git a/lib/hanami/cli/generators/app/operation.rb b/lib/hanami/cli/generators/app/operation.rb index 88c309b8..559af8c2 100644 --- a/lib/hanami/cli/generators/app/operation.rb +++ b/lib/hanami/cli/generators/app/operation.rb @@ -42,18 +42,20 @@ def generate_for_slice(context, slice) if context.namespaces.any? fs.mkdir(directory = fs.join(slice_directory, context.namespaces)) - fs.write(fs.join(directory, "#{context.name}.rb"), t("slice_operation.erb", context)) + fs.write(fs.join(directory, "#{context.name}.rb"), t("nested_slice_operation.erb", context)) else - print_error_message_about_naming(context.name, slice_directory) + fs.mkdir(directory = fs.join(slice_directory)) + fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_slice_operation.erb", context)) end end def generate_for_app(context) if context.namespaces.any? fs.mkdir(directory = fs.join("app", context.namespaces)) - fs.write(fs.join(directory, "#{context.name}.rb"), t("app_operation.erb", context)) + fs.write(fs.join(directory, "#{context.name}.rb"), t("nested_app_operation.erb", context)) else - print_error_message_about_naming(context.name, "app") + fs.mkdir(directory = fs.join("app")) + fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_app_operation.erb", context)) end end @@ -65,16 +67,6 @@ def template(path, context) ).result(context.ctx) end - def print_error_message_about_naming(provided_name, base_location) - raise NameNeedsNamespaceError.new( - "Failed to create operation `#{provided_name}'. " \ - "This would create the operation directly in the `#{base_location}/' folder. " \ - "Instead, you should provide a namespace for the folder where this operation will live. " \ - "NOTE: We recommend giving it a name that's specific to your domain, " \ - "but you can also use `operations.#{provided_name}' in the meantime if you're unsure." - ) - end - alias_method :t, :template end end diff --git a/lib/hanami/cli/generators/app/operation/app_operation.erb b/lib/hanami/cli/generators/app/operation/nested_app_operation.erb similarity index 100% rename from lib/hanami/cli/generators/app/operation/app_operation.erb rename to lib/hanami/cli/generators/app/operation/nested_app_operation.erb diff --git a/lib/hanami/cli/generators/app/operation/slice_operation.erb b/lib/hanami/cli/generators/app/operation/nested_slice_operation.erb similarity index 100% rename from lib/hanami/cli/generators/app/operation/slice_operation.erb rename to lib/hanami/cli/generators/app/operation/nested_slice_operation.erb diff --git a/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb b/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb new file mode 100644 index 00000000..0ce59f47 --- /dev/null +++ b/lib/hanami/cli/generators/app/operation/top_level_app_operation.erb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module <%= camelized_app_name %> +<%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_app_name %>::Operation +<%= module_namespace_offset %> def call +<%= module_namespace_offset %> end +<%= module_namespace_offset %>end +end diff --git a/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb b/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb new file mode 100644 index 00000000..240448ab --- /dev/null +++ b/lib/hanami/cli/generators/app/operation/top_level_slice_operation.erb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module <%= camelized_slice_name %> +<%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_slice_name %>::Operation +<%= module_namespace_offset %> def call +<%= module_namespace_offset %> end +<%= module_namespace_offset %>end +end diff --git a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb index 5529d230..be5f8c0d 100644 --- a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -18,23 +18,21 @@ def output context "generating for app" do it "generates an operation" do - subject.call(name: "operations/add_book") + subject.call(name: "add_book") operation_file = <<~EXPECTED # frozen_string_literal: true module Test - module Operations - class AddBook < Test::Operation - def call - end + class AddBook < Test::Operation + def call end end end EXPECTED - expect(fs.read("app/operations/add_book.rb")).to eq(operation_file) - expect(output).to include("Created app/operations/add_book.rb") + expect(fs.read("app/add_book.rb")).to eq(operation_file) + expect(output).to include("Created app/add_book.rb") end it "generates a operation in a deep namespace with default separator" do @@ -59,7 +57,7 @@ def call expect(output).to include("Created app/admin/books/add.rb") end - it "generates an operation in a deep namespace with slash separator" do + it "generates an operation in a deep namespace with slash separators" do subject.call(name: "admin/books/add") operation_file = <<~EXPECTED @@ -80,78 +78,26 @@ def call expect(fs.read("app/admin/books/add.rb")).to eq(operation_file) expect(output).to include("Created app/admin/books/add.rb") end - - it "outputs an error if trying to generate an operation without a separator" do - expect { - subject.call(name: "add_book") - }.to raise_error(Hanami::CLI::NameNeedsNamespaceError).with_message( - "Failed to create operation `add_book'. " \ - "This would create the operation directly in the `app/' folder. " \ - "Instead, you should provide a namespace for the folder where this operation will live. " \ - "NOTE: We recommend giving it a name that's specific to your domain, " \ - "but you can also use `operations.add_book' in the meantime if you're unsure." - ) - expect(fs.exist?("app/add_book.rb")).to be(false) - end end context "generating for a slice" do - it "generates a operation" do - fs.mkdir("slices/main") - subject.call(name: "operations.add_book", slice: "main") - - operation_file = <<~EXPECTED - # frozen_string_literal: true - - module Main - module Operations - class AddBook < Main::Operation - def call - end - end - end - end - EXPECTED - - expect(fs.read("slices/main/operations/add_book.rb")).to eq(operation_file) - expect(output).to include("Created slices/main/operations/add_book.rb") - end - - it "generates a operation in a deep namespace with default separator" do + it "generates a operation in a top-level namespace" do fs.mkdir("slices/main") - subject.call(name: "admin.books.add", slice: "main") + subject.call(name: "add_book", slice: "main") operation_file = <<~EXPECTED # frozen_string_literal: true module Main - module Admin - module Books - class Add < Main::Operation - def call - end - end + class AddBook < Main::Operation + def call end end end EXPECTED - expect(fs.read("slices/main/admin/books/add.rb")).to eq(operation_file) - expect(output).to include("Created slices/main/admin/books/add.rb") - end - - it "outputs an error if trying to generate an operation without a separator" do - fs.mkdir("slices/main") - expect { - subject.call(name: "add_book", slice: "main") - }.to raise_error(Hanami::CLI::NameNeedsNamespaceError).with_message( - "Failed to create operation `add_book'. " \ - "This would create the operation directly in the `slices/main/' folder. " \ - "Instead, you should provide a namespace for the folder where this operation will live. " \ - "NOTE: We recommend giving it a name that's specific to your domain, " \ - "but you can also use `operations.add_book' in the meantime if you're unsure." - ) - expect(fs.exist?("app/add_book.rb")).to be(false) + expect(fs.read("slices/main/add_book.rb")).to eq(operation_file) + expect(output).to include("Created slices/main/add_book.rb") end end end From 10232259fb6262d01288644d276b55d19104acd6 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Thu, 20 Jun 2024 10:42:28 -0600 Subject: [PATCH 15/25] Add recommendation to add namespace to operations --- lib/hanami/cli/files.rb | 6 ++++ lib/hanami/cli/generators/app/operation.rb | 2 ++ .../commands/app/generate/operation_spec.rb | 29 +++++++++++++++++-- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/hanami/cli/files.rb b/lib/hanami/cli/files.rb index eb969e5c..bceb568b 100644 --- a/lib/hanami/cli/files.rb +++ b/lib/hanami/cli/files.rb @@ -42,6 +42,12 @@ def chdir(path, &blk) super end + # @since x.x.x + # @api private + def recommend(message) + out.puts(" Recommendation: #{message}") + end + private attr_reader :out diff --git a/lib/hanami/cli/generators/app/operation.rb b/lib/hanami/cli/generators/app/operation.rb index 559af8c2..ec9b381d 100644 --- a/lib/hanami/cli/generators/app/operation.rb +++ b/lib/hanami/cli/generators/app/operation.rb @@ -46,6 +46,7 @@ def generate_for_slice(context, slice) else fs.mkdir(directory = fs.join(slice_directory)) fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_slice_operation.erb", context)) + fs.recommend("Add a namespace to operation names, so they go into a folder within #{directory}/.") end end @@ -56,6 +57,7 @@ def generate_for_app(context) else fs.mkdir(directory = fs.join("app")) fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_app_operation.erb", context)) + fs.recommend("Add a namespace to operation names, so they go into a folder within #{directory}/.") end end diff --git a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb index be5f8c0d..09a571d8 100644 --- a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -17,7 +17,7 @@ def output end context "generating for app" do - it "generates an operation" do + it "generates an operation without a namespace, with a recommendation" do subject.call(name: "add_book") operation_file = <<~EXPECTED @@ -33,6 +33,7 @@ def call expect(fs.read("app/add_book.rb")).to eq(operation_file) expect(output).to include("Created app/add_book.rb") + expect(output).to include(" Recommendation: Add a namespace to operation names, so they go into a folder within app/") end it "generates a operation in a deep namespace with default separator" do @@ -81,7 +82,7 @@ def call end context "generating for a slice" do - it "generates a operation in a top-level namespace" do + it "generates a operation in a top-level namespace, with recommendation" do fs.mkdir("slices/main") subject.call(name: "add_book", slice: "main") @@ -98,6 +99,30 @@ def call expect(fs.read("slices/main/add_book.rb")).to eq(operation_file) expect(output).to include("Created slices/main/add_book.rb") + expect(output).to include(" Recommendation: Add a namespace to operation names, so they go into a folder within slices/main/") + end + + it "generates a operation in a nested namespace" do + fs.mkdir("slices/main") + subject.call(name: "admin.books.add", slice: "main") + + operation_file = <<~EXPECTED + # frozen_string_literal: true + + module Main + module Admin + module Books + class Add < Main::Operation + def call + end + end + end + end + end + EXPECTED + + expect(fs.read("slices/main/admin/books/add.rb")).to eq(operation_file) + expect(output).to include("Created slices/main/admin/books/add.rb") end end end From 6a3c32ae8a20b83d4795ddb897fd80c8470fd24a Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Thu, 20 Jun 2024 15:09:54 -0600 Subject: [PATCH 16/25] Change examples --- lib/hanami/cli/commands/app/generate/operation.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/hanami/cli/commands/app/generate/operation.rb b/lib/hanami/cli/commands/app/generate/operation.rb index c12ece17..6c9a2ec3 100644 --- a/lib/hanami/cli/commands/app/generate/operation.rb +++ b/lib/hanami/cli/commands/app/generate/operation.rb @@ -18,8 +18,8 @@ class Operation < App::Command option :slice, required: false, desc: "Slice name" example [ - %(add_book (MyApp::Operations::AddBook)), - %(add_book --slice=admin (Admin::Operations::AddBook)), + %(books.add (MyApp::Books::Add)), + %(books.add --slice=admin (Admin::Books::Add)), ] attr_reader :generator private :generator From 8dc3de4c6df7b3c5a76efd7b62cc799cf1c826f0 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Fri, 21 Jun 2024 09:03:28 -0600 Subject: [PATCH 17/25] Switch to outputting directly, remove Files#recommend --- lib/hanami/cli/files.rb | 6 ------ lib/hanami/cli/generators/app/operation.rb | 11 +++++------ .../cli/commands/app/generate/operation_spec.rb | 12 +++++++++--- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/hanami/cli/files.rb b/lib/hanami/cli/files.rb index bceb568b..eb969e5c 100644 --- a/lib/hanami/cli/files.rb +++ b/lib/hanami/cli/files.rb @@ -42,12 +42,6 @@ def chdir(path, &blk) super end - # @since x.x.x - # @api private - def recommend(message) - out.puts(" Recommendation: #{message}") - end - private attr_reader :out diff --git a/lib/hanami/cli/generators/app/operation.rb b/lib/hanami/cli/generators/app/operation.rb index ec9b381d..afc92547 100644 --- a/lib/hanami/cli/generators/app/operation.rb +++ b/lib/hanami/cli/generators/app/operation.rb @@ -13,9 +13,10 @@ module App class Operation # @since x.x.x # @api private - def initialize(fs:, inflector:) + def initialize(fs:, inflector:, out: $stdout) @fs = fs @inflector = inflector + @out = out end # @since x.x.x @@ -32,9 +33,7 @@ def call(app, key, slice) private - attr_reader :fs - - attr_reader :inflector + attr_reader :fs, :inflector, :out def generate_for_slice(context, slice) slice_directory = fs.join("slices", slice) @@ -46,7 +45,7 @@ def generate_for_slice(context, slice) else fs.mkdir(directory = fs.join(slice_directory)) fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_slice_operation.erb", context)) - fs.recommend("Add a namespace to operation names, so they go into a folder within #{directory}/.") + out.puts(" Generating a top-level operation. To generate into a directory, add a namespace: `my_namespace.#{context.name}`") end end @@ -56,8 +55,8 @@ def generate_for_app(context) fs.write(fs.join(directory, "#{context.name}.rb"), t("nested_app_operation.erb", context)) else fs.mkdir(directory = fs.join("app")) + out.puts(" Generating a top-level operation. To generate into a directory, add a namespace: `my_namespace.#{context.name}`") fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_app_operation.erb", context)) - fs.recommend("Add a namespace to operation names, so they go into a folder within #{directory}/.") end end diff --git a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb index 09a571d8..9655ad71 100644 --- a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -8,7 +8,7 @@ let(:out) { StringIO.new } let(:fs) { Hanami::CLI::Files.new(memory: true, out: out) } let(:inflector) { Dry::Inflector.new } - let(:generator) { Hanami::CLI::Generators::App::Operation.new(fs: fs, inflector: inflector) } + let(:generator) { Hanami::CLI::Generators::App::Operation.new(fs: fs, inflector: inflector, out: out) } let(:app) { Hanami.app.namespace } let(:dir) { inflector.underscore(app) } @@ -33,7 +33,10 @@ def call expect(fs.read("app/add_book.rb")).to eq(operation_file) expect(output).to include("Created app/add_book.rb") - expect(output).to include(" Recommendation: Add a namespace to operation names, so they go into a folder within app/") + expect(output).to include( + " Generating a top-level operation. " \ + "To generate into a directory, add a namespace: `my_namespace.add_book`" + ) end it "generates a operation in a deep namespace with default separator" do @@ -99,7 +102,10 @@ def call expect(fs.read("slices/main/add_book.rb")).to eq(operation_file) expect(output).to include("Created slices/main/add_book.rb") - expect(output).to include(" Recommendation: Add a namespace to operation names, so they go into a folder within slices/main/") + expect(output).to include( + " Generating a top-level operation. " \ + "To generate into a directory, add a namespace: `my_namespace.add_book`" + ) end it "generates a operation in a nested namespace" do From 8f90b33f26ff18ab44434854705db82bd13f2474 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Sat, 22 Jun 2024 09:34:49 -0600 Subject: [PATCH 18/25] x.x.x => 2.2.0 --- .../cli/commands/app/generate/operation.rb | 6 +++--- lib/hanami/cli/generators/app/operation.rb | 6 +++--- .../cli/generators/app/operation_context.rb | 20 +++++++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/hanami/cli/commands/app/generate/operation.rb b/lib/hanami/cli/commands/app/generate/operation.rb index 6c9a2ec3..ad4fb374 100644 --- a/lib/hanami/cli/commands/app/generate/operation.rb +++ b/lib/hanami/cli/commands/app/generate/operation.rb @@ -11,7 +11,7 @@ module CLI module Commands module App module Generate - # @since x.x.x + # @since 2.2.0 # @api private class Operation < App::Command argument :name, required: true, desc: "Operation name" @@ -24,7 +24,7 @@ class Operation < App::Command attr_reader :generator private :generator - # @since x.x.x + # @since 2.2.0 # @api private def initialize( fs:, inflector:, @@ -35,7 +35,7 @@ def initialize( @generator = generator end - # @since x.x.x + # @since 2.2.0 # @api private def call(name:, slice: nil, **) slice = inflector.underscore(Shellwords.shellescape(slice)) if slice diff --git a/lib/hanami/cli/generators/app/operation.rb b/lib/hanami/cli/generators/app/operation.rb index afc92547..67c6e035 100644 --- a/lib/hanami/cli/generators/app/operation.rb +++ b/lib/hanami/cli/generators/app/operation.rb @@ -8,10 +8,10 @@ module Hanami module CLI module Generators module App - # @since x.x.x + # @since 2.2.0 # @api private class Operation - # @since x.x.x + # @since 2.2.0 # @api private def initialize(fs:, inflector:, out: $stdout) @fs = fs @@ -19,7 +19,7 @@ def initialize(fs:, inflector:, out: $stdout) @out = out end - # @since x.x.x + # @since 2.2.0 # @api private def call(app, key, slice) context = OperationContext.new(inflector, app, slice, key) diff --git a/lib/hanami/cli/generators/app/operation_context.rb b/lib/hanami/cli/generators/app/operation_context.rb index 6e17bdfb..03ac07a2 100644 --- a/lib/hanami/cli/generators/app/operation_context.rb +++ b/lib/hanami/cli/generators/app/operation_context.rb @@ -6,10 +6,10 @@ module Hanami module CLI module Generators - # @since x.x.x + # @since 2.2.0 # @api private module App - # @since x.x.x + # @since 2.2.0 # @api private class OperationContext < SliceContext # TODO: move these constants somewhere that will let us reuse them @@ -25,37 +25,37 @@ class OperationContext < SliceContext OFFSET = INDENTATION private_constant :OFFSET - # @since x.x.x + # @since 2.2.0 # @api private attr_reader :key - # @since x.x.x + # @since 2.2.0 # @api private def initialize(inflector, app, slice, key) @key = key super(inflector, app, slice, nil) end - # @since x.x.x + # @since 2.2.0 # @api private def namespaces @namespaces ||= key.split(KEY_SEPARATOR)[..-2] end - # @since x.x.x + # @since 2.2.0 # @api private def name @name ||= key.split(KEY_SEPARATOR)[-1] end # @api private - # @since x.x.x + # @since 2.2.0 # @api private def camelized_name inflector.camelize(name) end - # @since x.x.x + # @since 2.2.0 # @api private def module_namespace_declaration namespaces.each_with_index.map { |token, i| @@ -63,7 +63,7 @@ def module_namespace_declaration }.join($/) end - # @since x.x.x + # @since 2.2.0 # @api private def module_namespace_end namespaces.each_with_index.map { |_, i| @@ -71,7 +71,7 @@ def module_namespace_end }.reverse.join($/) end - # @since x.x.x + # @since 2.2.0 # @api private def module_namespace_offset "#{OFFSET}#{INDENTATION * namespaces.count}" From 8e62aa352ac382dde6ced0cd360c6528f80408fb Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Sat, 22 Jun 2024 10:04:49 -0600 Subject: [PATCH 19/25] Include Dry::Monads[:result] in base Action --- lib/hanami/cli/generators/gem/app/action.erb | 1 + spec/unit/hanami/cli/commands/gem/new_spec.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/hanami/cli/generators/gem/app/action.erb b/lib/hanami/cli/generators/gem/app/action.erb index c573be65..56eab2e4 100644 --- a/lib/hanami/cli/generators/gem/app/action.erb +++ b/lib/hanami/cli/generators/gem/app/action.erb @@ -5,5 +5,6 @@ require "hanami/action" module <%= camelized_app_name %> class Action < Hanami::Action + include Dry::Monads[:result] end end diff --git a/spec/unit/hanami/cli/commands/gem/new_spec.rb b/spec/unit/hanami/cli/commands/gem/new_spec.rb index e476107c..c535aaa2 100644 --- a/spec/unit/hanami/cli/commands/gem/new_spec.rb +++ b/spec/unit/hanami/cli/commands/gem/new_spec.rb @@ -341,6 +341,7 @@ class Routes < Hanami::Routes module #{inflector.camelize(app)} class Action < Hanami::Action + include Dry::Monads[:result] end end EXPECTED From c2c54c9aa07afeb3406c6c432616fec0b4bee2b2 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Mon, 1 Jul 2024 13:17:48 -0600 Subject: [PATCH 20/25] Add explanatory comment, and include monads result on Slice action Add dry-monads include for slice base action --- .../cli/generators/app/slice/action.erb | 4 +++ lib/hanami/cli/generators/context.rb | 6 +++++ lib/hanami/cli/generators/gem/app/action.erb | 1 + .../cli/commands/app/generate/slice_spec.rb | 27 +++++++++++++++++++ spec/unit/hanami/cli/commands/gem/new_spec.rb | 1 + 5 files changed, 39 insertions(+) diff --git a/lib/hanami/cli/generators/app/slice/action.erb b/lib/hanami/cli/generators/app/slice/action.erb index 3d0a12f8..94a55360 100644 --- a/lib/hanami/cli/generators/app/slice/action.erb +++ b/lib/hanami/cli/generators/app/slice/action.erb @@ -3,5 +3,9 @@ module <%= camelized_slice_name %> class Action < <%= camelized_app_name %>::Action + <%- if bundled_dry_monads? -%> + # Provide `Success` and `Failure` for pattern matching on operation results + include Dry::Monads[:result] + <%- end -%> end end diff --git a/lib/hanami/cli/generators/context.rb b/lib/hanami/cli/generators/context.rb index eb57b63b..fed0e925 100644 --- a/lib/hanami/cli/generators/context.rb +++ b/lib/hanami/cli/generators/context.rb @@ -92,6 +92,12 @@ def bundled_assets? Hanami.bundled?("hanami-assets") end + # @since 2.2.0 + # @api private + def bundled_dry_monads? + Hanami.bundled?("dry-monads") + end + # @since 2.1.0 # @api private # diff --git a/lib/hanami/cli/generators/gem/app/action.erb b/lib/hanami/cli/generators/gem/app/action.erb index 56eab2e4..e7fa33c5 100644 --- a/lib/hanami/cli/generators/gem/app/action.erb +++ b/lib/hanami/cli/generators/gem/app/action.erb @@ -5,6 +5,7 @@ require "hanami/action" module <%= camelized_app_name %> class Action < Hanami::Action + # Provide `Success` and `Failure` for pattern matching on operation results include Dry::Monads[:result] end end diff --git a/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb b/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb index c05825f2..54b1a1f4 100644 --- a/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb @@ -242,6 +242,33 @@ class Routes < Hanami::Routes end end + context "with dry-monads bundled" do + before do + allow(Hanami).to receive(:bundled?).with("dry-monads").and_return(bundled_assets) + end + + it "generates a slice with an operation that includes dry-monads result" do + within_application_directory do + subject.call(name: slice) + + action = <<~CODE + # auto_register: false + # frozen_string_literal: true + + module Admin + class Action < #{app}::Action + # Provide `Success` and `Failure` for pattern matching on operation results + include Dry::Monads[:result] + end + end + CODE + + expect(fs.read("slices/#{slice}/action.rb")).to eq(action) + expect(output).to include("Created slices/#{slice}/action.rb") + end + end + end + private def within_application_directory diff --git a/spec/unit/hanami/cli/commands/gem/new_spec.rb b/spec/unit/hanami/cli/commands/gem/new_spec.rb index c535aaa2..ecf0711c 100644 --- a/spec/unit/hanami/cli/commands/gem/new_spec.rb +++ b/spec/unit/hanami/cli/commands/gem/new_spec.rb @@ -341,6 +341,7 @@ class Routes < Hanami::Routes module #{inflector.camelize(app)} class Action < Hanami::Action + # Provide `Success` and `Failure` for pattern matching on operation results include Dry::Monads[:result] end end From 9933c8a8bd55793ff25fb333765325bbc1244be7 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Mon, 1 Jul 2024 14:45:38 -0600 Subject: [PATCH 21/25] Register command so it's available --- lib/hanami/cli/commands/app.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/hanami/cli/commands/app.rb b/lib/hanami/cli/commands/app.rb index 051463cf..ea3ad300 100644 --- a/lib/hanami/cli/commands/app.rb +++ b/lib/hanami/cli/commands/app.rb @@ -41,11 +41,12 @@ def self.extended(base) end register "generate", aliases: ["g"] do |prefix| - prefix.register "slice", Generate::Slice prefix.register "action", Generate::Action - prefix.register "view", Generate::View - prefix.register "part", Generate::Part prefix.register "component", Generate::Component + prefix.register "slice", Generate::Slice + prefix.register "operation", Generate::Operation + prefix.register "part", Generate::Part + prefix.register "view", Generate::View end end end From 9fc5e31ea4266ce5d1584672c232ecde40c7da51 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Mon, 1 Jul 2024 16:15:29 -0600 Subject: [PATCH 22/25] Require dry-monads --- lib/hanami/cli/generators/gem/app/action.erb | 1 + spec/unit/hanami/cli/commands/gem/new_spec.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/hanami/cli/generators/gem/app/action.erb b/lib/hanami/cli/generators/gem/app/action.erb index e7fa33c5..2126d8a5 100644 --- a/lib/hanami/cli/generators/gem/app/action.erb +++ b/lib/hanami/cli/generators/gem/app/action.erb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "hanami/action" +require "dry/monads" module <%= camelized_app_name %> class Action < Hanami::Action diff --git a/spec/unit/hanami/cli/commands/gem/new_spec.rb b/spec/unit/hanami/cli/commands/gem/new_spec.rb index ecf0711c..f6c86fe8 100644 --- a/spec/unit/hanami/cli/commands/gem/new_spec.rb +++ b/spec/unit/hanami/cli/commands/gem/new_spec.rb @@ -338,6 +338,7 @@ class Routes < Hanami::Routes # frozen_string_literal: true require "hanami/action" + require "dry/monads" module #{inflector.camelize(app)} class Action < Hanami::Action From ad0431d6de690d95c3babcca7e0393ac22e6e9d3 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Mon, 1 Jul 2024 16:44:50 -0600 Subject: [PATCH 23/25] Add require for slice action --- lib/hanami/cli/generators/app/slice/action.erb | 4 ++++ spec/unit/hanami/cli/commands/app/generate/slice_spec.rb | 2 ++ 2 files changed, 6 insertions(+) diff --git a/lib/hanami/cli/generators/app/slice/action.erb b/lib/hanami/cli/generators/app/slice/action.erb index 94a55360..e91af549 100644 --- a/lib/hanami/cli/generators/app/slice/action.erb +++ b/lib/hanami/cli/generators/app/slice/action.erb @@ -1,6 +1,10 @@ # auto_register: false # frozen_string_literal: true +<%- if bundled_dry_monads? -%> +require "dry/monads" + +<%- end -%> module <%= camelized_slice_name %> class Action < <%= camelized_app_name %>::Action <%- if bundled_dry_monads? -%> diff --git a/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb b/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb index 54b1a1f4..f313b12f 100644 --- a/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb @@ -255,6 +255,8 @@ class Routes < Hanami::Routes # auto_register: false # frozen_string_literal: true + require "dry/monads" + module Admin class Action < #{app}::Action # Provide `Success` and `Failure` for pattern matching on operation results From 0095497c84fc0ab1d7e6b575e392c8881518bbb5 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Mon, 1 Jul 2024 16:49:12 -0600 Subject: [PATCH 24/25] Change note to past tense --- lib/hanami/cli/generators/app/operation.rb | 4 ++-- spec/unit/hanami/cli/commands/app/generate/operation_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/hanami/cli/generators/app/operation.rb b/lib/hanami/cli/generators/app/operation.rb index 67c6e035..52754dd1 100644 --- a/lib/hanami/cli/generators/app/operation.rb +++ b/lib/hanami/cli/generators/app/operation.rb @@ -45,7 +45,7 @@ def generate_for_slice(context, slice) else fs.mkdir(directory = fs.join(slice_directory)) fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_slice_operation.erb", context)) - out.puts(" Generating a top-level operation. To generate into a directory, add a namespace: `my_namespace.#{context.name}`") + out.puts(" Note: We generated a top-level operation. To generate into a directory, add a namespace: `my_namespace.#{context.name}`") end end @@ -55,7 +55,7 @@ def generate_for_app(context) fs.write(fs.join(directory, "#{context.name}.rb"), t("nested_app_operation.erb", context)) else fs.mkdir(directory = fs.join("app")) - out.puts(" Generating a top-level operation. To generate into a directory, add a namespace: `my_namespace.#{context.name}`") + out.puts(" Note: We generated a top-level operation. To generate into a directory, add a namespace: `my_namespace.#{context.name}`") fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_app_operation.erb", context)) end end diff --git a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb index 9655ad71..b190f68a 100644 --- a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -34,7 +34,7 @@ def call expect(fs.read("app/add_book.rb")).to eq(operation_file) expect(output).to include("Created app/add_book.rb") expect(output).to include( - " Generating a top-level operation. " \ + " Note: We generated a top-level operation. " \ "To generate into a directory, add a namespace: `my_namespace.add_book`" ) end @@ -103,7 +103,7 @@ def call expect(fs.read("slices/main/add_book.rb")).to eq(operation_file) expect(output).to include("Created slices/main/add_book.rb") expect(output).to include( - " Generating a top-level operation. " \ + " Note: We generated a top-level operation. " \ "To generate into a directory, add a namespace: `my_namespace.add_book`" ) end From e60248dcc9e06247d9b4a5b91792b2f0e72206a7 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Fri, 5 Jul 2024 13:12:04 -0600 Subject: [PATCH 25/25] Remove inlude Dry::Monads in slice Action --- lib/hanami/cli/generators/app/slice/action.erb | 8 -------- spec/unit/hanami/cli/commands/app/generate/slice_spec.rb | 4 ---- 2 files changed, 12 deletions(-) diff --git a/lib/hanami/cli/generators/app/slice/action.erb b/lib/hanami/cli/generators/app/slice/action.erb index e91af549..3d0a12f8 100644 --- a/lib/hanami/cli/generators/app/slice/action.erb +++ b/lib/hanami/cli/generators/app/slice/action.erb @@ -1,15 +1,7 @@ # auto_register: false # frozen_string_literal: true -<%- if bundled_dry_monads? -%> -require "dry/monads" - -<%- end -%> module <%= camelized_slice_name %> class Action < <%= camelized_app_name %>::Action - <%- if bundled_dry_monads? -%> - # Provide `Success` and `Failure` for pattern matching on operation results - include Dry::Monads[:result] - <%- end -%> end end diff --git a/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb b/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb index f313b12f..a2611854 100644 --- a/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/slice_spec.rb @@ -255,12 +255,8 @@ class Routes < Hanami::Routes # auto_register: false # frozen_string_literal: true - require "dry/monads" - module Admin class Action < #{app}::Action - # Provide `Success` and `Failure` for pattern matching on operation results - include Dry::Monads[:result] end end CODE