Skip to content

Commit

Permalink
Generate db files during hanami new (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
cllns authored Jul 8, 2024
1 parent 2ffa120 commit 0dc1374
Show file tree
Hide file tree
Showing 13 changed files with 792 additions and 15 deletions.
2 changes: 1 addition & 1 deletion lib/hanami/cli/commands/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ def self.extended(base)
register "generate", aliases: ["g"] do |prefix|
prefix.register "action", Generate::Action
prefix.register "component", Generate::Component
prefix.register "slice", Generate::Slice
prefix.register "operation", Generate::Operation
prefix.register "part", Generate::Part
prefix.register "slice", Generate::Slice
prefix.register "view", Generate::View
end
end
Expand Down
86 changes: 75 additions & 11 deletions lib/hanami/cli/commands/gem/new.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,27 @@ class New < Command
SKIP_ASSETS_DEFAULT = false
private_constant :SKIP_ASSETS_DEFAULT

# @since 2.2.0
# @api private
SKIP_DB_DEFAULT = false
private_constant :SKIP_DB_DEFAULT

# @since 2.2.0
# @api private
DATABASE_SQLITE = "sqlite"

# @since 2.2.0
# @api private
DATABASE_POSTGRES = "postgres"

# @since 2.2.0
# @api private
DATABASE_MYSQL = "mysql"

# @since 2.2.0
# @api private
SUPPORTED_DATABASES = [DATABASE_SQLITE, DATABASE_POSTGRES, DATABASE_MYSQL].freeze

desc "Generate a new Hanami app"

# @since 2.0.0
Expand All @@ -47,14 +68,28 @@ class New < Command
# @api private
option :skip_assets, type: :boolean, required: false,
default: SKIP_ASSETS_DEFAULT,
desc: "Skip assets"
desc: "Skip including hanami-assets"

# @since 2.2.0
# @api private
option :skip_db, type: :boolean, required: false,
default: SKIP_DB_DEFAULT,
desc: "Skip including hanami-db"

# @since 2.2.0
# @api private
option :database, type: :string, required: false,
default: DATABASE_SQLITE,
desc: "Database adapter (supported: sqlite, mysql, postgres)"

# rubocop:disable Layout/LineLength
example [
"bookshelf # Generate a new Hanami app in `bookshelf/' directory, using `Bookshelf' namespace",
"bookshelf --head # Generate a new Hanami app, using Hanami HEAD version from GitHub `main' branches",
"bookshelf --skip-install # Generate a new Hanami app, but it skips Hanami installation",
"bookshelf --skip-assets # Generate a new Hanami app without assets"
"bookshelf # Generate a new Hanami app in `bookshelf/' directory, using `Bookshelf' namespace",
"bookshelf --head # Generate a new Hanami app, using Hanami HEAD version from GitHub `main' branches",
"bookshelf --skip-install # Generate a new Hanami app, but it skips Hanami installation",
"bookshelf --skip-assets # Generate a new Hanami app without hanmai-assets",
"bookshelf --skip-db # Generate a new Hanami app without hanami-db",
"bookshelf --database={sqlite|postgres|mysql} # Generate a new Hanami app with a specified database (default: sqlite)",
]
# rubocop:enable Layout/LineLength

Expand All @@ -75,20 +110,36 @@ def initialize(
@system_call = system_call
end

# rubocop:enable Metrics/ParameterLists

# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity

# @since 2.0.0
# @api private
def call(app:, head: HEAD_DEFAULT, skip_install: SKIP_INSTALL_DEFAULT, skip_assets: SKIP_ASSETS_DEFAULT, **)
def call(
app:,
head: HEAD_DEFAULT,
skip_install: SKIP_INSTALL_DEFAULT,
skip_assets: SKIP_ASSETS_DEFAULT,
skip_db: SKIP_DB_DEFAULT,
database: nil
)
# rubocop:enable Metrics/ParameterLists
app = inflector.underscore(app)

raise PathAlreadyExistsError.new(app) if fs.exist?(app)
raise ConflictingOptionsError.new("--skip-db", "--database") if skip_db && database

normalized_database ||= normalize_database(database)

fs.mkdir(app)
fs.chdir(app) do
context = Generators::Context.new(inflector, app, head: head, skip_assets: skip_assets)
context = Generators::Context.new(
inflector,
app,
head: head,
skip_assets: skip_assets,
skip_db: skip_db,
database: normalized_database
)
generator.call(app, context: context) do
if skip_install
out.puts "Skipping installation, please enter `#{app}' directory and run `bundle exec hanami install'"
Expand All @@ -112,14 +163,27 @@ def call(app:, head: HEAD_DEFAULT, skip_install: SKIP_INSTALL_DEFAULT, skip_asse
end
end
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity

private

attr_reader :bundler
attr_reader :generator
attr_reader :system_call

def normalize_database(database)
case database
when nil, "sqlite", "sqlite3"
DATABASE_SQLITE
when "mysql", "mysql2"
DATABASE_MYSQL
when "postgres", "postgresql", "pg"
DATABASE_POSTGRES
else
raise DatabaseNotSupportedError.new(database, SUPPORTED_DATABASES)
end
end

def run_install_command!(head:)
head_flag = head ? " --head" : ""
bundler.exec("hanami install#{head_flag}").tap do |result|
Expand Down
16 changes: 16 additions & 0 deletions lib/hanami/cli/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,21 @@ def initialize(scheme)
super("`#{scheme}' is not a supported db scheme")
end
end

# @since 2.2.0
# @api public
class DatabaseNotSupportedError < Error
def initialize(invalid_database, supported_databases)
super("`#{invalid_database}' is not a supported database. Supported databases are: #{supported_databases.join(', ')}")
end
end

# @since 2.2.0
# @api public
class ConflictingOptionsError < Error
def initialize(option1, option2)
super("`#{option1}' and `#{option2}' cannot be used together")
end
end
end
end
42 changes: 42 additions & 0 deletions lib/hanami/cli/generators/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,44 @@ def generate_assets?
!options.fetch(:skip_assets, false)
end

# @since 2.2.0
# @api private
def generate_db?
!options.fetch(:skip_db, false)
end

# @since 2.2.0
# @api private
def generate_sqlite?
database_option == Commands::Gem::New::DATABASE_SQLITE
end

# @since 2.2.0
# @api private
def generate_postgres?
database_option == Commands::Gem::New::DATABASE_POSTGRES
end

# @since 2.2.0
# @api private
def generate_mysql?
database_option == Commands::Gem::New::DATABASE_MYSQL
end

# @since 2.2.0
# @api private
def database_url
if generate_sqlite?
"sqlite://db/#{app}.sqlite"
elsif generate_postgres?
"postgres://localhost/#{app}"
elsif generate_mysql?
"mysql://localhost/#{app}"
else
raise "Unknown database option: #{database_option}"
end
end

# @since 2.1.0
# @api private
def bundled_views?
Expand Down Expand Up @@ -108,6 +146,10 @@ def ruby_omit_hash_values?

private

def database_option
options.fetch(:database, Commands::Gem::New::DATABASE_SQLITE)
end

# @since 2.0.0
# @api private
attr_reader :inflector
Expand Down
9 changes: 9 additions & 0 deletions lib/hanami/cli/generators/gem/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ def generate_app(app, context) # rubocop:disable Metrics/AbcSize
fs.write("app/assets/images/favicon.ico", file("favicon.ico"))
end

if context.generate_db?
fs.write("app/db/repo.rb", t("repo.erb", context))
fs.write("app/db/relation.rb", t("relation.erb", context))
fs.write("app/db/struct.rb", t("struct.erb", context))
fs.touch("app/structs/.keep")
fs.touch("app/repos/.keep")
fs.touch("config/db/migrate/.keep")
end

fs.write("app/operation.rb", t("operation.erb", context))

fs.write("public/404.html", file("404.html"))
Expand Down
4 changes: 4 additions & 0 deletions lib/hanami/cli/generators/gem/app/env.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This is checked into source control, so put sensitive values into `.env.local`
<%- if generate_db? -%>
DATABASE_URL=<%= database_url %>
<%- end -%>
10 changes: 10 additions & 0 deletions lib/hanami/cli/generators/gem/app/gemfile.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ source "https://rubygems.org"
<%= hanami_gem("assets") %>
<%- end -%>
<%= hanami_gem("controller") %>
<%- if generate_db? -%>
<%= hanami_gem("db") %>
<%- end -%>
<%= hanami_gem("router") %>
<%= hanami_gem("validations") %>
<%= hanami_gem("view") %>
Expand All @@ -15,6 +18,13 @@ gem "dry-types", "~> 1.0", ">= 1.6.1"
gem "dry-operation", github: "dry-rb/dry-operation"
gem "puma"
gem "rake"
<%- if generate_sqlite? -%>
gem "sqlite3"
<%- elsif generate_postgres? -%>
gem "pg"
<%- elsif generate_mysql? -%>
gem "mysql2"
<%- end -%>

group :development do
<%= hanami_gem("webconsole") %>
Expand Down
2 changes: 1 addition & 1 deletion lib/hanami/cli/generators/gem/app/gitignore.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.env
.env*.local
log/*
<%- if generate_assets? -%>
public/
Expand Down
4 changes: 4 additions & 0 deletions lib/hanami/cli/generators/gem/app/operation.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@ require "dry/operation"

module <%= camelized_app_name %>
class Operation < Dry::Operation
<%- if generate_db? -%>
# Provide `transaction do ... end` method for database transactions
include Dry::Operation::Extensions::ROM
<%- end -%>
end
end
10 changes: 10 additions & 0 deletions lib/hanami/cli/generators/gem/app/relation.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

require "hanami/db/relation"

module <%= camelized_app_name %>
module DB
class Relation < Hanami::DB::Relation
end
end
end
10 changes: 10 additions & 0 deletions lib/hanami/cli/generators/gem/app/repo.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

require "hanami/db/repo"

module <%= camelized_app_name %>
module DB
class Repo < Hanami::DB::Repo
end
end
end
10 changes: 10 additions & 0 deletions lib/hanami/cli/generators/gem/app/struct.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

require "hanami/db/struct"

module <%= camelized_app_name %>
module DB
class Struct < Hanami::DB::Struct
end
end
end
Loading

0 comments on commit 0dc1374

Please sign in to comment.