outbacker 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,95 @@
1
+ #
2
+ # Plain-old Ruby object that contains our business logic.
3
+ #
4
+ # Can be a domain object, a service object, a use-case object,
5
+ # a DCI context, or whatever.
6
+ #
7
+ class AppointmentCalendar
8
+
9
+ #
10
+ # Include the Outbacker module. See config/initializers/outbacker.rb
11
+ # for the restrictions on where you can include this.
12
+ #
13
+ include Outbacker
14
+
15
+ #
16
+ # An "outbacked" domain method, i.e., one that can
17
+ # process outcome callbacks passed into it—here via
18
+ # the &outcome_handlers parameter:
19
+ #
20
+ def book_appointment(params, &outcome_handlers)
21
+
22
+ #
23
+ # Immediately pass the outcome_handlers block to the
24
+ # with method (provided by Outbacker), which in turn
25
+ # takes a block that must wrap the body of your
26
+ # business-logic method:
27
+ #
28
+ with(outcome_handlers) do |outcomes|
29
+ if user_lacks_sufficient_credits?
30
+ #
31
+ # Trigger the insufficient_credits outcome and run the
32
+ # corresponding callback block (provided via the
33
+ # &outcome_handlers block):
34
+ #
35
+ outcomes.handle :insufficient_credits
36
+ return
37
+ end
38
+
39
+ appointment = Appointment.new(params)
40
+ if appointment.save
41
+ ledger.deduct_credits_for appointment
42
+
43
+ notify_user_about appointment
44
+ notify_office_about appointment
45
+
46
+ #
47
+ # Trigger the successful_booking outcome and run the
48
+ # corresponding callback block (provided via the
49
+ # &outcome_handlers block), and pass that block
50
+ # the newly-booked appointment:
51
+ #
52
+ outcomes.handle :successful_booking, appointment
53
+ else
54
+ #
55
+ # Trigger the failed_validation outcome and run the
56
+ # corresponding callback block (provided via the
57
+ # &outcome_handlers block), and pass that block
58
+ # the appointment that failed validation:
59
+ #
60
+ outcomes.handle :failed_validation, appointment
61
+ end
62
+ end
63
+ end
64
+
65
+ #
66
+ # Any other public business logic methods.
67
+ # ...
68
+ #
69
+
70
+
71
+ private
72
+
73
+ def user_lacks_sufficient_credits?
74
+ # Check current user's credit balance is >= cost of appointment.
75
+ end
76
+
77
+ def ledger
78
+ # Return Ledger object that manages credit balances and transactions.
79
+ end
80
+
81
+ def notify_user_about(appointment)
82
+ # Enqueue background jobs to send emails, SMS, phone push notifications, etc.
83
+ # This lets us avoids ActiveRecord callback spaghetti.
84
+ end
85
+
86
+ def notify_office_about(appointment)
87
+ # Post office dashboard notification and activity feed entry, enqueue
88
+ # background jobs to send emails, SMS, phone push notifications, etc.
89
+ # This also lets us avoids ActiveRecord callback spaghetti.
90
+ end
91
+
92
+ #
93
+ # Any other private internal helper methods.
94
+ #
95
+ end
@@ -0,0 +1,27 @@
1
+
2
+ #
3
+ # Specify where the Outbacker module cannot be included.
4
+ # If you try to include Outbacker within subclasses of
5
+ # ActiveRecord::Base, ActionController::Base, or
6
+ # MyBlacklistedClass, Outbacker will raise an exception.
7
+ #
8
+ # By default, ActiveRecord::Base and ActionController::Base
9
+ # are blacklisted. This is how Outbacker encourages skinny
10
+ # models, and discourages fat, obese models.
11
+ #
12
+ Outbacker.configure do |c|
13
+ c.blacklist = [ActiveRecord::Base, ActionController::Base, MyBlacklistedClass]
14
+ end
15
+
16
+ #
17
+ # Specify where the Outbacker module can be included.
18
+ # If you try to include Outbacker within subclasses of any
19
+ # classes other than UseCase, ServiceObject, or DomainObject,
20
+ # Outbacker will raise an exception.
21
+ #
22
+ # The default is an empty whitelist, but specifying a whitelist
23
+ # is recommended way to configure this policy.
24
+ #
25
+ Outbacker.configure do |c|
26
+ c.whitelist = [UseCase, ServiceObject, DomainObject]
27
+ end
@@ -0,0 +1,33 @@
1
+
2
+ #
3
+ # You'll probably want to move the following to your test_helper.rb
4
+ #
5
+ require 'outbacker'
6
+ require 'test_support/outbacker_stub'
7
+
8
+
9
+ class AppointmentsControllerTest < ActionController::TestCase
10
+
11
+
12
+ test "user is redirected to the credits purchase page when they lack sufficient credits" do
13
+ #
14
+ # Stub the AppointmntsController#book_appointment method, specifying that
15
+ # it will have an outcome of :insufficient_credits.
16
+ #
17
+ calendar_stub = Outbacker::OutbackerStub.new
18
+ calendar_stub.stub('book_appointment', :insufficient_credits, stubbed_appointment)
19
+
20
+ # This is a method we added to our controller to inject dependencies:
21
+ @controller.inject_calendar(calendar_stub)
22
+
23
+ post :create, appointment: {
24
+ starts_at: '201506051600-600',
25
+ ends_at: '201506051600-600',
26
+ etc: 'and so on'
27
+ }
28
+
29
+ assert_redirected_to new_credits_url
30
+ end
31
+
32
+
33
+ end
@@ -0,0 +1,130 @@
1
+ require "outbacker/version"
2
+ require "configurations"
3
+
4
+ module Outbacker
5
+
6
+ include Configurations
7
+
8
+ DEFAULT_BLACKLIST = [ActiveRecord::Base, ActionController::Base]
9
+ DEFAULT_WHITELIST = []
10
+
11
+ configuration_defaults do |c|
12
+ c.blacklist = DEFAULT_BLACKLIST
13
+ c.whitelist = DEFAULT_WHITELIST
14
+ end
15
+
16
+
17
+ #
18
+ # DSL-ish factory method to create an instance of OutcomeHandlerSet
19
+ # given a block of outcome handlers.
20
+ #
21
+ # To be used within your business-logic methods in combination with
22
+ # the OutcomeHandlerSet#handle method.
23
+ #
24
+ def with(outcome_handlers)
25
+ outcome_handler_set = OutcomeHandlerSet.new(outcome_handlers)
26
+ yield outcome_handler_set
27
+
28
+ if outcome_handlers.nil?
29
+ return outcome_handler_set.triggered_outcome, *outcome_handler_set.args
30
+ else
31
+ raise "No outcome selected" unless outcome_handler_set.outcome_handled?
32
+ end
33
+ end
34
+
35
+ #
36
+ # Class to encapsulate the processing of a block of outcome handlers.
37
+ #
38
+ OutcomeHandlerSet = Struct.new(:outcome_handlers,
39
+ :triggered_outcome,
40
+ :args,
41
+ :handled_outcome) do
42
+
43
+ #
44
+ # Process the outcome specified by the given outcome_key,
45
+ # using the outcome handlers set on this OutcomeHandlerSet
46
+ # instance. Any additiona arbitrary arguments can be passed
47
+ # through to the corresponding outcome handler callback.
48
+ #
49
+ def handle(outcome_key, *args)
50
+ self.triggered_outcome = outcome_key
51
+ self.args = args
52
+
53
+ if outcome_handlers
54
+ outcome_handlers.call(self)
55
+ raise "No outcome handler for outcome #{outcome_key}" unless outcome_handled?
56
+ end
57
+ end
58
+
59
+ #
60
+ # Internal method to indicate that the outcome has been
61
+ # handled by some han dler.
62
+ #
63
+ def outcome_handled?
64
+ !!self.handled_outcome
65
+ end
66
+
67
+ #
68
+ # Specify an outcome handler callback block for the specified
69
+ # outcome key.
70
+ #
71
+ def of(outcome_key, &outcome_block)
72
+ execute_outcome_block(outcome_key, &outcome_block)
73
+ end
74
+
75
+ #
76
+ # Provides an alternate way to specify a callback block using
77
+ # method names.
78
+ #
79
+ def method_missing(method_name, *args, &outcome_block)
80
+ super unless /^outcome_of_(?<suffix>.*)/ =~ method_name.to_s
81
+ outcome_key = suffix.to_sym
82
+
83
+ execute_outcome_block(outcome_key, &outcome_block)
84
+ end
85
+
86
+
87
+ private
88
+
89
+ #
90
+ # Internal helper method to execute the given outcome block
91
+ # if it matches the triggered outcome.
92
+ #
93
+ def execute_outcome_block(outcome_key, &outcome_block)
94
+ if !outcome_block
95
+ raise "No block provided for outcome #{outcome_key}"
96
+ end
97
+
98
+ if outcome_key == self.triggered_outcome
99
+ raise "Outcome #{outcome_key} already handled" if outcome_handled?
100
+ self.handled_outcome = outcome_key
101
+ outcome_block.call(*self.args)
102
+ end
103
+ end
104
+
105
+ end
106
+
107
+ #
108
+ # Restrict where Outbacker can be included.
109
+ #
110
+ def self.included(target_module)
111
+ apply_whitelist(target_module) if Outbacker.configuration.whitelist.any?
112
+ apply_blacklist(target_module) if Outbacker.configuration.blacklist.any?
113
+ end
114
+
115
+ def self.apply_whitelist(target_module)
116
+ Outbacker.configuration.whitelist.each do |whitelisted_classs|
117
+ return if target_module.ancestors.include?(whitelisted_classs)
118
+ end
119
+ fail "Can only include #{self.name} within a subclass of: #{Outbacker.configuration.whitelist.join(', ')}"
120
+ end
121
+
122
+ def self.apply_blacklist(target_module)
123
+ Outbacker.configuration.blacklist.each do |blacklisted_class|
124
+ if target_module.ancestors.include?(blacklisted_class)
125
+ fail "Cannot include #{self.name} within an #{blacklisted_class} class, a plain-old Ruby object is preferred."
126
+ end
127
+ end
128
+ end
129
+
130
+ end
@@ -0,0 +1,3 @@
1
+ module Outbacker
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,42 @@
1
+ #
2
+ # Provides a simple means to stub "Outbacked" methods. Specifically,
3
+ # it lets you specify what outcome callback you want invoked on
4
+ # your stubbed method.
5
+ #
6
+ # Usage:
7
+ # stubbed_object = OutbackerStub.new
8
+ # stubbed_object.stub('stubbed_method_name',
9
+ # :desired_outcome,
10
+ # block_arg1, block_arg2, ...)
11
+ #
12
+ # Alternatively, combine instantiation and stubbing:
13
+ # stubbed_object = OutbackerStub.new('stubbed_method_name',
14
+ # :desired_outcome,
15
+ # block_arg1, block_arg2, ...)
16
+ #
17
+ # Note that this only provides stubbing functionality, no mocking
18
+ # functionality (i.e., ability to verify that a method was invoked on
19
+ # a test double) is provided. This should be sufficient for your test
20
+ # needs, as you can/should write separate tests to verify that
21
+ # the expected methods were invoked on your double.
22
+ #
23
+ module Outbacker
24
+ class OutbackerStub
25
+ include Outbacker
26
+
27
+ def initialize(method_name=nil, outcome_key=nil, *block_args)
28
+ if method_name && outcome_key
29
+ stub(method_name, outcome_key, *block_args)
30
+ end
31
+ end
32
+
33
+ def stub(method_name, outcome_key, *block_args)
34
+ define_singleton_method(method_name, ->(*args, &outcome_handlers) {
35
+ with(outcome_handlers) do |outcomes|
36
+ outcomes.handle outcome_key, *block_args
37
+ end
38
+ })
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'outbacker/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "outbacker"
8
+ spec.version = Outbacker::VERSION
9
+ spec.authors = ["Anthony Garcia"]
10
+ spec.email = ["polypressure@outlook.com"]
11
+ spec.summary = "Drive complexity out of your Rails controllers once and for all, while keeping your models fit and trim."
12
+ spec.description = <<-DESC
13
+ A micro library to keep conditional logic out of your Rails
14
+ controllers and help you write more intention-revealing
15
+ code with both skinny controllers and skinny models.
16
+ DESC
17
+ spec.homepage = "https://github.com/polypressure/outbacker"
18
+ spec.license = "MIT"
19
+
20
+ spec.files = `git ls-files -z`.split("\x0")
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.7"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "minitest", "~> 5.5"
28
+ spec.add_development_dependency "m", "~> 1.3.1"
29
+ spec.add_development_dependency "simplecov"
30
+ spec.add_development_dependency 'configurations', '~> 2.2.0'
31
+ spec.add_development_dependency "codeclimate-test-reporter"
32
+ end
@@ -0,0 +1,97 @@
1
+ require 'test_helper'
2
+
3
+ class OutbackerStubTest < Minitest::Test
4
+ extend DefineTestNamesWithStrings
5
+
6
+ test "#stub defines the specified method on the stub instance" do
7
+ outbacker_stub = Outbacker::OutbackerStub.new
8
+
9
+ outbacker_stub.stub('register_user', :successful_registration, Object.new)
10
+
11
+ assert_respond_to outbacker_stub, 'register_user'
12
+ end
13
+
14
+ test "#stub invokes the outcome callback for the specified outcome key" do
15
+ outbacker_stub = Outbacker::OutbackerStub.new
16
+ correct_block_executed = false
17
+
18
+ outbacker_stub.stub('register_user', :successful_registration, Object.new)
19
+
20
+ outbacker_stub.register_user do |on_outcome|
21
+ on_outcome.of(:successful_registration) do |user|
22
+ correct_block_executed = true
23
+ end
24
+
25
+ on_outcome.of(:failed_validation) do |user|
26
+ correct_block_executed = false
27
+ end
28
+
29
+ on_outcome.of(:some_other_outcome) do |user|
30
+ correct_block_executed = false
31
+ end
32
+ end
33
+
34
+ assert correct_block_executed, "Outcome block not executed by stub."
35
+ end
36
+
37
+ test "#stub passes the specified block arguments to the outcome block" do
38
+ outbacker_stub = Outbacker::OutbackerStub.new
39
+ block_arg_1 = Object.new
40
+ block_arg_2 = Object.new
41
+ block_args_passed = []
42
+
43
+ outbacker_stub.stub('register_user', :successful_registration, block_arg_1, block_arg_2)
44
+
45
+ outbacker_stub.register_user do |on_outcome|
46
+ on_outcome.of(:successful_registration) do |arg1, arg2|
47
+ block_args_passed = [arg1, arg2]
48
+ end
49
+ end
50
+
51
+ assert_equal [block_arg_1, block_arg_2], block_args_passed
52
+ end
53
+
54
+
55
+ test "#new defines the specified method on the stub instance" do
56
+ outbacker_stub = Outbacker::OutbackerStub.new('register_user', :successful_registration, Object.new)
57
+
58
+ assert_respond_to outbacker_stub, 'register_user'
59
+ end
60
+
61
+ test "#new invokes the outcome callback for the specified outcome key" do
62
+ outbacker_stub = Outbacker::OutbackerStub.new('register_user', :successful_registration, Object.new)
63
+ correct_block_executed = false
64
+
65
+ outbacker_stub.register_user do |on_outcome|
66
+ on_outcome.of(:successful_registration) do |user|
67
+ correct_block_executed = true
68
+ end
69
+
70
+ on_outcome.of(:failed_validation) do |user|
71
+ correct_block_executed = false
72
+ end
73
+
74
+ on_outcome.of(:some_other_outcome) do |user|
75
+ correct_block_executed = false
76
+ end
77
+ end
78
+
79
+ assert correct_block_executed, "Outcome block not executed by stub."
80
+ end
81
+
82
+ test "#new passes the specified block arguments to the outcome block" do
83
+ block_arg_1 = Object.new
84
+ block_arg_2 = Object.new
85
+ outbacker_stub = Outbacker::OutbackerStub.new('register_user', :successful_registration, block_arg_1, block_arg_2)
86
+ block_args_passed = []
87
+
88
+ outbacker_stub.register_user do |on_outcome|
89
+ on_outcome.of(:successful_registration) do |arg1, arg2|
90
+ block_args_passed = [arg1, arg2]
91
+ end
92
+ end
93
+
94
+ assert_equal [block_arg_1, block_arg_2], block_args_passed
95
+ end
96
+
97
+ end