Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrise86/bea1162bc2fefd6ddb860de664ccaf24 to your computer and use it in GitHub Desktop.
Save chrise86/bea1162bc2fefd6ddb860de664ccaf24 to your computer and use it in GitHub Desktop.
Rails generator for services and lib objects
class ServiceGenerator < Rails::Generators::NamedBase
argument :arguments, type: :array, default: [], banner: "argument argument"
source_root File.expand_path('../templates', __FILE__)
check_class_collision suffix: "Service"
def create_service_files
template "service.rb.tt", File.join("lib", class_path, "#{file_name}_service.rb")
template(
'service_spec.rb.tt',
File.join('spec', 'lib', class_path, "#{file_name}_service_spec.rb"),
)
end
private
def file_name
@_file_name ||= remove_possible_suffix(super)
end
def remove_possible_suffix(name)
name.sub(/_?service$/i, "")
end
def class_name_without_module
file_name.camelize
end
# Wrap block with `module` blocks.
def module_namespacing(&block)
content = capture(&block)
class_path.reverse.each do |module_name|
content = wrap_with_module(module_name, content)
end
concat(content)
end
def indent(content, multiplier = 2)
spaces = " " * multiplier
content.each_line.map { |line| line.blank? ? line : "#{spaces}#{line}" }.join
end
def wrap_with_module(module_name, content) # :doc:
content = indent(content).chomp
"module #{module_name.camelize}\n#{content}\nend\n"
end
end
# frozen_string_literal: true
<% module_namespacing do -%>
class <%= class_name_without_module %>Service
class SpecificErrorClass < StandardError; end
<%- if arguments.any? -%>
def initialize(<%= arguments.map { |argument| "#{argument}:" }.join(', ') %>)
<%- for argument in arguments -%>
@<%= argument %> = <%= argument %>
<%- end -%>
end
<%- end -%>
def run!
raise SpecificErrorClass, "TODO: Implement this."
end
def run
run!
true
rescue SpecificErrorClass
false
end
private
<%- for argument in arguments -%>
attr_reader :<%= argument %>
<%- end -%>
end
<% end -%>
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe <%= class_name %>Service do
<%- if arguments.empty? -%>
let(:service) { described_class.new }
<%- else -%>
let(:service) {
described_class.new(
<%- for argument in arguments -%>
<%= argument %>: <%= argument %>,
<%- end -%>
)
}
<%- for argument in arguments -%>
let(:<%= argument %>) { nil }
<%- end -%>
<%- end -%>
before do
pending "TODO: Implement these specs and remove this `before` block."
end
describe '#run!' do
subject(:run!) { service.run! }
context 'when things go well' do
it 'does something'
end
context 'when things go wrong' do
it 'raises an error' do
expect { run! }.to raise_error(<%= class_name %>Service::SpecificErrorClass)
end
end
end
describe '#run' do
subject(:run) { service.run }
context 'when things go well' do
it 'returns true' do
expect(run).to be_truthy
end
end
context 'when things go wrong' do
it 'returns false' do
expect(run).to be_falsey
end
end
end
end
Description:
Stubs a new Service object in the `lib` folder and a spec for it.
Providing a list of argument names will set up the initializer to accept them
and create private accessors for each of them.
To create a service within a module, specify the service name as a
path like 'parent_module/service_name' or 'ParentModule::ServiceName'.
Example:
rails generate service ParentModule::Thing foo bar
This will create:
Service: lib/parent_module/thing_service.rb
Spec: spec/lib/parent_module/thing_service_spec.rb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment