From 1f3343253ece04b1db171f36d33d02b561ab2f30 Mon Sep 17 00:00:00 2001 From: Adam Lassek Date: Sat, 31 Oct 2026 19:39:15 -0500 Subject: [PATCH 1/2] Add generator option for SQL migrations Relates to hanakai-rb/rom-sql#440 --- .../cli/commands/app/generate/migration.rb | 11 +- lib/hanami/cli/generators/app/migration.rb | 25 +++- .../commands/app/generate/migration_spec.rb | 129 +++++++++++++----- 3 files changed, 121 insertions(+), 44 deletions(-) diff --git a/lib/hanami/cli/commands/app/generate/migration.rb b/lib/hanami/cli/commands/app/generate/migration.rb index c685f661..d62f964e 100644 --- a/lib/hanami/cli/commands/app/generate/migration.rb +++ b/lib/hanami/cli/commands/app/generate/migration.rb @@ -10,6 +10,7 @@ module Generate class Migration < Command argument :name, required: true, desc: "Migration name" option :gateway, desc: "Generate migration for gateway" + option :sql, type: :boolean, default: false, desc: "Generate SQL (default: Ruby)" example "create_posts", "Migration for the app" example "add_published_at_to_posts", "Migration for the app" @@ -20,20 +21,24 @@ def generator_class Generators::App::Migration end - def call(name:, slice: nil, gateway: nil) + def call(name:, slice: nil, gateway: nil, sql: false) + ext = sql ? "sql" : "rb" + if slice generator.call( key: name, namespace: slice, base_path: fs.join("slices", inflector.underscore(slice)), - gateway: gateway + gateway: gateway, + ext: ext ) else generator.call( key: name, namespace: app.namespace, base_path: "app", - gateway: gateway + gateway: gateway, + ext: ext ) end end diff --git a/lib/hanami/cli/generators/app/migration.rb b/lib/hanami/cli/generators/app/migration.rb index 876e100e..473ba09d 100644 --- a/lib/hanami/cli/generators/app/migration.rb +++ b/lib/hanami/cli/generators/app/migration.rb @@ -17,16 +17,16 @@ def initialize(fs:, inflector:, stdout: $stdout) # @since 2.2.0 # @api private - def call(key:, base_path:, gateway: nil, **_opts) + def call(key:, base_path:, gateway: nil, ext: "rb", **_opts) name = inflector.underscore(key) ensure_valid_name(name) base_path = nil if base_path == "app" # Migrations are in the root dir, not app/ migrate_dir = gateway ? "#{gateway}_migrate" : "migrate" - path = fs.join(*[base_path, "config", "db", migrate_dir, file_name(name)].compact) + path = fs.join(*[base_path, "config", "db", migrate_dir, file_name(name, ext)].compact) - fs.create(path, FILE_CONTENTS) + fs.create(path, FILE_CONTENTS[ext]) end private @@ -42,14 +42,14 @@ def ensure_valid_name(name) end end - def file_name(name) - "#{Time.now.strftime(VERSION_FORMAT)}_#{name}.rb" + def file_name(name, ext) + "#{Time.now.strftime(VERSION_FORMAT)}_#{name}.#{ext}" end VERSION_FORMAT = "%Y%m%d%H%M%S" private_constant :VERSION_FORMAT - FILE_CONTENTS = <<~RUBY + FILE_CONTENTS = {"rb" => <<~RUBY, "sql" => <<~SQL}.freeze # frozen_string_literal: true ROM::SQL.migration do @@ -60,6 +60,19 @@ def file_name(name) end end RUBY + -- Add your migration here. + -- + -- See https://hanakai.org/learn/hanami/database/migrations/ for details. + -- @migrate up + -- @begin + SELECT 'up SQL query'; + -- @end + + -- @migrate down + -- @begin + SELECT 'down SQL query'; + -- @end + SQL private_constant :FILE_CONTENTS end end 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 2fb69cb3..f6348dda 100644 --- a/spec/unit/hanami/cli/commands/app/generate/migration_spec.rb +++ b/spec/unit/hanami/cli/commands/app/generate/migration_spec.rb @@ -14,57 +14,116 @@ def output let(:app) { Hanami.app.namespace } - let(:migration_file_contents) { - <<~RUBY - # frozen_string_literal: true - - ROM::SQL.migration do - # Add your migration here. - # - # See https://hanakai.org/learn/hanami/database/migrations/ for details. - change do - end - end - RUBY - } - before do allow(Time).to receive(:now) { Time.new(2024, 7, 13, 14, 6, 0) } end context "generating for app" do - it "generates a migration" do - subject.call(name: "create_posts") + context "Ruby migration" do + let(:migration_file_contents) { <<~RUBY } + # frozen_string_literal: true + + ROM::SQL.migration do + # Add your migration here. + # + # See https://hanakai.org/learn/hanami/database/migrations/ for details. + change do + end + end + RUBY - expect(fs.read("config/db/migrate/20240713140600_create_posts.rb")).to eq migration_file_contents - expect(output).to eq("Created config/db/migrate/20240713140600_create_posts.rb") - end + it "generates a migration" do + subject.call(name: "create_posts") - it "generates a migration for a gateway" do - subject.call(name: "create_posts", gateway: "extra") + expect(fs.read("config/db/migrate/20240713140600_create_posts.rb")).to eq migration_file_contents + expect(output).to eq("Created config/db/migrate/20240713140600_create_posts.rb") + end - expect(fs.read("config/db/extra_migrate/20240713140600_create_posts.rb")).to eq migration_file_contents - expect(output).to eq("Created config/db/extra_migrate/20240713140600_create_posts.rb") - end + it "generates a migration for a gateway" do + subject.call(name: "create_posts", gateway: "extra") - it "generates a migration with underscored version of camel cased name" do - subject.call(name: "CreatePosts") + expect(fs.read("config/db/extra_migrate/20240713140600_create_posts.rb")).to eq migration_file_contents + expect(output).to eq("Created config/db/extra_migrate/20240713140600_create_posts.rb") + end + + it "generates a migration with underscored version of camel cased name" do + subject.call(name: "CreatePosts") - expect(fs.read("config/db/migrate/20240713140600_create_posts.rb")).to eq migration_file_contents - expect(output).to eq("Created config/db/migrate/20240713140600_create_posts.rb") + expect(fs.read("config/db/migrate/20240713140600_create_posts.rb")).to eq migration_file_contents + expect(output).to eq("Created config/db/migrate/20240713140600_create_posts.rb") + end + + it "raises an error if given an invalid name" do + expect { + subject.call(name: "create posts") + }.to raise_error(include_in_order( + "Invalid migration name: create posts", + "Name must contain only letters, numbers, and underscores." + )) + end end - it "raises an error if given an invalid name" do - expect { - subject.call(name: "create posts") - }.to raise_error(include_in_order( - "Invalid migration name: create posts", - "Name must contain only letters, numbers, and underscores." - )) + context "SQL migration" do + let(:migration_file_contents) { <<~SQL } + -- Add your migration here. + -- + -- See https://hanakai.org/learn/hanami/database/migrations/ for details. + -- @migrate up + -- @begin + SELECT 'up SQL query'; + -- @end + + -- @migrate down + -- @begin + SELECT 'down SQL query'; + -- @end + SQL + + it "generates a migration" do + subject.call(name: "create_posts", sql: true) + + expect(fs.read("config/db/migrate/20240713140600_create_posts.sql")).to eq migration_file_contents + expect(output).to eq("Created config/db/migrate/20240713140600_create_posts.sql") + end + + it "generates a migration for a gateway" do + subject.call(name: "create_posts", gateway: "extra", sql: true) + + expect(fs.read("config/db/extra_migrate/20240713140600_create_posts.sql")).to eq migration_file_contents + expect(output).to eq("Created config/db/extra_migrate/20240713140600_create_posts.sql") + end + + it "generates a migration with underscored version of camel cased name" do + subject.call(name: "CreatePosts", sql: true) + + expect(fs.read("config/db/migrate/20240713140600_create_posts.sql")).to eq migration_file_contents + expect(output).to eq("Created config/db/migrate/20240713140600_create_posts.sql") + end + + it "raises an error if given an invalid name" do + expect { + subject.call(name: "create posts", sql: true) + }.to raise_error(include_in_order( + "Invalid migration name: create posts", + "Name must contain only letters, numbers, and underscores." + )) + end end end context "generating for a slice" do + let(:migration_file_contents) { <<~RUBY } + # frozen_string_literal: true + + ROM::SQL.migration do + # Add your migration here. + # + # See https://hanakai.org/learn/hanami/database/migrations/ for details. + change do + end + end + RUBY + it "generates a migration" do fs.mkdir("slices/main") stdout.truncate 0 From 2c4b457341d2c8880dc5b0fdbf3f558f9ca13c13 Mon Sep 17 00:00:00 2001 From: Adam Lassek Date: Sat, 31 Oct 2026 19:46:43 -0500 Subject: [PATCH 2/2] Add CHANGELOG entry for #470 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3be8f34d..10e9f102 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Break Versioning](https://www.taoensso.com/break-ve - Add `hanami new --skip-git` to generate an app without initializing a git repository or generating a `.gitignore`. (@aaronmallen #441) - Proper support for MariaDB (@katafrakt in #458) +- Add `--sql` flag to migration generator to use a SQL template instead of Ruby. (@alassek in #470) ### Changed