Skip to content
Merged
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
2 changes: 1 addition & 1 deletion codequest_pipes.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Gem::Specification.new do |spec|
spec.name = 'codequest_pipes'
spec.version = '0.3.1.1'
spec.version = '0.3.2'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about 0.3.2.0 to keep consistent?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to use semantic versioning - that's why it consists of only three numbers now.


spec.author = 'codequest'
spec.email = 'hello@codequest.com'
Expand Down
53 changes: 39 additions & 14 deletions lib/codequest_pipes/context.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
require 'codequest_pipes/context/error_collector'

module Pipes
# Context is an object used to pass data between Pipes. It behaves like an
# OpenStruct except you can write a value only once - this way we prevent
# context keys from being overwritten.
class Context
attr_reader :error

# Override is an exception raised when an attempt is made to override an
# existing Context property.
class Override < ::StandardError; end
Expand All @@ -18,7 +18,7 @@ class ExecutionTerminated < ::StandardError; end
# @param values [Hash]
def initialize(values = {})
add(values)
@error = nil
@error_collector = ErrorCollector.new
end

# Method `add` allows adding new properties (as a Hash) to the Context.
Expand All @@ -32,31 +32,28 @@ def add(values)
end
end

# Quietly fail the pipe, allowing the error to be saved and accessed from
# the Context.
# Quietly fail the pipe. The error will be passed to the error_collector
# and stored in the :base errors collection.
#
# @param error [Any] Error to be set.
## @param error [String]
def halt(error = 'Execution stopped')
@error = error
add_errors(base: error)
end

# Explicitly fail the pipe, allowing the error to be saved and accessed from
# the Context.
#
# @param error [Any] Error to be set.
# Explicitly fail the pipe.
#
# @raise [ExecutionTerminated]
def terminate(error)
halt(error)
fail ExecutionTerminated, error
fail ExecutionTerminated
end

# Check if the Context finished successfully.
# This method smells of :reek:NilCheck
#
# @return [Boolean] Success status.
def success?
@error.nil?
errors.empty?
end

# Check if the Context failed.
Expand All @@ -73,9 +70,37 @@ def failure?
def inspect
keys = methods - Object.methods - Pipes::Context.instance_methods
fields = keys.map { |key| "#{key}=#{public_send(key).inspect}" }
fields << "@error=#{@error.inspect}"
fields << "@errors=#{@errors.inspect}"
object_id_hex = '%x' % (object_id << 1)
"#<Pipes::Context:0x00#{object_id_hex} #{fields.join(', ')}>"
end

# Return errors from ErrorCollector object.
#
# @return [Hash]
def errors
error_collector.errors
end

# This method is added to maintain backwards compatibility - previous
# versions implemented a single @error instance variable of String for error
# storage.
#
# @return [String]
def error
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should add deprecated warning here?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will not do that, since this PR will be released in new non-backward-compatible version.

errors[:base]&.first
end

# Add errors to ErrorCollector object.
# It doesn't fail the pipe as opposed to `halt` and `terminate` methods.
#
# @param collectable_errors [Hash]
def add_errors(collectable_errors)
error_collector.add(collectable_errors)
end

private

attr_reader :error_collector
end # class Context
end # module Pipes
20 changes: 20 additions & 0 deletions lib/codequest_pipes/context/error_collector.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Pipes
class Context
# ErrorCollector is Context's companion object for storing non-critical
# errors.
class ErrorCollector
attr_reader :errors

def initialize
@errors = {}
end

def add(errors_hash)
errors_hash.map do |key, errors|
@errors[key] ||= []
@errors[key] = @errors[key] | Array(errors)
end
end
end # class ErrorColletor
end # class Context
end # module Pipes
2 changes: 1 addition & 1 deletion lib/codequest_pipes/pipe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def self.|(other)
end

def self.call(ctx)
return ctx if ctx.error
return ctx if ctx.errors.any?
_validate_ctx(_required_context_elements, ctx)
new(ctx).call
_validate_ctx(_provided_context_elements, ctx)
Expand Down
25 changes: 23 additions & 2 deletions spec/context_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,34 @@
it 'lists all fields' do
subject.add(bacon: 'yum', raisins: 'bleh')
expect(subject.inspect)
.to match(/bacon=\"yum\", raisins=\"bleh\", @error=nil/)
.to match(/bacon=\"yum\", raisins=\"bleh\", @errors=nil/)
end

it 'lists nested contexts' do
subject.add(nested: Pipes::Context.new(foo: 'bar'))
expect(subject.inspect)
.to match(/nested=#<Pipes::Context:0x\w+ foo="bar", @error=nil>,/)
.to match(/nested=#<Pipes::Context:0x\w+ foo="bar", @errors=nil>,/)
end
end # describe '#inspect'

describe '#add_errors' do
it 'adds error to error_collector' do
subject.add_errors(base: 'Error message')
subject.add_errors(
base: ['Another error message'],
user: 'User error message'
)
expect(subject.errors).to eq(
base: ['Error message', 'Another error message'],
user: ['User error message']
)
end
end # describe '#add_errors'

describe '#halt' do
it 'adds error to error collector :base' do
subject.halt('Some error')
expect(subject.error).to eq('Some error')
end
end # describe '#halt'
end # describe Pipes::Context
2 changes: 1 addition & 1 deletion spec/matcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
shared_examples_for 'fails_with_message' do |message|
it 'fails' do
expected_message =
message || /expected #<Pipes::Context:.+ @error=nil> to match/
message || /expected #<Pipes::Context:.+ @errors=nil> to match/
expect { expect(ctx).to match(pipe_context(expected)) }
.to fail_with(expected_message)
end
Expand Down