Skip to content

Instantly share code, notes, and snippets.

@joegaudet
Created October 9, 2017 16:46
Show Gist options
  • Save joegaudet/e4d7410d9a3182158de7e1e6538eab4b to your computer and use it in GitHub Desktop.
Save joegaudet/e4d7410d9a3182158de7e1e6538eab4b to your computer and use it in GitHub Desktop.
command-macro
# example usage
class SomeService
# lets say Foo and Bar are AR models so they will be injected as classes
# I've been cooking up a further idea here to perhaps decorate the service
# call with another kind of dependency that would allow you to map the params
# to AR models or something... still a WIP though
dependency :foo_dao, Foo
dependency :bar_dao, Bar
dependency :logger, Logger, instance: Rails.logger
def call(foo_id, bar_id, some_other_param)
foo = foo_dao.find(foo_id)
bar = bar_dao.find(bar_id)
foo.bars << bar
foo.save!
logger.info("What's up")
foo
end
end
class SomeController < ActionController
command :foo, SomeService
end
# We have support for JR, which you probably don't need
module CommandSupport
def self.included(base)
base.extend(ClassMethods)
raise 'CommandSupport can only be included on controllers' unless base < ActionController::Base
end
private
module ClassMethods
# defines a command that creates some resource
# Defines a new controller command
# @param [Symbol] command_name
# @param [Class] service_class
# @param [Object] options
# @!macro [attach] command
# @return [$2] the $1 $0
def create_command(command_name, service_class, options = {})
options[:success_status] = :created
command(command_name, service_class, options)
end
# Defines a new controller command
# @param [Symbol] command_name
# @param [Class] service_class
# @param [Object] options
# @!macro [attach] command
# @return [$2] the $1 $0
def command(command_name, service_class, options = {})
service_instance = options[:service_instance]
with_mapping = options[:mapping]
ignore_resource = options[:ignore_resource]
no_content = options[:ignore_resource] || options[:no_content]
success_status = options[:success_status]
include = options.fetch(:include, '').underscore.split(',')
# extract the call parameters from the service class
call_param_names = service_class.instance_method(:call).parameters.flatten.reject {|p| p == :req}
# define a command for the controller
send :define_method, command_name do
# create strong params either with a mapping or with the service interface
strong_params = with_mapping.present? ? with_mapping.(params) : params.permit(*call_param_names)
# think about service injection via dependency here...
service = service_instance || service_class.new
# build call arguments out of params here
args = call_param_names.map {|param_name| strong_params[param_name]}
# call the service on the the args list, since we use the call params above to enforce the requirements
# we should have an args list that matches the service interface
service_result = service.(*args)
# If we are using a JR controller there will be a resource_klass present
if no_content
head :no_content
else
if ignore_resource
content_type = :json
result = service_result
else
content_type = JSONAPI::MEDIA_TYPE
# TODO: support array commands
# Break into the resource serializer that is on the controller by default
# and add includes
resource_serializer.instance_variable_set(:@include_directives, JSONAPI::IncludeDirectives.new(resource_klass, include))
resource = resource_klass.new(service_result, context)
result = resource_serializer.serialize_to_hash(resource)
end
render json: result, status: success_status || :ok, content_type: content_type
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment