Skip to content

Commit

Permalink
Add generate migration command and generator (#201)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Sean Collins <sean@cllns.com>
  • Loading branch information
timriley and cllns authored Jul 15, 2024
1 parent b96e9ab commit ad8d70f
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 3 deletions.
1 change: 1 addition & 0 deletions lib/hanami/cli/commands/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def self.extended(base)
register "generate", aliases: ["g"] do |prefix|
prefix.register "action", Generate::Action
prefix.register "component", Generate::Component
prefix.register "migration", Generate::Migration
prefix.register "operation", Generate::Operation
prefix.register "part", Generate::Part
prefix.register "repo", Generate::Repo
Expand Down
2 changes: 1 addition & 1 deletion lib/hanami/cli/commands/app/generate/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def generator_class
# @since 2.2.0
# @api private
def call(name:, slice: nil, **)
normalized_slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
normalized_slice = inflector.underscore(slice) if slice
generator.call(app.namespace, name, normalized_slice)
end
end
Expand Down
28 changes: 28 additions & 0 deletions lib/hanami/cli/commands/app/generate/migration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module Hanami
module CLI
module Commands
module App
module Generate
# @since 2.2.0
# @api private
class Migration < Command
argument :name, required: true, desc: "Migration name"
option :slice, required: false, desc: "Slice name"

example [
%(create_posts),
%(add_published_at_to_posts),
%(create_users --slice=admin),
]

def generator_class
Generators::App::Migration
end
end
end
end
end
end
end
2 changes: 0 additions & 2 deletions lib/hanami/cli/commands/app/generate/repo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ def call(name:, slice: nil, **opts)
else
"#{inflector.singularize(name)}_repo"
end
slice = inflector.underscore(slice) if slice

super(name: normalized_name, slice: slice, **opts)
end
end
Expand Down
12 changes: 12 additions & 0 deletions lib/hanami/cli/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,17 @@ def initialize(option1, option2)
super("`#{option1}' and `#{option2}' cannot be used together")
end
end

# @since 2.2.0
# @api public
class InvalidMigrationNameError < Error
def initialize(name)
super(<<~TEXT)
Invalid migration name: #{name}
Name must contain only letters, numbers, and underscores.
TEXT
end
end
end
end
69 changes: 69 additions & 0 deletions lib/hanami/cli/generators/app/migration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true

module Hanami
module CLI
module Generators
module App
# @since 2.2.0
# @api private
class Migration
# @since 2.2.0
# @api private
def initialize(fs:, inflector:, out: $stdout)
@fs = fs
@inflector = inflector
@out = out
end

# @since 2.2.0
# @api private
def call(_app_namespace, name, slice, **_opts)
normalized_name = inflector.underscore(name)
ensure_valid_name(normalized_name)

base = if slice
fs.join("slices", slice, "config", "db", "migrate")
else
fs.join("config", "db", "migrate")
end

path = fs.join(base, file_name(normalized_name))

fs.write(path, FILE_CONTENTS)
end

private

attr_reader :fs, :inflector, :out

VALID_NAME_REGEX = /^[_a-z0-9]+$/
private_constant :VALID_NAME_REGEX

def ensure_valid_name(name)
unless VALID_NAME_REGEX.match?(name.downcase)
raise InvalidMigrationNameError.new(name)
end
end

def file_name(name)
"#{Time.now.strftime(VERSION_FORMAT)}_#{name}.rb"
end

VERSION_FORMAT = "%Y%m%d%H%M%S"
private_constant :VERSION_FORMAT

FILE_CONTENTS = <<~RUBY
# frozen_string_literal: true
ROM::SQL.migration do
# Add your migration here.
#
# See https://sequel.jeremyevans.net/rdoc/files/doc/migration_rdoc.html for details.
end
RUBY
private_constant :FILE_CONTENTS
end
end
end
end
end
71 changes: 71 additions & 0 deletions spec/unit/hanami/cli/commands/app/generate/migration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

require "hanami"

RSpec.describe Hanami::CLI::Commands::App::Generate::Migration, :app do
subject { described_class.new(fs: fs, inflector: inflector) }

let(:fs) { Hanami::CLI::Files.new(memory: true, out: out) }
let(:inflector) { Dry::Inflector.new }

let(:out) { StringIO.new }

def output
out.string.strip
end

let(:app) { Hanami.app.namespace }

let(:migration_file_contents) {
<<~RUBY
# frozen_string_literal: true
ROM::SQL.migration do
# Add your migration here.
#
# See https://sequel.jeremyevans.net/rdoc/files/doc/migration_rdoc.html for details.
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")

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 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")
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

context "generating for a slice" do
it "generates a migration" do
fs.mkdir("slices/main")
out.truncate 0

subject.call(name: "create_posts", slice: "main")

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
end
end

0 comments on commit ad8d70f

Please sign in to comment.