Created
December 4, 2014 05:20
-
-
Save amoslanka/981104b9c7deb8e75343 to your computer and use it in GitHub Desktop.
A stripped down version of interactors.
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
require "ostruct" | |
# A base Service Object module. | |
# | |
# Example usage: | |
# | |
# class DoSomething | |
# include Service | |
# def call | |
# puts context.foo | |
# end | |
# end | |
# | |
# Alternatively, execute the service object and allow failures to raise errors. | |
# | |
# DoSomething.call! foo: :bar | |
# | |
# To declare a failure within a service's call method: | |
# | |
# class DoSomething | |
# include Service | |
# def call | |
# fail! | |
# end | |
# end | |
# | |
# Invoke a service object: | |
# | |
# DoSomething.call foo: :bar | |
# | |
# The key `:foo` with the value `:bar` will be set on the context of the service | |
# object, available to the call method when it runs and to the caller of the | |
# service when it is complete. | |
# | |
# Any values assigned to the context will be returned by the call method in the | |
# context object after the call method is complete. | |
# | |
# class DoSomething | |
# include Service | |
# def call | |
# context.foo = :bar | |
# end | |
# end | |
# | |
# result = DoSomething.call | |
# puts result.foo | |
# => :bar | |
# | |
# | |
module Service | |
# Internal: Install Interactor's behavior in the given class. | |
def self.included(base) | |
base.class_eval do | |
extend ClassMethods | |
# Public: Gets the Interactor::Context of the Interactor instance. | |
attr_reader :context | |
end | |
end | |
# Internal: Interactor class methods. | |
module ClassMethods | |
# Public: Invoke the service object. Wraps up instantiation and calling the | |
# #call method on the instance. Returns the context. | |
# | |
# context - A hash of object for service object to act upon. | |
def call(context={}) | |
new(context).tap(&:run).context | |
end | |
# Public: Invoke the service object, the same as `.call`, but allow failures | |
# to raise errors. | |
# | |
# context - A hash of object for service object to act upon. | |
def call!(context={}) | |
new(context).tap(&:run!).context | |
end | |
end | |
# Public: Initialize a service object. | |
def initialize(context={}) | |
@context = Context.build(context) | |
end | |
# Public: The primary process method. Override this method in your service object. | |
def call | |
call! | |
rescue Failure | |
end | |
# Internal: Used by the class to invoke the call method with rescue protection. | |
def run | |
run! | |
rescue Failure | |
end | |
# Internal: Used by the class to invoke the call method without rescue protection. | |
def run!; call; end | |
# Public: The object for tracking state of a service object's interaction with | |
# the subjects it acts upon. The context contains all arguments initially passed | |
# into the invokation, and is the object returned after the process is complete. | |
# The service object manipulates the context to produce the result of invocation. | |
# | |
# A context is little more than an OpenStruct and can be manipulated using hash | |
# or dot syntax. | |
# | |
# Examples | |
# | |
# context = Service::Context.new | |
# # => #<Service::Context> | |
# context.foo = "bar" | |
# # => "bar" | |
# context | |
# # => #<Service::Context foo="bar"> | |
# context.hello = "world" | |
# # => "world" | |
# context | |
# # => #<Service::Context foo="bar" hello="world"> | |
# context.foo = "baz" | |
# # => "baz" | |
# context | |
# # => #<Service::Context foo="baz" hello="world"> | |
class Context < OpenStruct | |
# Internal: Initialize a context or preserve an existing one. If the argument | |
# given is a context, the argument is returned. Otherwise, a new context is | |
# initialized from the provided hash. | |
# | |
# The "build" method is used during service object initialization. | |
def self.build(context={}) | |
self === context ? context : new(context) | |
end | |
# Public: Whether the context, which represents the state of a service | |
# invokation, is successful. By default, a new context is successful and only | |
# changes when explicitly failed. | |
# | |
# Returns true by default or false if failed. | |
def success? | |
!failure? | |
end | |
# Public: Whether the context, which represents the state of a service | |
# invokation, has failed. By default, a new context is successful and only | |
# changes when explicitly failed. | |
# | |
# Returns false by default or true if failed. | |
def failure? | |
@failure || false | |
end | |
# Public: Fail the context, which represents the state of a service invokation. | |
# Failing a context raises an error that may be rescued by the calling service. | |
# The context is also flagged as having failed. | |
# | |
# Optionally the caller may provide a hash of key/value pairs to be merged | |
# into the context before failure. | |
# | |
# context - A Hash whose key/value pairs are merged into the existing | |
# context. (default: {}) | |
# | |
# Examples | |
# | |
# context = Service::Context.new | |
# # => #<Service::Context> | |
# context.fail! | |
# # => Service::Failure: #<Service::Context> | |
# context.fail! rescue false | |
# # => false | |
# context.fail!(foo: "baz") | |
# # => Service::Failure: #<Service::Context foo="baz"> | |
# | |
# Raises Service::Failure initialized with the Service::Context. | |
def fail!(context = {}) | |
modifiable.update(context) | |
@failure = true | |
raise Failure, self | |
end | |
end | |
# Internal: An error class representing the failure of a service to complete | |
# execution as it expects to do. A Failure error holds a reference to the | |
# context used in the service. | |
class Failure < StandardError | |
attr_reader :context | |
def initialize(context=nil) | |
@context = context | |
super | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment