Skip to content

Commit

Permalink
chore: add script to generate scaffolding for a new resource (#415)
Browse files Browse the repository at this point in the history
* chore: add script to generate scaffolding for a new resource

* chore: remove puts

* chore(scaffolding): add more

* chore: more scaffolding

* chore: update scaffolding
  • Loading branch information
bethesque authored Apr 13, 2021
1 parent c72de13 commit 2079211
Show file tree
Hide file tree
Showing 12 changed files with 481 additions and 0 deletions.
23 changes: 23 additions & 0 deletions scaffolding/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Scaffolding

Generates a new model class and its associated:

* migration
* resource
* decorator (todo)
* service (todo)
* repository (todo)
* resource spec (todo)
* decorator spec (todo)
* service spec (todo)
* repository spec (todo)

## Usage

Set `MODEL_CLASS_FULL_NAME` to the full name of the class, and run:

```
bundle exec ruby scaffolding/run.rb
```

Note that the class name must be in the format X::Y::Z (a class nested inside two modules).
237 changes: 237 additions & 0 deletions scaffolding/run.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
require 'pact_broker/string_refinements'
require 'pact_broker/project_root'
require 'date'
require 'erb'
require 'pathname'

using PactBroker::StringRefinements

MODEL_CLASS_FULL_NAME = "PactBroker::Foos::Foo"
DRY_RUN = false

TEMPLATE_DIR = Pathname.new(File.join(__dir__, "templates"))
MIGRATIONS_DIR = PactBroker.project_root.join("db", "migrations")
LIB_DIR = PactBroker.project_root.join("lib")
SPEC_DIR = PactBroker.project_root.join("spec", "lib")

def model_full_class_name
MODEL_CLASS_FULL_NAME
end

def today
DateTime.now.strftime('%Y%m%d')
end

def require_path_prefix
model_top_module.snakecase
end

def migration_path
MIGRATIONS_DIR.join(today + "_create_#{table_name}_table.rb")
end

def model_class_name
model_full_class_name.split("::").last
end

def model_top_module
model_full_class_name.split("::").first
end

def repository_class_full_name
model_full_class_name.split("::")[0..1].join("::") + "::Repository"
end

# Resource

def resource_top_module
model_top_module
end

def resource_class_name
model_class_name
end

def resource_class_full_name
"#{resource_top_module}::Api::Resources::#{resource_class_name}"
end

def resource_url_path
model_class_name_snakecase.gsub("_", "-") + "s"
end

# Decorator

def decorator_class_name
model_class_name + "Decorator"
end

def decorator_full_class_name
"#{resource_top_module}::Api::Decorators::#{resource_class_name}Decorator"
end

def decorator_instance_name
model_class_name_snakecase + "_decorator"
end

# Service

def service_class_full_name
model_full_class_name.split("::")[0..1].join("::") + "::Service"
end

def service_class_name
service_class_full_name.split("::").last
end

def model_secondary_module
model_full_class_name.split("::")[1]
end

def model_instance_name
model_class_name.snakecase
end

def policy_name
model_secondary_module.snakecase + "::" + model_class_name.snakecase
end

def service_instance_name
model_class_name.snakecase + "_service"
end

# Repository

def repository_class_full_name
model_full_class_name.split("::")[0..1].join("::") + "::Repository"
end

def repository_class_name
repository_class_full_name.split("::").last
end

def repository_instance_name
model_class_name.snakecase + "_repository"
end

# Table

def table_name
model_class_name.snakecase
end

def model_class_name_snakecase
model_class_name.snakecase
end

# File paths

def migration_template_path
File.join(__dir__, "templates", "migration.erb")
end

def model_path
LIB_DIR.join(*model_full_class_name.split("::").collect(&:snakecase)).to_s.chomp("/") + ".rb"
end

def resource_path
LIB_DIR.join(model_top_module.snakecase, "api", "resources", model_class_name_snakecase + ".rb")
end

def resource_spec_path
resource_path.to_s.gsub(LIB_DIR.to_s, SPEC_DIR.to_s).gsub(".rb", "_spec.rb")
end

def resource_require_path
Pathname.new(resource_path).relative_path_from(LIB_DIR).to_s.chomp(".rb")
end

def decorator_path
LIB_DIR.join(model_top_module.snakecase, "api", "decorators", model_class_name_snakecase + "_decorator.rb")
end

def decorator_require_path
Pathname.new(decorator_path).relative_path_from(LIB_DIR).to_s.chomp(".rb")
end

def service_path
LIB_DIR.join(*service_class_full_name.split("::").collect(&:snakecase)).to_s.chomp("/") + ".rb"
end

def service_require_path
Pathname.new(service_path).relative_path_from(LIB_DIR).to_s.chomp(".rb")
end

def service_spec_path
service_path.to_s.gsub(LIB_DIR.to_s, SPEC_DIR.to_s).gsub(".rb", "_spec.rb")
end

def repository_path
LIB_DIR.join(*repository_class_full_name.split("::").collect(&:snakecase)).to_s.chomp("/") + ".rb"
end

def repository_require_path
Pathname.new(repository_path).relative_path_from(LIB_DIR).to_s.chomp(".rb")
end

def repository_spec_path
repository_path.to_s.gsub(LIB_DIR.to_s, SPEC_DIR.to_s).gsub(".rb", "_spec.rb")
end

# Generate

def generate_migration_file
generate_file(migration_template_path, migration_path)
end

def generate_model_file
generate_file(TEMPLATE_DIR.join("model.erb"), model_path)
end

def generate_resource_file
generate_file(TEMPLATE_DIR.join("resource.erb"), resource_path)
end

def generate_resource_spec
generate_file(TEMPLATE_DIR.join("resource_spec.rb.erb"), resource_spec_path)
end

def generate_decorator_file
generate_file(TEMPLATE_DIR.join("decorator.rb.erb"), decorator_path)
end

def generate_service_file
generate_file(TEMPLATE_DIR.join("service.rb.erb"), service_path)
end

def generate_service_spec_file
generate_file(TEMPLATE_DIR.join("service_spec.rb.erb"), service_spec_path)
end

def generate_repository_file
generate_file(TEMPLATE_DIR.join("repository.rb.erb"), repository_path)
end

def generate_repository_spec_file
generate_file(TEMPLATE_DIR.join("repository_spec.rb.erb"), repository_spec_path)
end


def generate_file(template, destination)
puts "Generating file #{destination}"
file_content = ERB.new(File.read(template)).result(binding).tap { |it| puts it }
if !DRY_RUN
FileUtils.mkdir_p(File.dirname(destination))
File.open(destination, "w") { |file| file << file_content }
end
end

generate_migration_file
generate_model_file
generate_resource_file
generate_resource_spec
generate_decorator_file
generate_service_file
generate_service_spec_file
generate_repository_file
generate_repository_spec_file
13 changes: 13 additions & 0 deletions scaffolding/templates/decorator.rb.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
require 'pact_broker/api/decorators/base_decorator'

module <%= resource_top_module %>
module Api
module Decorators
class <%= decorator_class_name %> < BaseDecorator
property :uuid

include Timestamps
end
end
end
end
Empty file.
12 changes: 12 additions & 0 deletions scaffolding/templates/migration.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Sequel.migration do
change do
create_table(:<%= table_name %>, charset: 'utf8') do
primary_key :id
String :uuid, null: false

DateTime :created_at, null: false
DateTime :updated_at, null: false
index [:uuid], unique: true, name: "<%= table_name %>_uuid_index"
end
end
end
14 changes: 14 additions & 0 deletions scaffolding/templates/model.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
require 'sequel'
require 'pact_broker/repositories/helpers'

module <%= model_top_module %>
module <%= model_secondary_module %>
class <%= model_class_name %> < Sequel::Model
plugin :timestamps, update_on_create: true

dataset_module do
include PactBroker::Repositories::Helpers
end
end
end
end
18 changes: 18 additions & 0 deletions scaffolding/templates/repository.rb.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require 'pact_broker/logging'
require 'pact_broker/error'

module <%= model_top_module %>
module <%= model_secondary_module %>
class <%= repository_class_name %>
include PactBroker::Logging

def self.find_by_uuid(uuid)
<%= model_class_name %>.where(uuid: uuid).single_record
end

def self.find_by_uuid!(uuid)
find_by_uuid(uuid) or raise PactBroker::Error.new("<%= model_class_name %> with UUID #{uuid} does not exist")
end
end
end
end
9 changes: 9 additions & 0 deletions scaffolding/templates/repository_spec.rb.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require '<%= repository_require_path %>'

module <%= model_top_module %>
module <%= model_secondary_module %>
describe <%= repository_class_name %> do

end
end
end
46 changes: 46 additions & 0 deletions scaffolding/templates/resource.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require 'pact_broker/api/resources/base_resource'
require '<%= decorator_require_path %>'

module <%= resource_top_module %>
module Api
module Resources
class <%= resource_class_name %> < BaseResource
def content_types_provided
[["application/hal+json", :to_json]]
end

def allowed_methods
["GET", "OPTIONS"]
end

def resource_exists?
!!<%= model_instance_name %>
end

def to_json
decorator_class(:<%= decorator_instance_name %>).new(<%= model_instance_name %>).to_json(decorator_options)
end

def policy_name
:<%= policy_name %>
end

private

attr_reader :<%= model_instance_name %>

def <%= model_instance_name %>
@<%= model_instance_name %> ||= <%= service_instance_name %>.find_by_uuid(uuid)
end

# DELETE THIS!!! It's just here so that the generated test can be run
def <%= service_instance_name %>
end

def uuid
identifier_from_path[:<%= model_instance_name %>_uuid]
end
end
end
end
end
Loading

0 comments on commit 2079211

Please sign in to comment.