Created
November 23, 2014 14:20
-
-
Save robyurkowski/0567a483e3e070f7ff16 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
require 'active_record' | |
module Skywalker | |
class Command | |
################################################################################ | |
# Class interface | |
################################################################################ | |
def self.call(*args) | |
new(*args).call | |
end | |
################################################################################ | |
# Instantiates command, setting all arguments. | |
################################################################################ | |
def initialize(**args) | |
self.args = args | |
self.args.freeze | |
parse_arguments | |
validate_arguments! | |
end | |
attr_accessor :on_success, | |
:on_failure, | |
:error, | |
:args | |
################################################################################ | |
# Ensure required keys are present. | |
################################################################################ | |
private def validate_arguments! | |
missing_args = required_args.map(&:to_s) - args.keys.map(&:to_s) | |
raise ArgumentError, "#{missing_args.join(", ")} required but not given" \ | |
if missing_args.any? | |
end | |
################################################################################ | |
# Any required keys should go here as either strings or symbols. | |
################################################################################ | |
private def required_args | |
[] | |
end | |
private def parse_arguments | |
args.each_pair do |reader_method, value| | |
writer_method = "#{reader_method}=" | |
singleton_class.class_eval do | |
send(:attr_reader, reader_method) unless respond_to?(reader_method) | |
send(:attr_writer, reader_method) unless respond_to?(writer_method) | |
end | |
self.send(writer_method, value) | |
end | |
end | |
################################################################################ | |
# Call: runs the transaction and all operations. | |
################################################################################ | |
def call | |
transaction do | |
execute! | |
confirm_success | |
end | |
rescue Exception => error | |
confirm_failure error | |
end | |
################################################################################ | |
# Operations should be defined in this method. | |
################################################################################ | |
private def execute! | |
end | |
################################################################################ | |
# Override to customize. | |
################################################################################ | |
private def transaction(&block) | |
::ActiveRecord::Base.transaction(&block) | |
end | |
################################################################################ | |
# Trigger the given callback on success | |
################################################################################ | |
private def confirm_success | |
run_success_callbacks | |
end | |
private def run_success_callbacks | |
on_success.call(self) if on_success.respond_to?(:call) | |
end | |
################################################################################ | |
# Set the error so we can get it with `command.error`, and trigger error. | |
################################################################################ | |
private def confirm_failure(error) | |
self.error = error | |
run_failure_callbacks | |
end | |
private def run_failure_callbacks | |
on_failure.call(self) if on_failure.respond_to?(:call) | |
end | |
end | |
end |
This file contains hidden or 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
require 'spec_helper' | |
require 'skywalker/command' | |
module Skywalker | |
RSpec.describe Command do | |
describe "convenience" do | |
it "provides a class call method that instantiates and calls" do | |
expect(Command).to receive_message_chain('new.call') | |
Command.call | |
end | |
end | |
describe "instantiation" do | |
it "freezes the arguments given to it" do | |
command = Command.new(a_symbol: :my_symbol) | |
expect(command.args).to be_frozen | |
end | |
it "accepts a variable list of arguments" do | |
expect { Command.new(a_symbol: :my_symbol, a_string: "my string") }.not_to raise_error | |
end | |
it "sets a reader for each argument" do | |
command = Command.new(a_symbol: :my_symbol) | |
expect(command).to respond_to(:a_symbol) | |
end | |
it "sets a writer for each argument" do | |
command = Command.new(a_symbol: :my_symbol) | |
expect(command).to respond_to(:a_symbol=) | |
end | |
it "sets the instance variable to the passed value" do | |
command = Command.new(a_symbol: :my_symbol) | |
expect(command.a_symbol).to eq(:my_symbol) | |
end | |
it "raises an error if an argument in its required_args is not present" do | |
allow_any_instance_of(Command).to receive(:required_args).and_return([:required_arg]) | |
expect { Command.new }.to raise_error | |
end | |
it "does not raise an error if an argument in its required_args is present" do | |
allow_any_instance_of(Command).to receive(:required_args).and_return([:required_arg]) | |
expect { Command.new(required_arg: :blah) }.not_to raise_error | |
end | |
end | |
describe "validity control" do | |
let(:command) { Command.new } | |
it "executes in a transaction" do | |
expect(command).to receive(:transaction) | |
command.call | |
end | |
end | |
describe "execution" do | |
before do | |
allow(command).to receive(:transaction).and_yield | |
end | |
describe "success handling" do | |
let(:on_success) { double("on_success callback") } | |
let(:command) { Command.new(on_success: on_success) } | |
before do | |
allow(command).to receive(:execute!).and_return(true) | |
end | |
it "triggers the confirm_success method" do | |
expect(command).to receive(:confirm_success) | |
command.call | |
end | |
it "runs the success callbacks" do | |
expect(command).to receive(:run_success_callbacks) | |
command.call | |
end | |
describe "on_success" do | |
context "when on_success is callable" do | |
it "calls the on_success callback with itself" do | |
expect(on_success).to receive(:call).with(command) | |
command.call | |
end | |
end | |
context "when on_success is not callable" do | |
let(:nil_callback) { double("nil_callback") } | |
let(:command) { Command.new(on_success: nil_callback) } | |
it "does not call on_success" do | |
expect(nil_callback).not_to receive(:call) | |
command.call | |
end | |
end | |
end | |
end | |
describe "failure handling" do | |
let(:on_failure) { double("on_failure callback") } | |
let(:command) { Command.new(on_failure: on_failure) } | |
before do | |
allow(command).to receive(:execute!).and_raise(ScriptError) | |
end | |
it "triggers the confirm_failure method" do | |
expect(command).to receive(:confirm_failure) | |
command.call | |
end | |
it "sets the error on the command" do | |
allow(on_failure).to receive(:call) | |
expect(command).to receive(:error=) | |
command.call | |
end | |
it "runs the failure callbacks" do | |
allow(command).to receive(:error=) | |
expect(command).to receive(:run_failure_callbacks) | |
command.call | |
end | |
describe "on_failure" do | |
before do | |
allow(command).to receive(:error=) | |
end | |
context "when on_failure is callable" do | |
it "calls the on_failure callback with itself" do | |
expect(on_failure).to receive(:call).with(command) | |
command.call | |
end | |
end | |
context "when on_failure is not callable" do | |
let(:nil_callback) { double("nil_callback") } | |
let(:command) { Command.new(on_failure: nil_callback) } | |
it "does not call on_failure" do | |
expect(nil_callback).not_to receive(:call) | |
command.call | |
end | |
end | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment