Created
February 15, 2019 02:49
-
-
Save RichardJordan/2798389f9c3ca66ec9df5b6ca37b5234 to your computer and use it in GitHub Desktop.
Command Pattern example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This creates a nice chainable command pattern which is very powerful | |
# when pulling business logic out of controllers or models, and allows | |
# for observers and notifications to be easily added. | |
module RJ | |
module Utils | |
module Commands | |
class Base | |
include Errors | |
include Notifications | |
include NullListener | |
include OptionsHash | |
class << self | |
attr_reader :default_failure_callback, :default_success_callback | |
def call(inputs={}, **args) | |
new(inputs, **args).call | |
rescue NoMethodError => e | |
raise Errors::InvalidCommandInputs.new "Invalid Input #{e.message[/`(.*?)='/m, 1]}" | |
end | |
def for(inputs={}, **args) | |
new(inputs, **args) | |
rescue NoMethodError => e | |
raise Errors::InvalidCommandInputs.new "Invalid Input #{e.message[/`(.*?)='/m, 1]}" | |
end | |
def default_callbacks(**args) | |
@default_success_callback = args[:on_success] | |
@default_failure_callback = args[:on_failure] | |
end | |
def optional(*input_names) | |
input_names.each do |input_name| | |
optional_inputs << input_name | |
attr_accessor input_name | |
end | |
end | |
def optional_inputs | |
@optional_inputs ||= Set.new | |
end | |
def required(*input_names) | |
input_names.each do |input_name| | |
required_inputs << input_name | |
attr_accessor input_name | |
end | |
end | |
def required_inputs | |
@required_inputs ||= Set.new | |
end | |
end | |
attr_reader :listener, :on_success, :on_failure | |
def initialize(inputs={}, listener: nil, on_success: nil, on_failure: nil, **args) | |
@listener = listener || null_listener | |
@on_success = on_success || default_success_callback | |
@on_failure = on_failure || default_failure_callback | |
self.inputs = recursive_symbolize_keys inputs.merge!(args) | |
unless valid_command_inputs? | |
raise Errors::InvalidCommandInputs.new( | |
"Missing required inputs: '#{missing_command_inputs.join(', ')}'", | |
) | |
end | |
end | |
def call | |
raise NotImplementedError | |
end | |
def inputs=(inputs) | |
inputs.each { |input, value| public_send("#{input}=", value) } | |
end | |
def inputs | |
required_inputs.merge(optional_inputs) | |
end | |
def optional_inputs | |
self.class.optional_inputs.each_with_object({}) do |input, optional_inputs| | |
optional_inputs[input] = public_send(input) | |
end | |
end | |
def required_inputs | |
self.class.required_inputs.each_with_object({}) do |input, required_inputs| | |
required_inputs[input] = public_send(input) | |
end | |
end | |
private | |
def valid_command_inputs? | |
self.class.required_inputs.all? { |input| !send(input).nil? } | |
end | |
def missing_command_inputs | |
self.class.required_inputs.select { |input| send(input).nil? } | |
end | |
# Callbacks | |
def failure(*return_args) | |
notify_observers of_event: :failure | |
listener.public_send(on_failure, *return_args) | |
end | |
def success(*return_args) | |
notify_observers of_event: :success | |
listener.public_send(on_success, *return_args) | |
end | |
# Default Callbacks | |
def default_failure_callback | |
raise NotImplementedError unless self.class.default_failure_callback | |
self.class.default_failure_callback | |
end | |
def default_success_callback | |
raise NotImplementedError unless self.class.default_success_callback | |
self.class.default_success_callback | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment