From 9a99784b4577e9f9723f579f563d7fb2da6136d0 Mon Sep 17 00:00:00 2001 From: Max Mitchell Date: Thu, 14 Nov 2024 16:02:54 -0600 Subject: [PATCH 1/7] wip: added overwrite prevention and action specs --- lib/hanami/cli/commands/app/db/migrate.rb | 2 +- .../cli/commands/app/db/utils/postgres.rb | 4 +- .../cli/commands/app/db/utils/sqlite.rb | 14 +- .../cli/commands/app/generate/action.rb | 3 +- lib/hanami/cli/commands/app/generate/slice.rb | 16 +- lib/hanami/cli/commands/app/install.rb | 2 +- lib/hanami/cli/commands/gem/new.rb | 4 +- lib/hanami/cli/errors.rb | 7 + lib/hanami/cli/files.rb | 16 +- lib/hanami/cli/generators/app/action.rb | 16 +- lib/hanami/cli/generators/app/component.rb | 4 +- lib/hanami/cli/generators/app/migration.rb | 2 +- lib/hanami/cli/generators/app/part.rb | 4 +- lib/hanami/cli/generators/app/slice.rb | 36 +- lib/hanami/cli/generators/app/view.rb | 12 +- lib/hanami/cli/generators/gem/app.rb | 72 +-- .../cli/commands/app/db/prepare_spec.rb | 10 +- .../cli/commands/app/generate/action_spec.rb | 534 ++++++++---------- 18 files changed, 370 insertions(+), 388 deletions(-) diff --git a/lib/hanami/cli/commands/app/db/migrate.rb b/lib/hanami/cli/commands/app/db/migrate.rb index 6b4c3dbb..6d437929 100644 --- a/lib/hanami/cli/commands/app/db/migrate.rb +++ b/lib/hanami/cli/commands/app/db/migrate.rb @@ -12,7 +12,7 @@ class Migrate < DB::Command option :gateway, required: false, desc: "Use database for gateway" option :target, desc: "Target migration number", aliases: ["-t"] option :dump, required: false, type: :boolean, default: true, - desc: "Dump the database structure after migrating" + desc: "Dump the database structure after migrating" def call(target: nil, app: false, slice: nil, gateway: nil, dump: true, command_exit: method(:exit), **) databases(app: app, slice: slice, gateway: gateway).each do |database| diff --git a/lib/hanami/cli/commands/app/db/utils/postgres.rb b/lib/hanami/cli/commands/app/db/utils/postgres.rb index 3d10c154..06131096 100644 --- a/lib/hanami/cli/commands/app/db/utils/postgres.rb +++ b/lib/hanami/cli/commands/app/db/utils/postgres.rb @@ -57,12 +57,12 @@ def exec_load_command def schema_migrations_sql_dump migrations_sql = super return unless migrations_sql - + search_path = gateway.connection .fetch("SHOW search_path").to_a.first .fetch(:search_path) - +"SET search_path TO #{search_path};\n\n" << migrations_sql + "SET search_path TO #{search_path};\n\n" << migrations_sql end private diff --git a/lib/hanami/cli/commands/app/db/utils/sqlite.rb b/lib/hanami/cli/commands/app/db/utils/sqlite.rb index d3f9c6cc..16056076 100644 --- a/lib/hanami/cli/commands/app/db/utils/sqlite.rb +++ b/lib/hanami/cli/commands/app/db/utils/sqlite.rb @@ -38,7 +38,7 @@ def exec_create_command def exec_drop_command begin File.unlink(file_path) if exists? - rescue => e + rescue StandardError => e # Mimic a system_call result return Failure.new(e.message) end @@ -76,13 +76,11 @@ def name private def file_path - @file_path ||= begin - if File.absolute_path?(name) - name - else - slice.app.root.join(name).to_s - end - end + @file_path ||= if File.absolute_path?(name) + name + else + slice.app.root.join(name).to_s + end end end end diff --git a/lib/hanami/cli/commands/app/generate/action.rb b/lib/hanami/cli/commands/app/generate/action.rb index 226aea86..75d5faba 100644 --- a/lib/hanami/cli/commands/app/generate/action.rb +++ b/lib/hanami/cli/commands/app/generate/action.rb @@ -106,7 +106,8 @@ def call( raise InvalidActionNameError.new(name) end - generator.call(app.namespace, controller, action, url, http, format, skip_view, skip_route, slice, context: context) + generator.call(app.namespace, controller, action, url, http, format, skip_view, skip_route, slice, + context: context) end # rubocop:enable Metrics/ParameterLists diff --git a/lib/hanami/cli/commands/app/generate/slice.rb b/lib/hanami/cli/commands/app/generate/slice.rb index b6201d9c..4a4eaaec 100644 --- a/lib/hanami/cli/commands/app/generate/slice.rb +++ b/lib/hanami/cli/commands/app/generate/slice.rb @@ -29,17 +29,17 @@ class Slice < App::Command # @since 2.2.0 # @api private option :skip_db, - type: :flag, - required: false, - default: SKIP_DB_DEFAULT, - desc: "Skip database" + type: :flag, + required: false, + default: SKIP_DB_DEFAULT, + desc: "Skip database" # @since 2.2.0 # @api private option :skip_route, - type: :flag, - required: false, - default: DEFAULT_SKIP_ROUTE, - desc: "Skip route generation" + type: :flag, + required: false, + default: DEFAULT_SKIP_ROUTE, + desc: "Skip route generation" example [ "admin # Admin slice (/admin URL prefix)", diff --git a/lib/hanami/cli/commands/app/install.rb b/lib/hanami/cli/commands/app/install.rb index 7d383226..f9b96ee0 100644 --- a/lib/hanami/cli/commands/app/install.rb +++ b/lib/hanami/cli/commands/app/install.rb @@ -39,7 +39,7 @@ class Install < Command def initialize( fs:, bundler: CLI::Bundler.new(fs: fs), - **opts + **_opts ) @bundler = bundler end diff --git a/lib/hanami/cli/commands/gem/new.rb b/lib/hanami/cli/commands/gem/new.rb index 1c9ea042..6771f9cb 100644 --- a/lib/hanami/cli/commands/gem/new.rb +++ b/lib/hanami/cli/commands/gem/new.rb @@ -113,7 +113,7 @@ def initialize( @system_call = system_call end - # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity + # rubocop:disable Metrics/AbcSize # @since 2.0.0 # @api private @@ -166,7 +166,7 @@ def call( end end end - # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity + # rubocop:enable Metrics/AbcSize private diff --git a/lib/hanami/cli/errors.rb b/lib/hanami/cli/errors.rb index def13a8a..05b8718f 100644 --- a/lib/hanami/cli/errors.rb +++ b/lib/hanami/cli/errors.rb @@ -44,6 +44,13 @@ def initialize(path) end end + # @api public + class FileAlreadyExistsError < Error + def initialize(path) + super("Cannot overwrite existing file: `#{path}`") + end + end + # @api public class ForbiddenAppNameError < Error def initialize(name) diff --git a/lib/hanami/cli/files.rb b/lib/hanami/cli/files.rb index 529aa462..ed01625a 100644 --- a/lib/hanami/cli/files.rb +++ b/lib/hanami/cli/files.rb @@ -14,6 +14,14 @@ def initialize(out: $stdout, **args) @out = out end + # @since 2.3.0 + # @api private + def create(path, *content) + raise FileAlreadyExistsError.new(path) if exist?(path) + + write(path, *content) + end + # @since 2.0.0 # @api private def write(path, *content) @@ -33,10 +41,10 @@ def write(path, *content) # @since 2.0.0 # @api private def mkdir(path) - unless exist?(path) - super - created(_path(path)) - end + return if exist?(path) + + super + created(_path(path)) end # @since 2.0.0 diff --git a/lib/hanami/cli/generators/app/action.rb b/lib/hanami/cli/generators/app/action.rb index 024e4687..6e18da7f 100644 --- a/lib/hanami/cli/generators/app/action.rb +++ b/lib/hanami/cli/generators/app/action.rb @@ -85,15 +85,15 @@ def generate_for_slice(controller, action, url, http, format, skip_view, skip_ro end fs.mkdir(directory = fs.join(slice_directory, "actions", controller)) - fs.write(fs.join(directory, "#{action}.rb"), t("slice_action.erb", context)) + fs.create(fs.join(directory, "#{action}.rb"), t("slice_action.erb", context)) if generate_view?(skip_view, action, directory) fs.mkdir(directory = fs.join(slice_directory, "views", controller)) - fs.write(fs.join(directory, "#{action}.rb"), t("slice_view.erb", context)) + fs.create(fs.join(directory, "#{action}.rb"), t("slice_view.erb", context)) fs.mkdir(directory = fs.join(slice_directory, "templates", controller)) - fs.write(fs.join(directory, "#{action}.#{format}.erb"), - t(template_with_format_ext("slice_template", format), context)) + fs.create(fs.join(directory, "#{action}.#{format}.erb"), + t(template_with_format_ext("slice_template", format), context)) end end @@ -107,18 +107,18 @@ def generate_for_app(controller, action, url, http, format, skip_view, skip_rout end fs.mkdir(directory = fs.join("app", "actions", controller)) - fs.write(fs.join(directory, "#{action}.rb"), t("action.erb", context)) + fs.create(fs.join(directory, "#{action}.rb"), t("action.erb", context)) view = action view_directory = fs.join("app", "views", controller) if generate_view?(skip_view, view, view_directory) fs.mkdir(view_directory) - fs.write(fs.join(view_directory, "#{view}.rb"), t("view.erb", context)) + fs.create(fs.join(view_directory, "#{view}.rb"), t("view.erb", context)) fs.mkdir(template_directory = fs.join("app", "templates", controller)) - fs.write(fs.join(template_directory, "#{view}.#{format}.erb"), - t(template_with_format_ext("template", format), context)) + fs.create(fs.join(template_directory, "#{view}.#{format}.erb"), + t(template_with_format_ext("template", format), context)) end end # rubocop:enable Metrics/AbcSize diff --git a/lib/hanami/cli/generators/app/component.rb b/lib/hanami/cli/generators/app/component.rb index 0d1143a4..890c803c 100644 --- a/lib/hanami/cli/generators/app/component.rb +++ b/lib/hanami/cli/generators/app/component.rb @@ -40,12 +40,12 @@ def generate_for_slice(context, slice) raise MissingSliceError.new(slice) unless fs.directory?(slice_directory) fs.mkdir(directory = fs.join(slice_directory, context.namespaces)) - fs.write(fs.join(directory, "#{context.underscored_name}.rb"), t("slice_component.erb", context)) + fs.create(fs.join(directory, "#{context.underscored_name}.rb"), t("slice_component.erb", context)) end def generate_for_app(context) fs.mkdir(directory = fs.join("app", context.namespaces)) - fs.write(fs.join(directory, "#{context.underscored_name}.rb"), t("component.erb", context)) + fs.create(fs.join(directory, "#{context.underscored_name}.rb"), t("component.erb", context)) end def template(path, context) diff --git a/lib/hanami/cli/generators/app/migration.rb b/lib/hanami/cli/generators/app/migration.rb index 858c6799..03893628 100644 --- a/lib/hanami/cli/generators/app/migration.rb +++ b/lib/hanami/cli/generators/app/migration.rb @@ -26,7 +26,7 @@ def call(key:, base_path:, gateway: nil, **_opts) path = fs.join(*[base_path, "config", "db", migrate_dir, file_name(name)].compact) - fs.write(path, FILE_CONTENTS) + fs.create(path, FILE_CONTENTS) end private diff --git a/lib/hanami/cli/generators/app/part.rb b/lib/hanami/cli/generators/app/part.rb index 3a1c039d..c749f2ed 100644 --- a/lib/hanami/cli/generators/app/part.rb +++ b/lib/hanami/cli/generators/app/part.rb @@ -50,7 +50,7 @@ def generate_for_slice(context, slice) generate_base_part_for_slice(context, slice) fs.mkdir(directory = fs.join(slice_directory, "views", "parts", *context.underscored_namespace)) - fs.write(fs.join(directory, "#{context.underscored_name}.rb"), t("slice_part.erb", context)) + fs.create(fs.join(directory, "#{context.underscored_name}.rb"), t("slice_part.erb", context)) end # @since 2.1.0 @@ -59,7 +59,7 @@ def generate_for_app(context) generate_base_part_for_app(context) fs.mkdir(directory = fs.join("app", "views", "parts", *context.underscored_namespace)) - fs.write(fs.join(directory, "#{context.underscored_name}.rb"), t("app_part.erb", context)) + fs.create(fs.join(directory, "#{context.underscored_name}.rb"), t("app_part.erb", context)) end # @since 2.1.0 diff --git a/lib/hanami/cli/generators/app/slice.rb b/lib/hanami/cli/generators/app/slice.rb index a355c76b..033db87d 100644 --- a/lib/hanami/cli/generators/app/slice.rb +++ b/lib/hanami/cli/generators/app/slice.rb @@ -30,33 +30,33 @@ def call(app, slice, url, context: nil, **opts) fs.mkdir(directory = "slices/#{slice}") - fs.write(fs.join(directory, "action.rb"), t("action.erb", context)) - 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)) + fs.create(fs.join(directory, "action.rb"), t("action.erb", context)) + fs.create(fs.join(directory, "view.rb"), t("view.erb", context)) + fs.create(fs.join(directory, "views", "helpers.rb"), t("helpers.erb", context)) + fs.create(fs.join(directory, "templates", "layouts", "app.html.erb"), t("app_layout.erb", context)) + fs.create(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)) - fs.write(fs.join(directory, "assets", "css", "app.css"), t("app_css.erb", context)) - fs.write(fs.join(directory, "assets", "images", "favicon.ico"), file("favicon.ico")) + fs.create(fs.join(directory, "assets", "js", "app.js"), t("app_js.erb", context)) + fs.create(fs.join(directory, "assets", "css", "app.css"), t("app_css.erb", context)) + fs.create(fs.join(directory, "assets", "images", "favicon.ico"), file("favicon.ico")) end if context.generate_db? - fs.write(fs.join(directory, "db", "relation.rb"), t("relation.erb", context)) - fs.write(fs.join(directory, "relations", ".keep"), t("keep.erb", context)) + fs.create(fs.join(directory, "db", "relation.rb"), t("relation.erb", context)) + fs.create(fs.join(directory, "relations", ".keep"), t("keep.erb", context)) - fs.write(fs.join(directory, "db", "repo.rb"), t("repo.erb", context)) - fs.write(fs.join(directory, "repos", ".keep"), t("keep.erb", context)) + fs.create(fs.join(directory, "db", "repo.rb"), t("repo.erb", context)) + fs.create(fs.join(directory, "repos", ".keep"), t("keep.erb", context)) - fs.write(fs.join(directory, "db", "struct.rb"), t("struct.erb", context)) - fs.write(fs.join(directory, "structs", ".keep"), t("keep.erb", context)) + fs.create(fs.join(directory, "db", "struct.rb"), t("struct.erb", context)) + fs.create(fs.join(directory, "structs", ".keep"), t("keep.erb", context)) end - fs.write(fs.join(directory, "actions/.keep"), t("keep.erb", context)) - fs.write(fs.join(directory, "views/.keep"), t("keep.erb", context)) - fs.write(fs.join(directory, "templates/.keep"), t("keep.erb", context)) - fs.write(fs.join(directory, "templates/layouts/.keep"), t("keep.erb", context)) + fs.create(fs.join(directory, "actions/.keep"), t("keep.erb", context)) + fs.create(fs.join(directory, "views/.keep"), t("keep.erb", context)) + fs.create(fs.join(directory, "templates/.keep"), t("keep.erb", context)) + fs.create(fs.join(directory, "templates/layouts/.keep"), t("keep.erb", context)) end private diff --git a/lib/hanami/cli/generators/app/view.rb b/lib/hanami/cli/generators/app/view.rb index 7078977d..f6675438 100644 --- a/lib/hanami/cli/generators/app/view.rb +++ b/lib/hanami/cli/generators/app/view.rb @@ -43,20 +43,20 @@ def generate_for_slice(context, format, slice) raise MissingSliceError.new(slice) unless fs.directory?(slice_directory) fs.mkdir(directory = fs.join(slice_directory, "views", context.namespaces)) - fs.write(fs.join(directory, "#{context.name}.rb"), t("slice_view.erb", context)) + fs.create(fs.join(directory, "#{context.name}.rb"), t("slice_view.erb", context)) fs.mkdir(directory = fs.join(slice_directory, "templates", context.namespaces)) - fs.write(fs.join(directory, "#{context.name}.#{format}.erb"), - t(template_with_format_ext("slice_template", format), context)) + fs.create(fs.join(directory, "#{context.name}.#{format}.erb"), + t(template_with_format_ext("slice_template", format), context)) end def generate_for_app(context, format, _slice) fs.mkdir(directory = fs.join("app", "views", context.namespaces)) - fs.write(fs.join(directory, "#{context.name}.rb"), t("app_view.erb", context)) + fs.create(fs.join(directory, "#{context.name}.rb"), t("app_view.erb", context)) fs.mkdir(directory = fs.join("app", "templates", context.namespaces)) - fs.write(fs.join(directory, "#{context.name}.#{format}.erb"), - t(template_with_format_ext("app_template", format), context)) + fs.create(fs.join(directory, "#{context.name}.#{format}.erb"), + t(template_with_format_ext("app_template", format), context)) end # rubocop:enable Metrics/AbcSize diff --git a/lib/hanami/cli/generators/gem/app.rb b/lib/hanami/cli/generators/gem/app.rb index 2f68b95c..19ce5c34 100644 --- a/lib/hanami/cli/generators/gem/app.rb +++ b/lib/hanami/cli/generators/gem/app.rb @@ -34,62 +34,62 @@ def call(app, context: Context.new(inflector, app), &blk) attr_reader :inflector def generate_app(app, context) # rubocop:disable Metrics/AbcSize - fs.write(".gitignore", t("gitignore.erb", context)) - fs.write(".env", t("env.erb", context)) + fs.create(".gitignore", t("gitignore.erb", context)) + fs.create(".env", t("env.erb", context)) - fs.write("README.md", t("readme.erb", context)) - fs.write("Gemfile", t("gemfile.erb", context)) - fs.write("Rakefile", t("rakefile.erb", context)) - fs.write("Procfile.dev", t("procfile.erb", context)) - fs.write("config.ru", t("config_ru.erb", context)) + fs.create("README.md", t("readme.erb", context)) + fs.create("Gemfile", t("gemfile.erb", context)) + fs.create("Rakefile", t("rakefile.erb", context)) + fs.create("Procfile.dev", t("procfile.erb", context)) + fs.create("config.ru", t("config_ru.erb", context)) - fs.write("bin/dev", file("dev")) + fs.create("bin/dev", file("dev")) fs.chmod("bin/dev", 0o755) - fs.write("config/app.rb", t("app.erb", context)) - fs.write("config/settings.rb", t("settings.erb", context)) - fs.write("config/routes.rb", t("routes.erb", context)) - fs.write("config/puma.rb", t("puma.erb", context)) + fs.create("config/app.rb", t("app.erb", context)) + fs.create("config/settings.rb", t("settings.erb", context)) + fs.create("config/routes.rb", t("routes.erb", context)) + fs.create("config/puma.rb", t("puma.erb", context)) - fs.write("lib/tasks/.keep", t("keep.erb", context)) - fs.write("lib/#{app}/types.rb", t("types.erb", context)) + fs.create("lib/tasks/.keep", t("keep.erb", context)) + fs.create("lib/#{app}/types.rb", t("types.erb", context)) - fs.write("app/actions/.keep", t("keep.erb", context)) - fs.write("app/action.rb", t("action.erb", context)) - fs.write("app/view.rb", t("view.erb", context)) - fs.write("app/views/helpers.rb", t("helpers.erb", context)) - fs.write("app/templates/layouts/app.html.erb", t("app_layout.erb", context)) + fs.create("app/actions/.keep", t("keep.erb", context)) + fs.create("app/action.rb", t("action.erb", context)) + fs.create("app/view.rb", t("view.erb", context)) + fs.create("app/views/helpers.rb", t("helpers.erb", context)) + fs.create("app/templates/layouts/app.html.erb", t("app_layout.erb", context)) if context.generate_assets? - fs.write("package.json", t("package.json.erb", context)) - fs.write("config/assets.js", file("assets.js")) - fs.write("app/assets/js/app.js", t("app_js.erb", context)) - fs.write("app/assets/css/app.css", t("app_css.erb", context)) - fs.write("app/assets/images/favicon.ico", file("favicon.ico")) + fs.create("package.json", t("package.json.erb", context)) + fs.create("config/assets.js", file("assets.js")) + fs.create("app/assets/js/app.js", t("app_js.erb", context)) + fs.create("app/assets/css/app.css", t("app_css.erb", context)) + fs.create("app/assets/images/favicon.ico", file("favicon.ico")) end if context.generate_db? - fs.write("app/db/relation.rb", t("relation.erb", context)) - fs.write("app/relations/.keep", t("keep.erb", context)) + fs.create("app/db/relation.rb", t("relation.erb", context)) + fs.create("app/relations/.keep", t("keep.erb", context)) - fs.write("app/db/repo.rb", t("repo.erb", context)) - fs.write("app/repos/.keep", t("keep.erb", context)) + fs.create("app/db/repo.rb", t("repo.erb", context)) + fs.create("app/repos/.keep", t("keep.erb", context)) - fs.write("app/db/struct.rb", t("struct.erb", context)) - fs.write("app/structs/.keep", t("keep.erb", context)) + fs.create("app/db/struct.rb", t("struct.erb", context)) + fs.create("app/structs/.keep", t("keep.erb", context)) - fs.write("config/db/seeds.rb", t("seeds.erb", context)) - fs.write("config/db/migrate/.keep", t("keep.erb", context)) + fs.create("config/db/seeds.rb", t("seeds.erb", context)) + fs.create("config/db/migrate/.keep", t("keep.erb", context)) if context.generate_sqlite? - fs.write("db/.keep", t("keep.erb", context)) + fs.create("db/.keep", t("keep.erb", context)) end end - fs.write("app/operation.rb", t("operation.erb", context)) + fs.create("app/operation.rb", t("operation.erb", context)) - fs.write("public/404.html", file("404.html")) - fs.write("public/500.html", file("500.html")) + fs.create("public/404.html", file("404.html")) + fs.create("public/500.html", file("500.html")) end def template(path, context) diff --git a/spec/unit/hanami/cli/commands/app/db/prepare_spec.rb b/spec/unit/hanami/cli/commands/app/db/prepare_spec.rb index 8b5f8ce2..11203d55 100644 --- a/spec/unit/hanami/cli/commands/app/db/prepare_spec.rb +++ b/spec/unit/hanami/cli/commands/app/db/prepare_spec.rb @@ -291,7 +291,7 @@ class Comments < Hanami::DB::Relation expect(output).to include_in_order( "database #{POSTGRES_BASE_DB_NAME}_app created", "database #{POSTGRES_BASE_DB_NAME}_app migrated", - "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql", + "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql", "seed data loaded from config/db/seeds.rb", ) expect(output).not_to include "#{POSTGRES_BASE_DB_NAME}_main" @@ -310,7 +310,7 @@ class Comments < Hanami::DB::Relation expect(output).to include_in_order( "database #{POSTGRES_BASE_DB_NAME}_main created", "database #{POSTGRES_BASE_DB_NAME}_main migrated", - "#{POSTGRES_BASE_DB_NAME}_main structure dumped to slices/main/config/db/structure.sql", + "#{POSTGRES_BASE_DB_NAME}_main structure dumped to slices/main/config/db/structure.sql", "seed data loaded from slices/main/config/db/seeds.rb" ) expect(output).not_to include "#{POSTGRES_BASE_DB_NAME}_app" @@ -349,7 +349,7 @@ class Comments < Hanami::DB::Relation expect(output).to include_in_order( "database #{POSTGRES_BASE_DB_NAME}_app created", "database #{POSTGRES_BASE_DB_NAME}_app migrated", - "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql", + "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql", "seed data loaded from config/db/seeds.rb", ) end @@ -377,7 +377,7 @@ class Comments < Hanami::DB::Relation expect(output).to include_in_order( "database #{POSTGRES_BASE_DB_NAME}_app created", # TODO: it would be good not to include this "database #{POSTGRES_BASE_DB_NAME}_app migrated", - "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql", + "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql", "seed data loaded from config/db/seeds.rb", ) end @@ -405,7 +405,7 @@ class Comments < Hanami::DB::Relation expect(output).to include_in_order( "database #{POSTGRES_BASE_DB_NAME}_app created", # TODO: it would be good not to include this "database #{POSTGRES_BASE_DB_NAME}_app migrated", - "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql" + "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql" ) expect(output).not_to include "seed data loaded from config/db/seeds.rb" 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 c895b226..f9561e97 100644 --- a/spec/unit/hanami/cli/commands/app/generate/action_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/action_spec.rb @@ -20,6 +20,69 @@ def output out.rewind && out.read.chomp end + shared_context "with existing files" do + context "with existing route file" do + it "generates action without error" do + within_application_directory do + generate_action + + expect(output).to include("Updated config/routes.rb") + expect(output).to include("Created app/actions/#{controller}/#{action}.rb") + expect(output).to include("Created app/views/#{controller}/#{action}.rb") + expect(output).to include("Created app/templates/#{controller}/#{action}.html.erb") + end + end + end + + context "with existing action file" do + before do + within_application_directory do + fs.write("app/actions/#{controller}/#{action}.rb", "") + end + end + + it "raises error" do + expect { + within_application_directory do + generate_action + end + }.to raise_error(Hanami::CLI::FileAlreadyExistsError, "Cannot overwrite existing file: `app/actions/#{controller}/#{action}.rb`") + end + end + + context "with existing view file" do + before do + within_application_directory do + fs.write("app/views/#{controller}/#{action}.rb", "") + end + end + + it "raises error" do + expect { + within_application_directory do + generate_action + end + }.to raise_error(Hanami::CLI::FileAlreadyExistsError, "Cannot overwrite existing file: `app/views/#{controller}/#{action}.rb`") + end + end + + context "with existing template file" do + before do + within_application_directory do + fs.write("app/templates/#{controller}/#{action}.html.erb", "") + end + end + + it "raises error" do + expect { + within_application_directory do + generate_action + end + }.to raise_error(Hanami::CLI::FileAlreadyExistsError, "Cannot overwrite existing file: `app/templates/#{controller}/#{action}.html.erb`") + end + end + end + context "generate for app, without hanami view bundled" do it "generates action" do within_application_directory do @@ -88,7 +151,7 @@ class #{inflector.camelize(action)} < #{inflector.camelize(app)}::View EXPECTED expect(fs.read("app/templates/#{controller}/#{action}.html.erb")).to eq(template_file) - expect(output).to include("Created app/views/#{controller}/#{action}.rb") + expect(output).to include("Created app/templates/#{controller}/#{action}.html.erb") end end @@ -233,13 +296,20 @@ def handle(request, response) expect(output).to_not include("Updated config/routes.rb") end end + + include_context "with existing files" do + let(:generate_action) { subject.call(name: action_name) } + end end context "generate for app, with hanami view bundled" do - it "generates action" do - context = Hanami::CLI::Generators::App::ActionContext.new(inflector, app, nil, [controller], action) + let(:context) { Hanami::CLI::Generators::App::ActionContext.new(inflector, app, nil, [controller], action) } + + before do allow(context).to receive(:bundled_views?) { true } + end + it "generates action" do within_application_directory do subject.call(name: action_name, context: context) @@ -305,47 +375,45 @@ class #{inflector.camelize(action)} < #{inflector.camelize(app)}::View EXPECTED expect(fs.read("app/templates/#{controller}/#{action}.html.erb")).to eq(template_file) - expect(output).to include("Created app/views/#{controller}/#{action}.rb") + expect(output).to include("Created app/templates/#{controller}/#{action}.html.erb") end end - it "allows to specify nested action name" do - context = Hanami::CLI::Generators::App::ActionContext.new(inflector, app, nil, %w[api users], "thing") - allow(context).to receive(:bundled_views?) { true } + context "with nested action name" do + let(:context) { Hanami::CLI::Generators::App::ActionContext.new(inflector, app, nil, %w[api users], "thing") } - within_application_directory do - action_name = "api/users.thing" - subject.call(name: action_name, context: context) + it "allows to specify nested action name" do + within_application_directory do + action_name = "api/users.thing" + subject.call(name: action_name, context: context) - expect(fs.read("config/routes.rb")).to match(%(get "/api/users/thing", to: "api.users.thing")) - expect(output).to include("Updated config/routes.rb") + expect(fs.read("config/routes.rb")).to match(%(get "/api/users/thing", to: "api.users.thing")) + expect(output).to include("Updated config/routes.rb") - action_file = <<~EXPECTED - # frozen_string_literal: true + action_file = <<~EXPECTED + # frozen_string_literal: true - module #{inflector.camelize(app)} - module Actions - module API - module Users - class Thing < #{inflector.camelize(app)}::Action - def handle(request, response) + module #{inflector.camelize(app)} + module Actions + module API + module Users + class Thing < #{inflector.camelize(app)}::Action + def handle(request, response) + end end end end end end - end - EXPECTED + EXPECTED - expect(fs.read("app/actions/api/users/thing.rb")).to eq(action_file) - expect(output).to include("Created app/actions/api/users/thing.rb") + expect(fs.read("app/actions/api/users/thing.rb")).to eq(action_file) + expect(output).to include("Created app/actions/api/users/thing.rb") + end end end it "allows route generation to be skipped" do - context = Hanami::CLI::Generators::App::ActionContext.new(inflector, app, nil, [controller], action) - allow(context).to receive(:bundled_views?) { true } - within_application_directory do subject.call(name: action_name, context: context, skip_route: true) @@ -410,7 +478,7 @@ class #{inflector.camelize(action)} < #{inflector.camelize(app)}::View EXPECTED expect(fs.read("app/templates/#{controller}/#{action}.html.erb")).to eq(template_file) - expect(output).to include("Created app/views/#{controller}/#{action}.rb") + expect(output).to include("Created app/templates/#{controller}/#{action}.html.erb") end end @@ -419,9 +487,6 @@ class #{inflector.camelize(action)} < #{inflector.camelize(app)}::View let(:action) { "create" } it "skips view generation when New view is present" do - context = Hanami::CLI::Generators::App::ActionContext.new(inflector, app, nil, [controller], action) - allow(context).to receive(:bundled_views?) { true } - within_application_directory do # Prepare routes = <<~CODE @@ -518,9 +583,6 @@ def handle(request, response) context "when New view is NOT present" do it "generates view" do - context = Hanami::CLI::Generators::App::ActionContext.new(inflector, app, nil, [controller], action) - allow(context).to receive(:bundled_views?) { true } - within_application_directory do # Prepare routes = <<~CODE @@ -598,9 +660,6 @@ class Create < #{app}::View end it "skips view generation if --skip-view is used" do - context = Hanami::CLI::Generators::App::ActionContext.new(inflector, app, nil, [controller], action) - allow(context).to receive(:bundled_views?) { true } - within_application_directory do # Prepare routes = <<~CODE @@ -667,9 +726,6 @@ def handle(request, response) let(:action) { "update" } it "skips view generation when Edit view is present" do - context = Hanami::CLI::Generators::App::ActionContext.new(inflector, app, nil, [controller], action) - allow(context).to receive(:bundled_views?) { true } - within_application_directory do # Prepare routes = <<~CODE @@ -766,9 +822,6 @@ def handle(request, response) context "when Edit view is NOT present" do it "generates view" do - context = Hanami::CLI::Generators::App::ActionContext.new(inflector, app, nil, [controller], action) - allow(context).to receive(:bundled_views?) { true } - within_application_directory do # Prepare routes = <<~CODE @@ -846,9 +899,6 @@ class Update < #{app}::View end it "skips view if --skip-view is used" do - context = Hanami::CLI::Generators::App::ActionContext.new(inflector, app, nil, [controller], action) - allow(context).to receive(:bundled_views?) { true } - within_application_directory do # Prepare routes = <<~CODE @@ -911,6 +961,10 @@ def handle(request, response) end end end + + include_context "with existing files" do + let(:generate_action) { subject.call(name: action_name, context: context) } + end end context "generate for a slice" do @@ -1088,209 +1142,160 @@ class #{inflector.camelize(action)} < #{inflector.camelize(slice)}::View end end - context "with hanami view bundled" do - it "generates action with view" do - within_application_directory do - prepare_slice! - context = Hanami::CLI::Generators::App::ActionContext.new(inflector, app, slice, [controller], action) - allow(context).to receive(:bundled_views?) { true } + it "appends routes within the proper slice block" do + within_application_directory do + prepare_slice! + fs.mkdir("slices/api") - subject.call(name: action_name, slice: slice, context: context) + routes_contents = <<~CODE + # frozen_string_literal: true - # Route - routes = <<~CODE - # frozen_string_literal: true + require "hanami/routes" - require "hanami/routes" + module #{app} + class Routes < Hanami::Routes + root { "Hello from Hanami" } - module #{app} - class Routes < Hanami::Routes - root { "Hello from Hanami" } + slice :#{slice}, at: "/#{slice}" do + root to: "home.index" + end - slice :#{slice}, at: "/#{slice}" do - get "/users", to: "users.index" - end + slice :api, at: "/api" do + root to: "home.index" end end - CODE + end + CODE + fs.write("config/routes.rb", routes_contents) - # route - expect(fs.read("config/routes.rb")).to eq(routes) - expect(output).to include("Updated config/routes.rb") - expect(output).to include("Created slices/#{slice}/actions/#{controller}/") + expected = <<~CODE + # frozen_string_literal: true - # action - expect(fs.directory?("slices/#{slice}/actions/#{controller}")).to be(true) - expect(output).to include("Created slices/#{slice}/actions/#{controller}/") + require "hanami/routes" - action_file = <<~EXPECTED - # frozen_string_literal: true + module #{app} + class Routes < Hanami::Routes + root { "Hello from Hanami" } - module #{inflector.camelize(slice)} - module Actions - module #{inflector.camelize(controller)} - class #{inflector.camelize(action)} < #{inflector.camelize(slice)}::Action - def handle(request, response) - end - end - end + slice :#{slice}, at: "/#{slice}" do + root to: "home.index" + get "/users", to: "users.index" end - end - EXPECTED - expect(fs.read("slices/#{slice}/actions/#{controller}/#{action}.rb")).to eq(action_file) - expect(output).to include("Created slices/#{slice}/actions/#{controller}/#{action}.rb") - - # view - expect(fs.directory?("slices/#{slice}/views/#{controller}")).to be(true) - expect(output).to include("Created slices/#{slice}/views/#{controller}/") - - view_file = <<~EXPECTED - # frozen_string_literal: true - module #{inflector.camelize(slice)} - module Views - module #{inflector.camelize(controller)} - class #{inflector.camelize(action)} < #{inflector.camelize(slice)}::View - end - end + slice :api, at: "/api" do + root to: "home.index" + get "/users/:id", to: "users.show" end end - EXPECTED - expect(fs.read("slices/#{slice}/views/#{controller}/#{action}.rb")).to eq(view_file) - expect(output).to include("Created slices/#{slice}/views/#{controller}/#{action}.rb") + end + CODE - # template - expect(fs.directory?("slices/#{slice}/templates/#{controller}")).to be(true) - expect(output).to include("Created slices/#{slice}/templates/#{controller}/") + subject.call(slice: slice, name: "users.index") + subject.call(slice: "api", name: "users.show") - template_file = <<~EXPECTED -

#{inflector.camelize(slice)}::Views::#{inflector.camelize(controller)}::#{inflector.camelize(action)}

- EXPECTED - expect(fs.read("slices/#{slice}/templates/#{controller}/#{action}.html.erb")).to eq(template_file) - expect(output).to include("Created slices/#{slice}/templates/#{controller}/#{action}.html.erb") - end + expect(fs.read("config/routes.rb")).to eq(expected) end + end - context "RESTful actions" do - context "CREATE" do - let(:action) { "create" } - - it "skips view generation when New view is present" do - prepare_slice! - context = Hanami::CLI::Generators::App::ActionContext.new(inflector, app, slice, [controller], action) - allow(context).to receive(:bundled_views?) { true } + it "raises error if slice is unexisting" do + expect { subject.call(slice: "foo", name: action_name) }.to raise_error(Hanami::CLI::MissingSliceError, "slice `foo' is missing, please generate with `hanami generate slice foo'") + end + end - # Route - routes = <<~CODE - # frozen_string_literal: true + context "with hanami view bundled" do + let(:context) { Hanami::CLI::Generators::App::ActionContext.new(inflector, app, slice, [controller], action) } - require "hanami/routes" + before do + allow(context).to receive(:bundled_views?) { true } + end - module #{app} - class Routes < Hanami::Routes - root { "Hello from Hanami" } + it "generates action with view" do + within_application_directory do + prepare_slice! - slice :#{slice}, at: "/#{slice}" do - get "/users/new", to: "users.new" - end - end - end - CODE - fs.write("config/routes.rb", routes) + subject.call(name: action_name, slice: slice, context: context) - action_file = <<~EXPECTED - # frozen_string_literal: true + # Route + routes = <<~CODE + # frozen_string_literal: true - module #{inflector.camelize(slice)} - module Actions - module Users - class New < #{inflector.camelize(slice)}::Action - def handle(request, response) - end - end - end - end - end - EXPECTED - fs.write("slices/#{slice}/actions/users/new.rb", action_file) + require "hanami/routes" - view_file = <<~EXPECTED - # frozen_string_literal: true + module #{app} + class Routes < Hanami::Routes + root { "Hello from Hanami" } - module #{inflector.camelize(slice)} - module Views - module Users - class New < #{inflector.camelize(slice)}::Action - end - end - end + slice :#{slice}, at: "/#{slice}" do + get "/users", to: "users.index" end - EXPECTED - fs.write("slices/#{slice}/views/users/new.rb", view_file) - - subject.call(name: action_name, slice: slice, context: context) + end + end + CODE - expected_routes = <<~CODE - # frozen_string_literal: true + # route + expect(fs.read("config/routes.rb")).to eq(routes) + expect(output).to include("Updated config/routes.rb") + expect(output).to include("Created slices/#{slice}/actions/#{controller}/") - require "hanami/routes" + # action + expect(fs.directory?("slices/#{slice}/actions/#{controller}")).to be(true) + expect(output).to include("Created slices/#{slice}/actions/#{controller}/") - module #{app} - class Routes < Hanami::Routes - root { "Hello from Hanami" } + action_file = <<~EXPECTED + # frozen_string_literal: true - slice :#{slice}, at: "/#{slice}" do - get "/users/new", to: "users.new" - post "/users", to: "users.create" + module #{inflector.camelize(slice)} + module Actions + module #{inflector.camelize(controller)} + class #{inflector.camelize(action)} < #{inflector.camelize(slice)}::Action + def handle(request, response) end end end - CODE + end + end + EXPECTED + expect(fs.read("slices/#{slice}/actions/#{controller}/#{action}.rb")).to eq(action_file) + expect(output).to include("Created slices/#{slice}/actions/#{controller}/#{action}.rb") - # route - expect(fs.read("config/routes.rb")).to eq(expected_routes) - expect(output).to include("Updated config/routes.rb") - expect(output).to include("Created slices/#{slice}/actions/#{controller}/") + # view + expect(fs.directory?("slices/#{slice}/views/#{controller}")).to be(true) + expect(output).to include("Created slices/#{slice}/views/#{controller}/") - # action - expected_action = <<~EXPECTED - # frozen_string_literal: true + view_file = <<~EXPECTED + # frozen_string_literal: true - module #{inflector.camelize(slice)} - module Actions - module Users - class Create < #{inflector.camelize(slice)}::Action - def handle(request, response) - end - end - end + module #{inflector.camelize(slice)} + module Views + module #{inflector.camelize(controller)} + class #{inflector.camelize(action)} < #{inflector.camelize(slice)}::View end end - EXPECTED - expect(fs.read("slices/#{slice}/actions/#{controller}/#{action}.rb")).to eq(expected_action) - expect(output).to include("Created slices/#{slice}/actions/#{controller}/#{action}.rb") - - # view - expect(output).to_not include("Created slices/#{slice}/views/#{controller}/#{action}.rb") - # template - expect(output).to_not include("Created slices/#{slice}/templates/#{controller}/#{action}.html.erb") + end end - end + EXPECTED + expect(fs.read("slices/#{slice}/views/#{controller}/#{action}.rb")).to eq(view_file) + expect(output).to include("Created slices/#{slice}/views/#{controller}/#{action}.rb") + + # template + expect(fs.directory?("slices/#{slice}/templates/#{controller}")).to be(true) + expect(output).to include("Created slices/#{slice}/templates/#{controller}/") + + template_file = <<~EXPECTED +

#{inflector.camelize(slice)}::Views::#{inflector.camelize(controller)}::#{inflector.camelize(action)}

+ EXPECTED + expect(fs.read("slices/#{slice}/templates/#{controller}/#{action}.html.erb")).to eq(template_file) + expect(output).to include("Created slices/#{slice}/templates/#{controller}/#{action}.html.erb") end end - context "deeply nested action" do - let(:controller) { %w[books bestsellers nonfiction] } - let(:controller_name) { controller.join(".") } - let(:action) { "index" } - let(:action_name) { "#{controller_name}.#{action}" } + context "RESTful actions" do + context "CREATE" do + let(:action) { "create" } - it "generates action" do - within_application_directory do + it "skips view generation when New view is present" do prepare_slice! - subject.call(slice: slice, name: action_name) - # Route routes = <<~CODE # frozen_string_literal: true @@ -1302,129 +1307,92 @@ class Routes < Hanami::Routes root { "Hello from Hanami" } slice :#{slice}, at: "/#{slice}" do - get "/books/bestsellers/nonfiction", to: "books.bestsellers.nonfiction.index" + get "/users/new", to: "users.new" end end end CODE - - # route - expect(fs.read("config/routes.rb")).to eq(routes) - - # action - expect(fs.directory?("slices/#{slice}/actions/books/bestsellers/nonfiction")).to be(true) + fs.write("config/routes.rb", routes) action_file = <<~EXPECTED # frozen_string_literal: true module #{inflector.camelize(slice)} module Actions - module Books - module Bestsellers - module Nonfiction - class #{inflector.camelize(action)} < #{inflector.camelize(slice)}::Action - def handle(request, response) - response.body = self.class.name - end - end + module Users + class New < #{inflector.camelize(slice)}::Action + def handle(request, response) end end end end end EXPECTED - expect(fs.read("slices/#{slice}/actions/books/bestsellers/nonfiction/#{action}.rb")).to eq(action_file) - - # view - expect(fs.directory?("slices/#{slice}/views/books/bestsellers/nonfiction")).to be(true) + fs.write("slices/#{slice}/actions/users/new.rb", action_file) view_file = <<~EXPECTED # frozen_string_literal: true module #{inflector.camelize(slice)} module Views - module Books - module Bestsellers - module Nonfiction - class #{inflector.camelize(action)} < #{inflector.camelize(slice)}::View - end - end + module Users + class New < #{inflector.camelize(slice)}::Action end end end end EXPECTED - expect(fs.read("slices/#{slice}/views/books/bestsellers/nonfiction/#{action}.rb")).to eq(view_file) - - # template - expect(fs.directory?("slices/#{slice}/templates/books/bestsellers/nonfiction")).to be(true) - - template_file = <<~EXPECTED -

#{inflector.camelize(slice)}::Views::Books::Bestsellers::Nonfiction::Index

- EXPECTED - expect(fs.read("slices/#{slice}/templates/books/bestsellers/nonfiction/#{action}.html.erb")).to eq(template_file) - end - end - end + fs.write("slices/#{slice}/views/users/new.rb", view_file) - it "appends routes within the proper slice block" do - within_application_directory do - prepare_slice! - fs.mkdir("slices/api") - - routes_contents = <<~CODE - # frozen_string_literal: true + subject.call(name: action_name, slice: slice, context: context) - require "hanami/routes" + expected_routes = <<~CODE + # frozen_string_literal: true - module #{app} - class Routes < Hanami::Routes - root { "Hello from Hanami" } + require "hanami/routes" - slice :#{slice}, at: "/#{slice}" do - root to: "home.index" - end + module #{app} + class Routes < Hanami::Routes + root { "Hello from Hanami" } - slice :api, at: "/api" do - root to: "home.index" + slice :#{slice}, at: "/#{slice}" do + get "/users/new", to: "users.new" + post "/users", to: "users.create" + end end end - end - CODE - fs.write("config/routes.rb", routes_contents) - - expected = <<~CODE - # frozen_string_literal: true - - require "hanami/routes" + CODE - module #{app} - class Routes < Hanami::Routes - root { "Hello from Hanami" } + # route + expect(fs.read("config/routes.rb")).to eq(expected_routes) + expect(output).to include("Updated config/routes.rb") + expect(output).to include("Created slices/#{slice}/actions/#{controller}/") - slice :#{slice}, at: "/#{slice}" do - root to: "home.index" - get "/users", to: "users.index" - end + # action + expected_action = <<~EXPECTED + # frozen_string_literal: true - slice :api, at: "/api" do - root to: "home.index" - get "/users/:id", to: "users.show" + module #{inflector.camelize(slice)} + module Actions + module Users + class Create < #{inflector.camelize(slice)}::Action + def handle(request, response) + end + end + end end end - end - CODE - - subject.call(slice: slice, name: "users.index") - subject.call(slice: "api", name: "users.show") + EXPECTED + expect(fs.read("slices/#{slice}/actions/#{controller}/#{action}.rb")).to eq(expected_action) + expect(output).to include("Created slices/#{slice}/actions/#{controller}/#{action}.rb") - expect(fs.read("config/routes.rb")).to eq(expected) + # view + expect(output).to_not include("Created slices/#{slice}/views/#{controller}/#{action}.rb") + # template + expect(output).to_not include("Created slices/#{slice}/templates/#{controller}/#{action}.html.erb") + end end end - - it "raises error if slice is unexisting" do - expect { subject.call(slice: "foo", name: action_name) }.to raise_error(Hanami::CLI::MissingSliceError, "slice `foo' is missing, please generate with `hanami generate slice foo'") - end end end From edf81f4ec8aa25deffbcc9b229e141292abe8e50 Mon Sep 17 00:00:00 2001 From: Max Mitchell Date: Thu, 14 Nov 2024 16:49:51 -0600 Subject: [PATCH 2/7] finish specs --- Gemfile | 2 +- .../cli/generators/app/ruby_file_writer.rb | 6 ++- .../commands/app/generate/component_spec.rb | 48 +++++++++++++++++++ .../commands/app/generate/migration_spec.rb | 26 ++++++++++ .../commands/app/generate/operation_spec.rb | 25 ++++++++++ .../cli/commands/app/generate/part_spec.rb | 33 +++++++++++++ .../commands/app/generate/relation_spec.rb | 22 +++++++++ .../cli/commands/app/generate/repo_spec.rb | 24 ++++++++++ .../cli/commands/app/generate/struct_spec.rb | 24 ++++++++++ .../cli/commands/app/generate/view_spec.rb | 33 +++++++++++++ 10 files changed, 241 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index d695fadb..e079e69b 100644 --- a/Gemfile +++ b/Gemfile @@ -20,7 +20,7 @@ gem "dry-system", github: "dry-rb/dry-system", branch: "main" gem "rack" -gem "mysql2" +# gem "mysql2" gem "pg" gem "sqlite3" diff --git a/lib/hanami/cli/generators/app/ruby_file_writer.rb b/lib/hanami/cli/generators/app/ruby_file_writer.rb index 203de554..e1f493c7 100644 --- a/lib/hanami/cli/generators/app/ruby_file_writer.rb +++ b/lib/hanami/cli/generators/app/ruby_file_writer.rb @@ -31,7 +31,7 @@ def call(key:, namespace:, base_path:, relative_parent_class:, extra_namespace: relative_parent_class: relative_parent_class, extra_namespace: extra_namespace, body: body, - ).write + ).create end private @@ -62,6 +62,10 @@ def initialize( @body = body end + def create + fs.create(path, file_contents) + end + def write fs.write(path, file_contents) end diff --git a/spec/unit/hanami/cli/commands/app/generate/component_spec.rb b/spec/unit/hanami/cli/commands/app/generate/component_spec.rb index 54a336fa..d7254a39 100644 --- a/spec/unit/hanami/cli/commands/app/generate/component_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/component_spec.rb @@ -38,6 +38,18 @@ class SendWelcomeEmail expect(fs.read("app/operations/send_welcome_email.rb")).to eq(component) expect(output).to include("Created app/operations/send_welcome_email.rb") end + + context "with existing file" do + before do + fs.write("app/operations/send_welcome_email.rb", "existing content") + end + + it "raises error" do + expect { + subject.call(name: "operations.send_welcome_email") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end end context "deeply nested" do @@ -62,6 +74,18 @@ class SendWelcomeEmail expect(fs.read("app/operations/user/mailing/send_welcome_email.rb")).to eq(component) expect(output).to include("Created app/operations/user/mailing/send_welcome_email.rb") end + + context "with existing file" do + before do + fs.write("app/operations/user/mailing/send_welcome_email.rb", "existing content") + end + + it "raises error" do + expect { + subject.call(name: "operations.user.mailing.send_welcome_email") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end end end @@ -85,6 +109,18 @@ class WelcomeEmail expect(fs.read("slices/main/renderers/welcome_email.rb")).to eq(component) expect(output).to include("Created slices/main/renderers/welcome_email.rb") end + + context "with existing file" do + before do + fs.write("slices/main/renderers/welcome_email.rb", "existing content") + end + + it "raises error" do + expect { + subject.call(name: "renderers.welcome_email", slice: "main") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end end context "deeply nested" do @@ -110,6 +146,18 @@ class WelcomeEmail expect(fs.read("slices/main/renderers/user/mailing/welcome_email.rb")).to eq(component) expect(output).to include("Created slices/main/renderers/user/mailing/welcome_email.rb") end + + context "with existing file" do + before do + fs.write("slices/main/renderers/user/mailing/welcome_email.rb", "existing content") + end + + it "raises error" do + expect { + subject.call(name: "renderers.user.mailing.welcome_email", slice: "main") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end end context "with missing slice" do diff --git a/spec/unit/hanami/cli/commands/app/generate/migration_spec.rb b/spec/unit/hanami/cli/commands/app/generate/migration_spec.rb index b4435587..3707e076 100644 --- a/spec/unit/hanami/cli/commands/app/generate/migration_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/migration_spec.rb @@ -64,6 +64,18 @@ def output "Name must contain only letters, numbers, and underscores." )) end + + context "with existing file" do + before do + fs.write("config/db/migrate/20240713140600_create_posts.rb", "existing content") + end + + it "raises error" do + expect { + subject.call(name: "create_posts") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end end context "generating for a slice" do @@ -76,5 +88,19 @@ def output expect(fs.read("slices/main/config/db/migrate/20240713140600_create_posts.rb")).to eq migration_file_contents expect(output).to eq("Created slices/main/config/db/migrate/20240713140600_create_posts.rb") end + + context "with existing file" do + context "with existing file" do + before do + fs.write("slices/main/config/db/migrate/20240713140600_create_posts.rb", "existing content") + end + + it "raises error" do + expect { + subject.call(name: "create_posts", slice: "main") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end + 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 5244cd29..46a1e992 100644 --- a/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/operation_spec.rb @@ -78,6 +78,18 @@ 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 + + context "with existing file" do + before do + fs.write("app/admin/books/add.rb", "existing content") + end + + it "raises error" do + expect { + subject.call(name: "admin.books.add") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end end context "generating for a slice" do @@ -126,5 +138,18 @@ def call 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 + + context "with existing file" do + before do + fs.mkdir("slices/main") + fs.write("slices/main/admin/books/add.rb", "existing content") + end + + it "raises error" do + expect { + subject.call(name: "admin.books.add", slice: "main") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end end end diff --git a/spec/unit/hanami/cli/commands/app/generate/part_spec.rb b/spec/unit/hanami/cli/commands/app/generate/part_spec.rb index 9245c8a3..00bc74fc 100644 --- a/spec/unit/hanami/cli/commands/app/generate/part_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/part_spec.rb @@ -58,6 +58,22 @@ class User < Test::Views::Part expect(output).to include("Created app/views/parts/user.rb") end end + + context "with existing file" do + before do + within_application_directory do + fs.write("app/views/parts/user.rb", "existing content") + end + end + + it "raises error" do + within_application_directory do + expect { + subject.call(name: "user") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end + end end context "with base part" do @@ -209,6 +225,23 @@ class User < Main::Views::Part end end + context "with existing file" do + before do + within_application_directory do + fs.mkdir("slices/main") + fs.write("slices/main/views/parts/user.rb", "existing content") + end + end + + it "raises error" do + within_application_directory do + expect { + subject.call(name: "user", slice: "main") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end + end + context "with missing slice" do it "raises error" do within_application_directory do diff --git a/spec/unit/hanami/cli/commands/app/generate/relation_spec.rb b/spec/unit/hanami/cli/commands/app/generate/relation_spec.rb index 2a79d7ab..669816ec 100644 --- a/spec/unit/hanami/cli/commands/app/generate/relation_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/relation_spec.rb @@ -114,6 +114,17 @@ class Books < TestApp::DB::Relation expect(Hanami.app.root.join("app/relations/books.rb").read).to eq relation_file expect(output).to include("Created app/relations/books.rb") end + + context "with existing file" do + before do + write "app/relations/books.rb", "existing content" + end + + it "raises error" do + expect { subject.call(name: "books") } + .to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end end context "generating for a slice" do @@ -178,5 +189,16 @@ class Drafts < Main::DB::Relation expect(Hanami.app.root.join("slices/main/relations/book/drafts.rb").read).to eq(relation_file) expect(output).to include("Created slices/main/relations/book/drafts.rb") end + + context "with existing file" do + before do + write "slices/main/relations/books.rb", "existing content" + end + + it "raises error" do + expect { subject.call(name: "books", slice: "main") } + .to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end end end diff --git a/spec/unit/hanami/cli/commands/app/generate/repo_spec.rb b/spec/unit/hanami/cli/commands/app/generate/repo_spec.rb index c547bef2..a15163bd 100644 --- a/spec/unit/hanami/cli/commands/app/generate/repo_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/repo_spec.rb @@ -92,6 +92,18 @@ class HardcoverRepo < Test::DB::Repo expect(fs.read("app/repos/books/published/hardcover_repo.rb")).to eq(repo_file) expect(output).to include("Created app/repos/books/published/hardcover_repo.rb") end + + context "with existing file" do + before do + fs.write("app/repos/book_repo.rb", "existing content") + end + + it "raises error" do + expect { + subject.call(name: "books") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end end context "generating for a slice" do @@ -134,5 +146,17 @@ class DraftRepo < Main::DB::Repo expect(fs.read("slices/main/repos/book/draft_repo.rb")).to eq(repo_file) expect(output).to include("Created slices/main/repos/book/draft_repo.rb") end + + context "with existing file" do + before do + fs.write("slices/main/repos/book_repo.rb", "existing content") + end + + it "raises error" do + expect { + subject.call(name: "books", slice: "main") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end end end diff --git a/spec/unit/hanami/cli/commands/app/generate/struct_spec.rb b/spec/unit/hanami/cli/commands/app/generate/struct_spec.rb index 65746193..490914cd 100644 --- a/spec/unit/hanami/cli/commands/app/generate/struct_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/struct_spec.rb @@ -72,6 +72,18 @@ class Hardcover < Test::DB::Struct expect(fs.read("app/structs/book/published/hardcover.rb")).to eq(struct_file) expect(output).to include("Created app/structs/book/published/hardcover.rb") end + + context "with existing file" do + before do + fs.write("app/structs/book/published/hardcover.rb", "existing content") + end + + it "raises error" do + expect { + subject.call(name: "book/published/hardcover") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end end context "generating for a slice" do @@ -114,5 +126,17 @@ class DraftBook < Main::DB::Struct expect(fs.read("slices/main/structs/book/draft_book.rb")).to eq(struct_file) expect(output).to include("Created slices/main/structs/book/draft_book.rb") end + + context "with existing file" do + before do + fs.write("slices/main/structs/book/draft_book.rb", "existing content") + end + + it "raises error" do + expect { + subject.call(name: "book.draft_book", slice: "main") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end end end 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 4abf30ad..b761979f 100644 --- a/spec/unit/hanami/cli/commands/app/generate/view_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/view_spec.rb @@ -91,6 +91,22 @@ class Index < Test::View expect(output).to include("Created app/templates/special/users/index.html.erb") end end + + context "with existing file" do + before do + within_application_directory do + fs.write("app/views/users/index.rb", "existing content") + end + end + + it "raises error" do + within_application_directory do + expect { + subject.call(name: "users.index") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end + end end context "generating for a slice" do @@ -127,6 +143,23 @@ class Index < Main::View expect(output).to include("Created slices/main/templates/users/index.html.erb") end end + + context "with existing file" do + before do + within_application_directory do + fs.mkdir("slices/main") + fs.write("slices/main/views/users/index.rb", "existing content") + end + end + + it "raises error" do + within_application_directory do + expect { + subject.call(name: "users.index", slice: "main") + }.to raise_error(Hanami::CLI::FileAlreadyExistsError) + end + end + end end private From 3f32755acce4dd9b253b5ebc555aa4c2b1409304 Mon Sep 17 00:00:00 2001 From: Max Mitchell Date: Thu, 14 Nov 2024 17:10:09 -0600 Subject: [PATCH 3/7] fixes --- Gemfile | 2 +- lib/hanami/cli/commands/app/db/migrate.rb | 2 +- lib/hanami/cli/commands/app/db/utils/postgres.rb | 3 ++- lib/hanami/cli/commands/app/db/utils/sqlite.rb | 14 ++++++++------ lib/hanami/cli/commands/app/generate/action.rb | 3 +-- lib/hanami/cli/commands/app/generate/slice.rb | 16 ++++++++-------- lib/hanami/cli/commands/app/install.rb | 2 +- lib/hanami/cli/commands/gem/new.rb | 4 ++-- .../hanami/cli/commands/app/db/prepare_spec.rb | 10 +++++----- 9 files changed, 29 insertions(+), 27 deletions(-) diff --git a/Gemfile b/Gemfile index e079e69b..d695fadb 100644 --- a/Gemfile +++ b/Gemfile @@ -20,7 +20,7 @@ gem "dry-system", github: "dry-rb/dry-system", branch: "main" gem "rack" -# gem "mysql2" +gem "mysql2" gem "pg" gem "sqlite3" diff --git a/lib/hanami/cli/commands/app/db/migrate.rb b/lib/hanami/cli/commands/app/db/migrate.rb index 6d437929..6b4c3dbb 100644 --- a/lib/hanami/cli/commands/app/db/migrate.rb +++ b/lib/hanami/cli/commands/app/db/migrate.rb @@ -12,7 +12,7 @@ class Migrate < DB::Command option :gateway, required: false, desc: "Use database for gateway" option :target, desc: "Target migration number", aliases: ["-t"] option :dump, required: false, type: :boolean, default: true, - desc: "Dump the database structure after migrating" + desc: "Dump the database structure after migrating" def call(target: nil, app: false, slice: nil, gateway: nil, dump: true, command_exit: method(:exit), **) databases(app: app, slice: slice, gateway: gateway).each do |database| diff --git a/lib/hanami/cli/commands/app/db/utils/postgres.rb b/lib/hanami/cli/commands/app/db/utils/postgres.rb index 06131096..09425d73 100644 --- a/lib/hanami/cli/commands/app/db/utils/postgres.rb +++ b/lib/hanami/cli/commands/app/db/utils/postgres.rb @@ -58,11 +58,12 @@ def schema_migrations_sql_dump migrations_sql = super return unless migrations_sql + search_path = gateway.connection .fetch("SHOW search_path").to_a.first .fetch(:search_path) - "SET search_path TO #{search_path};\n\n" << migrations_sql + +"SET search_path TO #{search_path};\n\n" << migrations_sql end private diff --git a/lib/hanami/cli/commands/app/db/utils/sqlite.rb b/lib/hanami/cli/commands/app/db/utils/sqlite.rb index 16056076..d3f9c6cc 100644 --- a/lib/hanami/cli/commands/app/db/utils/sqlite.rb +++ b/lib/hanami/cli/commands/app/db/utils/sqlite.rb @@ -38,7 +38,7 @@ def exec_create_command def exec_drop_command begin File.unlink(file_path) if exists? - rescue StandardError => e + rescue => e # Mimic a system_call result return Failure.new(e.message) end @@ -76,11 +76,13 @@ def name private def file_path - @file_path ||= if File.absolute_path?(name) - name - else - slice.app.root.join(name).to_s - end + @file_path ||= begin + if File.absolute_path?(name) + name + else + slice.app.root.join(name).to_s + end + end end end end diff --git a/lib/hanami/cli/commands/app/generate/action.rb b/lib/hanami/cli/commands/app/generate/action.rb index 75d5faba..226aea86 100644 --- a/lib/hanami/cli/commands/app/generate/action.rb +++ b/lib/hanami/cli/commands/app/generate/action.rb @@ -106,8 +106,7 @@ def call( raise InvalidActionNameError.new(name) end - generator.call(app.namespace, controller, action, url, http, format, skip_view, skip_route, slice, - context: context) + generator.call(app.namespace, controller, action, url, http, format, skip_view, skip_route, slice, context: context) end # rubocop:enable Metrics/ParameterLists diff --git a/lib/hanami/cli/commands/app/generate/slice.rb b/lib/hanami/cli/commands/app/generate/slice.rb index 4a4eaaec..b6201d9c 100644 --- a/lib/hanami/cli/commands/app/generate/slice.rb +++ b/lib/hanami/cli/commands/app/generate/slice.rb @@ -29,17 +29,17 @@ class Slice < App::Command # @since 2.2.0 # @api private option :skip_db, - type: :flag, - required: false, - default: SKIP_DB_DEFAULT, - desc: "Skip database" + type: :flag, + required: false, + default: SKIP_DB_DEFAULT, + desc: "Skip database" # @since 2.2.0 # @api private option :skip_route, - type: :flag, - required: false, - default: DEFAULT_SKIP_ROUTE, - desc: "Skip route generation" + type: :flag, + required: false, + default: DEFAULT_SKIP_ROUTE, + desc: "Skip route generation" example [ "admin # Admin slice (/admin URL prefix)", diff --git a/lib/hanami/cli/commands/app/install.rb b/lib/hanami/cli/commands/app/install.rb index f9b96ee0..7d383226 100644 --- a/lib/hanami/cli/commands/app/install.rb +++ b/lib/hanami/cli/commands/app/install.rb @@ -39,7 +39,7 @@ class Install < Command def initialize( fs:, bundler: CLI::Bundler.new(fs: fs), - **_opts + **opts ) @bundler = bundler end diff --git a/lib/hanami/cli/commands/gem/new.rb b/lib/hanami/cli/commands/gem/new.rb index 6771f9cb..1c9ea042 100644 --- a/lib/hanami/cli/commands/gem/new.rb +++ b/lib/hanami/cli/commands/gem/new.rb @@ -113,7 +113,7 @@ def initialize( @system_call = system_call end - # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity # @since 2.0.0 # @api private @@ -166,7 +166,7 @@ def call( end end end - # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity private diff --git a/spec/unit/hanami/cli/commands/app/db/prepare_spec.rb b/spec/unit/hanami/cli/commands/app/db/prepare_spec.rb index 11203d55..8b5f8ce2 100644 --- a/spec/unit/hanami/cli/commands/app/db/prepare_spec.rb +++ b/spec/unit/hanami/cli/commands/app/db/prepare_spec.rb @@ -291,7 +291,7 @@ class Comments < Hanami::DB::Relation expect(output).to include_in_order( "database #{POSTGRES_BASE_DB_NAME}_app created", "database #{POSTGRES_BASE_DB_NAME}_app migrated", - "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql", + "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql", "seed data loaded from config/db/seeds.rb", ) expect(output).not_to include "#{POSTGRES_BASE_DB_NAME}_main" @@ -310,7 +310,7 @@ class Comments < Hanami::DB::Relation expect(output).to include_in_order( "database #{POSTGRES_BASE_DB_NAME}_main created", "database #{POSTGRES_BASE_DB_NAME}_main migrated", - "#{POSTGRES_BASE_DB_NAME}_main structure dumped to slices/main/config/db/structure.sql", + "#{POSTGRES_BASE_DB_NAME}_main structure dumped to slices/main/config/db/structure.sql", "seed data loaded from slices/main/config/db/seeds.rb" ) expect(output).not_to include "#{POSTGRES_BASE_DB_NAME}_app" @@ -349,7 +349,7 @@ class Comments < Hanami::DB::Relation expect(output).to include_in_order( "database #{POSTGRES_BASE_DB_NAME}_app created", "database #{POSTGRES_BASE_DB_NAME}_app migrated", - "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql", + "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql", "seed data loaded from config/db/seeds.rb", ) end @@ -377,7 +377,7 @@ class Comments < Hanami::DB::Relation expect(output).to include_in_order( "database #{POSTGRES_BASE_DB_NAME}_app created", # TODO: it would be good not to include this "database #{POSTGRES_BASE_DB_NAME}_app migrated", - "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql", + "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql", "seed data loaded from config/db/seeds.rb", ) end @@ -405,7 +405,7 @@ class Comments < Hanami::DB::Relation expect(output).to include_in_order( "database #{POSTGRES_BASE_DB_NAME}_app created", # TODO: it would be good not to include this "database #{POSTGRES_BASE_DB_NAME}_app migrated", - "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql" + "#{POSTGRES_BASE_DB_NAME}_app structure dumped to config/db/structure.sql" ) expect(output).not_to include "seed data loaded from config/db/seeds.rb" end From c7167d1ed29400bc7584a35e2941b525c54111a5 Mon Sep 17 00:00:00 2001 From: Max Mitchell Date: Thu, 14 Nov 2024 17:12:18 -0600 Subject: [PATCH 4/7] cleanup --- lib/hanami/cli/commands/app/db/utils/postgres.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/hanami/cli/commands/app/db/utils/postgres.rb b/lib/hanami/cli/commands/app/db/utils/postgres.rb index 09425d73..70b59682 100644 --- a/lib/hanami/cli/commands/app/db/utils/postgres.rb +++ b/lib/hanami/cli/commands/app/db/utils/postgres.rb @@ -58,7 +58,6 @@ def schema_migrations_sql_dump migrations_sql = super return unless migrations_sql - search_path = gateway.connection .fetch("SHOW search_path").to_a.first .fetch(:search_path) From b93ded95773ca6095b009b87b12b2bde433cc7b4 Mon Sep 17 00:00:00 2001 From: Max Mitchell Date: Thu, 14 Nov 2024 17:14:20 -0600 Subject: [PATCH 5/7] cleanup --- lib/hanami/cli/commands/app/db/utils/postgres.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hanami/cli/commands/app/db/utils/postgres.rb b/lib/hanami/cli/commands/app/db/utils/postgres.rb index 70b59682..571a3f19 100644 --- a/lib/hanami/cli/commands/app/db/utils/postgres.rb +++ b/lib/hanami/cli/commands/app/db/utils/postgres.rb @@ -57,7 +57,7 @@ def exec_load_command def schema_migrations_sql_dump migrations_sql = super return unless migrations_sql - + search_path = gateway.connection .fetch("SHOW search_path").to_a.first .fetch(:search_path) From 3fd02f22e5968851045d62b554845cb8b3bd87f3 Mon Sep 17 00:00:00 2001 From: Max Mitchell Date: Thu, 14 Nov 2024 17:14:49 -0600 Subject: [PATCH 6/7] cleanup --- lib/hanami/cli/commands/app/db/utils/postgres.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hanami/cli/commands/app/db/utils/postgres.rb b/lib/hanami/cli/commands/app/db/utils/postgres.rb index 571a3f19..3d10c154 100644 --- a/lib/hanami/cli/commands/app/db/utils/postgres.rb +++ b/lib/hanami/cli/commands/app/db/utils/postgres.rb @@ -57,7 +57,7 @@ def exec_load_command def schema_migrations_sql_dump migrations_sql = super return unless migrations_sql - + search_path = gateway.connection .fetch("SHOW search_path").to_a.first .fetch(:search_path) From 3d68320fb7fda4a9e099291261c36c70c5fa2859 Mon Sep 17 00:00:00 2001 From: Max Mitchell Date: Thu, 14 Nov 2024 17:18:00 -0600 Subject: [PATCH 7/7] remove version comment --- lib/hanami/cli/files.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/hanami/cli/files.rb b/lib/hanami/cli/files.rb index ed01625a..01869850 100644 --- a/lib/hanami/cli/files.rb +++ b/lib/hanami/cli/files.rb @@ -14,7 +14,6 @@ def initialize(out: $stdout, **args) @out = out end - # @since 2.3.0 # @api private def create(path, *content) raise FileAlreadyExistsError.new(path) if exist?(path)