Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the Context class swapable #172

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions interactor.gemspec
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
require "English"

Gem::Specification.new do |spec|
spec.name = "interactor"
spec.name = "interactor"
spec.version = "3.1.1"

spec.author = "Collective Idea"
spec.email = "[email protected]"
spec.author = "Collective Idea"
spec.email = "[email protected]"
spec.description = "Interactor provides a common interface for performing complex user interactions."
spec.summary = "Simple interactor implementation"
spec.homepage = "https://github.com/collectiveidea/interactor"
spec.license = "MIT"
spec.summary = "Simple interactor implementation"
spec.homepage = "https://github.com/collectiveidea/interactor"
spec.license = "MIT"

spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
spec.test_files = spec.files.grep(/^spec/)

spec.add_development_dependency "bundler"
Expand Down
10 changes: 9 additions & 1 deletion lib/interactor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ def call(context = {})
def call!(context = {})
new(context).tap(&:run!).context
end

def context_class
@context_class || Interactor::Context
end

def context_class=(klass)
@context_class = klass
end
end

# Internal: Initialize an Interactor.
Expand All @@ -91,7 +99,7 @@ def call!(context = {})
# MyInteractor.new
# # => #<MyInteractor @context=#<Interactor::Context>>
def initialize(context = {})
@context = Context.build(context)
@context = self.class.context_class.build(context)
end

# Internal: Invoke an interactor instance along with all defined hooks. The
Expand Down
295 changes: 154 additions & 141 deletions lib/interactor/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,153 +29,166 @@ module Interactor
# context
# # => #<Interactor::Context foo="baz" hello="world">
class Context < OpenStruct
# Internal: Initialize an Interactor::Context or preserve an existing one.
# If the argument given is an Interactor::Context, the argument is returned.
# Otherwise, a new Interactor::Context is initialized from the provided
# hash.
#
# The "build" method is used during interactor initialization.
#
# context - A Hash whose key/value pairs are used in initializing a new
# Interactor::Context object. If an existing Interactor::Context
# is given, it is simply returned. (default: {})
#
# Examples
#
# context = Interactor::Context.build(foo: "bar")
# # => #<Interactor::Context foo="bar">
# context.object_id
# # => 2170969340
# context = Interactor::Context.build(context)
# # => #<Interactor::Context foo="bar">
# context.object_id
# # => 2170969340
#
# Returns the Interactor::Context.
def self.build(context = {})
self === context ? context : new(context)
end
module Mixin
def self.included(receiver)
receiver.extend ClassMethods
receiver.send :include, InstanceMethods
end

# Public: Whether the Interactor::Context is successful. By default, a new
# context is successful and only changes when explicitly failed.
#
# The "success?" method is the inverse of the "failure?" method.
#
# Examples
#
# context = Interactor::Context.new
# # => #<Interactor::Context>
# context.success?
# # => true
# context.fail!
# # => Interactor::Failure: #<Interactor::Context>
# context.success?
# # => false
#
# Returns true by default or false if failed.
def success?
!failure?
end
module ClassMethods
# Internal: Initialize an Interactor::Context or preserve an existing one.
# If the argument given is an Interactor::Context, the argument is returned.
# Otherwise, a new Interactor::Context is initialized from the provided
# hash.
#
# The "build" method is used during interactor initialization.
#
# context - A Hash whose key/value pairs are used in initializing a new
# Interactor::Context object. If an existing Interactor::Context
# is given, it is simply returned. (default: {})
#
# Examples
#
# context = Interactor::Context.build(foo: "bar")
# # => #<Interactor::Context foo="bar">
# context.object_id
# # => 2170969340
# context = Interactor::Context.build(context)
# # => #<Interactor::Context foo="bar">
# context.object_id
# # => 2170969340
#
# Returns the Interactor::Context.
def build(context = {})
self === context ? context : new(context)
end
end

# Public: Whether the Interactor::Context has failed. By default, a new
# context is successful and only changes when explicitly failed.
#
# The "failure?" method is the inverse of the "success?" method.
#
# Examples
#
# context = Interactor::Context.new
# # => #<Interactor::Context>
# context.failure?
# # => false
# context.fail!
# # => Interactor::Failure: #<Interactor::Context>
# context.failure?
# # => true
#
# Returns false by default or true if failed.
def failure?
@failure || false
end
module InstanceMethods
# Public: Whether the Interactor::Context is successful. By default, a new
# context is successful and only changes when explicitly failed.
#
# The "success?" method is the inverse of the "failure?" method.
#
# Examples
#
# context = Interactor::Context.new
# # => #<Interactor::Context>
# context.success?
# # => true
# context.fail!
# # => Interactor::Failure: #<Interactor::Context>
# context.success?
# # => false
#
# Returns true by default or false if failed.
def success?
!failure?
end

# Public: Fail the Interactor::Context. Failing a context raises an error
# that may be rescued by the calling interactor. 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
# Interactor::Context instance. (default: {})
#
# Examples
#
# context = Interactor::Context.new
# # => #<Interactor::Context>
# context.fail!
# # => Interactor::Failure: #<Interactor::Context>
# context.fail! rescue false
# # => false
# context.fail!(foo: "baz")
# # => Interactor::Failure: #<Interactor::Context foo="baz">
#
# Raises Interactor::Failure initialized with the Interactor::Context.
def fail!(context = {})
context.each { |key, value| modifiable[key.to_sym] = value }
@failure = true
raise Failure, self
end
# Public: Whether the Interactor::Context has failed. By default, a new
# context is successful and only changes when explicitly failed.
#
# The "failure?" method is the inverse of the "success?" method.
#
# Examples
#
# context = Interactor::Context.new
# # => #<Interactor::Context>
# context.failure?
# # => false
# context.fail!
# # => Interactor::Failure: #<Interactor::Context>
# context.failure?
# # => true
#
# Returns false by default or true if failed.
def failure?
@failure || false
end

# Internal: Track that an Interactor has been called. The "called!" method
# is used by the interactor being invoked with this context. After an
# interactor is successfully called, the interactor instance is tracked in
# the context for the purpose of potential future rollback.
#
# interactor - An Interactor instance that has been successfully called.
#
# Returns nothing.
def called!(interactor)
_called << interactor
end
# Public: Fail the Interactor::Context. Failing a context raises an error
# that may be rescued by the calling interactor. 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
# Interactor::Context instance. (default: {})
#
# Examples
#
# context = Interactor::Context.new
# # => #<Interactor::Context>
# context.fail!
# # => Interactor::Failure: #<Interactor::Context>
# context.fail! rescue false
# # => false
# context.fail!(foo: "baz")
# # => Interactor::Failure: #<Interactor::Context foo="baz">
#
# Raises Interactor::Failure initialized with the Interactor::Context.
def fail!(context = {})
context.each { |key, value| send("#{key}=", value) }
@failure = true
raise Failure, self
end

# Public: Roll back the Interactor::Context. Any interactors to which this
# context has been passed and which have been successfully called are asked
# to roll themselves back by invoking their "rollback" instance methods.
#
# Examples
#
# context = MyInteractor.call(foo: "bar")
# # => #<Interactor::Context foo="baz">
# context.rollback!
# # => true
# context
# # => #<Interactor::Context foo="bar">
#
# Returns true if rolled back successfully or false if already rolled back.
def rollback!
return false if @rolled_back
_called.reverse_each(&:rollback)
@rolled_back = true
end
# Internal: Track that an Interactor has been called. The "called!" method
# is used by the interactor being invoked with this context. After an
# interactor is successfully called, the interactor instance is tracked in
# the context for the purpose of potential future rollback.
#
# interactor - An Interactor instance that has been successfully called.
#
# Returns nothing.
def called!(interactor)
_called << interactor
end

# Internal: An Array of successfully called Interactor instances invoked
# against this Interactor::Context instance.
#
# Examples
#
# context = Interactor::Context.new
# # => #<Interactor::Context>
# context._called
# # => []
#
# context = MyInteractor.call(foo: "bar")
# # => #<Interactor::Context foo="baz">
# context._called
# # => [#<MyInteractor @context=#<Interactor::Context foo="baz">>]
#
# Returns an Array of Interactor instances or an empty Array.
def _called
@called ||= []
# Public: Roll back the Interactor::Context. Any interactors to which this
# context has been passed and which have been successfully called are asked
# to roll themselves back by invoking their "rollback" instance methods.
#
# Examples
#
# context = MyInteractor.call(foo: "bar")
# # => #<Interactor::Context foo="baz">
# context.rollback!
# # => true
# context
# # => #<Interactor::Context foo="bar">
#
# Returns true if rolled back successfully or false if already rolled back.
def rollback!
return false if @rolled_back
_called.reverse_each(&:rollback)
@rolled_back = true
end

# Internal: An Array of successfully called Interactor instances invoked
# against this Interactor::Context instance.
#
# Examples
#
# context = Interactor::Context.new
# # => #<Interactor::Context>
# context._called
# # => []
#
# context = MyInteractor.call(foo: "bar")
# # => #<Interactor::Context foo="baz">
# context._called
# # => [#<MyInteractor @context=#<Interactor::Context foo="baz">>]
#
# Returns an Array of Interactor instances or an empty Array.
def _called
@called ||= []
end
end
end

include Mixin
end
end
Loading