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

Allow developers to define #call with arguments for convenience #1

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
692f4f7
add rubocop
hedgesky Mar 22, 2017
a18c417
drop support for Rubies < 2.1.0
hedgesky Mar 22, 2017
6e3ea2a
Build the "v4" branch on Travis
laserlemon Apr 21, 2015
6c1c586
Drop support for Ruby 1.9 and add support for Ruby 2.2
laserlemon Apr 21, 2015
8dc9bb3
Reconfigure Travis to use container-based builds and to cache the bundle
laserlemon Apr 21, 2015
e5d48f7
Update gem dependency version requirements
laserlemon Apr 21, 2015
90c9575
Target version 4.0.0
laserlemon Apr 21, 2015
e6b19d9
Use RSpec's verbose "documentation" formatting by default
laserlemon Apr 22, 2015
0ffa4e9
implemented requested changes (#124)
hedgesky Mar 23, 2017
633555b
configure rubocop to Ruby 2.0 syntax
hedgesky Mar 23, 2017
5a6f451
return Ruby 2.0 to Travis config
hedgesky Mar 23, 2017
cfd4720
Reconfigure the CodeClimate test reporter for version 1.0+
laserlemon Mar 24, 2017
c2715e7
Update test dependency version requirements
laserlemon Mar 31, 2017
372d608
Fix new Rubocop failures as of version 0.48.0
laserlemon Mar 31, 2017
13a97c8
allow multiple organize calls in an organizer (implements #127)
hedgesky Mar 26, 2017
780d202
Remove bundler development dependency
taylorthurlow Jul 12, 2020
71ffe10
Update rake development dependency to 13.x
taylorthurlow Jul 12, 2020
b078430
Use Rubocop 0.85.x because it supports the latest standardrb version
taylorthurlow Jul 12, 2020
ba6c79c
Drop support for all EOL Ruby versions (< 2.5)
taylorthurlow Jul 12, 2020
3bbca93
Run "standardrb --fix"
taylorthurlow Jul 12, 2020
d667412
Fix standardrb warning
taylorthurlow Jul 12, 2020
8ab998a
Allow developers to define #call with arguments for convenience
laserlemon Mar 31, 2017
8bc3e8c
Address Rubocop concerns
laserlemon Mar 31, 2017
2d9d0ed
Refactor Interactor::Context#fail! to be simpler
laserlemon Mar 31, 2017
c36f52b
Back out the concept of magically assigning positional call arguments
laserlemon Apr 28, 2017
5a68345
replace raise with throw to handle context failure (#126)
hedgesky Mar 25, 2017
6e34662
avoid context inner logic duplicating
hedgesky Mar 26, 2017
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
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
--color
--format documentation
--order random
--require spec_helper
47 changes: 47 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# This should always correspond to the required Ruby version specified in the
# gemspec.
AllCops:
TargetRubyVersion: 2.5

# TODO: What should we do here?
Style/FrozenStringLiteralComment:
Enabled: false

# Allow some style changes in the specs.
AmbiguousBlockAssociation:
Exclude:
- spec/**/*
Metrics/BlockLength:
Exclude:
- spec/**/*
Metrics/ModuleLength:
Exclude:
- spec/**/*
Style/BlockDelimiters:
Exclude:
- spec/**/*

# Here, inconsistent indentation helps to understand tree nature of callbacks.
Layout/ArrayAlignment:
Exclude:
- spec/integration_spec.rb

# TODO: Remove when throw is used rather than raise in Interactor::Context.fail!
Lint/SuppressedException:
Exclude:
- lib/interactor.rb
Style/RescueModifier:
Exclude:
- spec/**/*

# These style conventions are personal preference.
Style/EmptyMethod:
Enabled: false
Layout/IndentArray:
EnforcedStyle: consistent
Style/StringLiterals:
EnforcedStyle: double_quotes
Style/SymbolArray:
Enabled: false
Style/WordArray:
Enabled: false
2 changes: 1 addition & 1 deletion .standard.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ruby_version: 2.3.0
ruby_version: 2.5.0
ignore:
- 'spec/**/*':
- Lint/AmbiguousBlockAssociation
18 changes: 3 additions & 15 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,21 @@ before_install:
branches:
only:
- master
- v3
env:
global:
- secure: | # CODECLIMATE_REPO_TOKEN
BIemhM273wHZMpuULDMYGPsxYdfw+NMw7IQbOD6gy5r+dha07y9ssTYYE5Gn
t1ptAb09lhQ4gexXTr83i6angMrnHgQ1ZX2wfeoZ0FvWDHQht9YkXyiNH+R6
odHUeDIYAlUiqLX9nAkklL89Rc22BrHMGGNyuA8Uc5sktW5P/FE=
- v4
cache: bundler
language: ruby
matrix:
allow_failures:
- rvm: "2.0"
- rvm: "2.1"
- rvm: "2.2"
- rvm: ruby-head
notifications:
webhooks:
on_start: always
urls:
- http://buildlight.collectiveidea.com/
rvm:
- "2.0"
- "2.1"
- "2.2"
- "2.3"
- "2.4"
- "2.5"
- "2.6"
- "2.7"
- ruby-head
script: bundle exec rake
sudo: false
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.0.0 / Unreleased

* [ENHANCEMENT] Drop support for all EOL Ruby versions (< 2.5)

## 3.1.2 / 2019-12-29
* [BUGFIX] Fix Context#fail! on Ruby 2.7

Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ gem "standard"
group :test do
gem "codeclimate-test-reporter", require: false
gem "rspec", "~> 3.7"
gem "rubocop", "~> 0.85.0"
end
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
Add Interactor to your Gemfile and `bundle install`.

```ruby
gem "interactor", "~> 3.0"
gem "interactor", "~> 4.0"
```

## What is an Interactor?
Expand Down
2 changes: 2 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "rubocop/rake_task"
require "standard/rake"

RSpec::Core::RakeTask.new(:spec)
RuboCop::RakeTask.new(:rubocop)

task default: [:spec, :standard]
8 changes: 4 additions & 4 deletions interactor.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ require "English"

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

spec.author = "Collective Idea"
spec.email = "[email protected]"
Expand All @@ -11,9 +11,9 @@ Gem::Specification.new do |spec|
spec.homepage = "https://github.com/collectiveidea/interactor"
spec.license = "MIT"

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

spec.add_development_dependency "bundler"
spec.add_development_dependency "rake"
spec.required_ruby_version = ">= 2.5"

spec.add_development_dependency "rake", "~> 13.0"
end
46 changes: 37 additions & 9 deletions lib/interactor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,17 @@ def initialize(context = {})
#
# Returns nothing.
def run
run!
rescue Failure
catch(:early_return) do
with_hooks do
call(*arguments_for_call)
context.called!(self)
end
end

context.rollback! if context.failure?
rescue
context.rollback!
raise
end

# Internal: Invoke an Interactor instance along with all defined hooks. The
Expand All @@ -139,13 +148,8 @@ def run
# Returns nothing.
# Raises Interactor::Failure if the context is failed.
def run!
with_hooks do
call
context.called!(self)
end
rescue
context.rollback!
raise
run
raise(Failure, context) if context.failure?
end

# Public: Invoke an Interactor instance without any hooks, tracking, or
Expand All @@ -163,4 +167,28 @@ def call
# Returns nothing.
def rollback
end

private

# Internal: Determine what keyword arguments (if any) should be passed to the
# "call" instance method when invoking an Interactor. The "call" instance
# method may accept any number of keyword arguments. This method will extract
# values from the context in order to populate those arguments based on their
# names.
#
# Returns an Array of arguments to be applied as an argument list.
def arguments_for_call
positional_arguments = []
keyword_arguments = {}

method(:call).parameters.each do |(type, name)|
next unless type == :keyreq || type == :key
next unless context.include?(name)

keyword_arguments[name] = context[name]
end

positional_arguments << keyword_arguments if keyword_arguments.any?
positional_arguments
end
end
19 changes: 16 additions & 3 deletions lib/interactor/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class Context < OpenStruct
#
# Returns the Interactor::Context.
def self.build(context = {})
self === context ? context : new(context)
context.is_a?(Context) ? context : new(context)
end

# Public: Whether the Interactor::Context is successful. By default, a new
Expand Down Expand Up @@ -121,9 +121,13 @@ def failure?
#
# Raises Interactor::Failure initialized with the Interactor::Context.
def fail!(context = {})
context.each { |key, value| self[key.to_sym] = value }
context.each { |key, value| self[key] = value }
@failure = true
raise Failure, self
signal_early_return!
end

def signal_early_return!
throw :early_return
end

# Internal: Track that an Interactor has been called. The "called!" method
Expand Down Expand Up @@ -158,6 +162,15 @@ def rollback!
@rolled_back = true
end

# Public: Check for the presence of a given key in the context. This does
# not check whether the value is truthy, just whether the key is set to any
# value at all.
#
# Returns true if the key is found or false otherwise.
def include?(key)
table.include?(key.to_sym)
end

# Internal: An Array of successfully called Interactor instances invoked
# against this Interactor::Context instance.
#
Expand Down
8 changes: 5 additions & 3 deletions lib/interactor/organizer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module Interactor
# class MyOrganizer
# include Interactor::Organizer
#
# organizer InteractorOne, InteractorTwo
# organize InteractorOne, InteractorTwo
# end
module Organizer
# Internal: Install Interactor::Organizer's behavior in the given class.
Expand Down Expand Up @@ -41,11 +41,12 @@ module ClassMethods
# include Interactor::Organizer
#
# organize [InteractorThree, InteractorFour]
# organize InteractorFive
# end
#
# Returns nothing.
def organize(*interactors)
@organized = interactors.flatten
organized.concat(interactors.flatten)
end

# Internal: An Array of declared Interactors to be invoked.
Expand Down Expand Up @@ -76,7 +77,8 @@ module InstanceMethods
# Returns nothing.
def call
self.class.organized.each do |interactor|
interactor.call!(context)
context.signal_early_return! if context.failure?
interactor.call(context)
end
end
end
Expand Down
Loading