pay_dirt 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 80daedc2e23621dd1692c5092c9cce375baf07a3
4
- data.tar.gz: 6db425bb6a5785adb71396cfa42ef26fdc29ecf0
3
+ metadata.gz: deb62d56966b35151bb79ca90be9ef8782668bb7
4
+ data.tar.gz: daec7ce9dc30b8c260de4e0eabac949c92e24401
5
5
  SHA512:
6
- metadata.gz: 6d5956acf790c96f0086e66b0312cbfe4037e421ff3f902a23386f937fe265f458738c299c8d0fd183efd942e860a2cc305cbf25bbf0f8d749806f95c05b6277
7
- data.tar.gz: 6cb08a75659b00259a7b273d6c0107c9f30a669817710fb97c2814984788cacb9065e4c4b000fa70622039eea600d7d6aaf20334197254123b687d7e8bda1183
6
+ metadata.gz: 55677cf4e2a832677f672c8b9e332a2f40ca3df46c1c517bbc0cef4365f5c90b4726da53b4cd4f48a556f3e83596860d98c08f9db4ca377ffbef765c8292f43a
7
+ data.tar.gz: b422b2fbf53018bd23d471cfebb351645d845459fe1298d225ea4ee643ed6f184c1c7e71dfbfbc92b5e1cd4dae57bc8bf058a3ab0cf90c7400e80e848147803c
data/.travis.yml CHANGED
@@ -8,8 +8,7 @@ rvm:
8
8
  - 2.0.0
9
9
  - 1.9.3
10
10
  - 1.9.2
11
- - jruby-19mode
11
+ - jruby
12
12
  - ruby-head
13
- - jruby-head
14
13
 
15
14
  script: bundle exec rake test
data/README.md CHANGED
@@ -1,9 +1,33 @@
1
1
  ## pay_dirt [![Gem Version](https://badge.fury.io/rb/pay_dirt.png)](http://badge.fury.io/rb/pay_dirt) [![Build Status](https://travis-ci.org/rthbound/pay_dirt.png?branch=master)](https://travis-ci.org/rthbound/pay_dirt) [![Coverage Status](https://coveralls.io/repos/rthbound/pay_dirt/badge.png?branch=master)](https://coveralls.io/r/rthbound/pay_dirt?branch=master) [![Code Climate](https://codeclimate.com/github/rthbound/pay_dirt.png)](https://codeclimate.com/github/rthbound/pay_dirt)
2
2
 
3
- #### A Ruby gem based on the "use case" pattern set forth in [opencurriculum-flashcards](https://github.com/isotope11/opencurriculum-flashcards)
4
-
5
3
  Provides the basic building blocks of a pattern capable of reducing a towering codebase to modular rubble (or more Ruby gems)
6
4
 
5
+ ### What is PayDirt
6
+
7
+ PayDirt gets its name from an 18th century gold mining idiom. One was said to have "struck pay dirt" when his pick axe revealed a vein of ore.
8
+ I hit pay dirt when I discovered this pattern. It provides me the freedom to build quickly with the confidence of knowing that testing will be a breeze.
9
+
10
+ ### What is the use case?
11
+
12
+ Its use case is gem making. It's for getting rid of callbacks and for shipping business logic off to the more suitable (and more portable) location.
13
+ It's for packaging use cases up in a modular fashion, where each unit expects to be provided certain dependencies and can be called to provide an expected result.
14
+ It makes sure you're using dependency injection so you can painlessly mock all your dependencies.
15
+
16
+ The basic idea:
17
+
18
+ 1. Initialize an object by supplying ALL dependencies as a single options hash.
19
+ 2. The object should have ONE public method, `#call`, which will return an expected result object.
20
+
21
+ What pay_dirt does to help:
22
+
23
+ 1. It will set instance variables from the hash of dependencies, using top level key-value pairs.
24
+ 2. It will not initialize (it WILL error) without all required dependencies.
25
+ 3. It allows you to set default values for any dependencies (just merge the `options` argument into your defaults hash before calling `#load_options`)
26
+
27
+ PayDirt also provides a `PayDirt::Result` object for your service objects to return (it will respond to `#successful?` and `#data`, see some examples). This is entirely optional, as this object can return whatever you like.
28
+
29
+ ### Getting on to it
30
+
7
31
  There are two ways to employ the pattern:
8
32
 
9
33
  1. use a class that inherits from [PayDirt::Base](https://github.com/rthbound/pay_dirt/blob/master/test/unit/pay_dirt/base_test.rb#L6-L24)
@@ -44,16 +68,18 @@ create a service object
44
68
  example
45
69
  -------
46
70
  ```
47
- $ thor pay_dirt:service_object:new digit_check -d fingers toes -D fingers:10 toes:10
48
- create lib/service_objects/digit_check.rb
71
+ $ thor pay_dirt:service_object:new quick/digit_check -d fingers toes nose -D fingers:10 toes:10
72
+ create lib/quick/digit_check.rb
73
+ create test/unit/quick/digit_check_test.rb
74
+ append test/minitest_helper.rb
49
75
  ```
50
76
 
51
- Running the above generator will create the following file
77
+ Running the above generator will create the following object
52
78
 
53
79
  ```ruby
54
80
  require 'pay_dirt'
55
81
 
56
- module ServiceObjects
82
+ module Quick
57
83
  class DigitCheck < PayDirt::Base
58
84
  def initialize(options = {})
59
85
  options = {
@@ -61,23 +87,58 @@ module ServiceObjects
61
87
  toes: 10,
62
88
  }.merge(options)
63
89
 
64
- load_options(:fingers, :toes, options)
90
+ load_options(:fingers, :toes, :nose, options)
65
91
  end
66
92
 
67
- def execute!
68
- result(true)
93
+ def call
94
+ return result(true)
95
+ end
96
+ end
97
+ end
98
+ ```
99
+
100
+ and the following unit test
101
+ ```ruby
102
+ require 'minitest_helper'
103
+
104
+ describe Quick::DigitCheck do
105
+ before do
106
+ @subject = Quick::DigitCheck
107
+ @params = {
108
+ fingers: MiniTest::Mock.new,
109
+ toes: MiniTest::Mock.new,
110
+ nose: MiniTest::Mock.new,
111
+ }
112
+ end
113
+
114
+ describe "as a class" do
115
+ it "initializes properly" do
116
+ @subject.new(@params).must_respond_to :call
117
+ end
118
+
119
+ it "errors when initialized without required dependencies" do
120
+ -> { @subject.new(@params.reject { |k| k.to_s == 'nose' }) }.must_raise RuntimeError
121
+ end
122
+ end
123
+
124
+ describe "as an instance" do
125
+ it "executes successfully" do
126
+ result = @subject.new(@params).call
127
+ result.successful?.must_equal true
128
+ result.must_be_kind_of PayDirt::Result
69
129
  end
70
130
  end
71
131
  end
72
132
  ```
73
133
 
74
134
  ### Usage:
135
+ The class generated can be used in the following manner:
75
136
  ```ruby
76
- require "service_objects/digit_check" #=> true
77
- ServiceObjects::DigitCheck.new.execute!
137
+ require "quick/digit_check" #=> true
138
+ Quick::DigitCheck.new(nose: true).call
78
139
  #=> #<PayDirt::Result:0xa0be85c @data=nil, @success=true>
79
140
  ```
80
- As you can see, we can now call `ServiceObjects::DigitCheck.new(fingers: 10, toes: 10).execute!`
141
+ As you can see, we can now call `Quick::DigitCheck.new(nose: true).call`
81
142
  and expect a successful return object. Where you take it from there is up to you.
82
143
 
83
144
  more examples
@@ -86,3 +147,8 @@ more examples
86
147
  2. [protected_record](https://github.com/rthbound/protected_record)
87
148
  3. [konamio](https://github.com/rthbound/konamio)
88
149
  4. [eenie_meenie](https://github.com/rthbound/eenie_meenie)
150
+ 5. [foaas](https://github.com/rthbound/foaas)
151
+ 6. [konami-fo](https://github.com/rthbound/konami-fo)
152
+ 7. [rungs](https://github.com/rthbound/rungs)
153
+
154
+ #### PayDirt is a Ruby gem based on the "use case" pattern set forth in [opencurriculum-flashcards](https://github.com/isotope11/opencurriculum-flashcards)
@@ -1,3 +1,3 @@
1
1
  module PayDirt
2
- VERSION = "1.0.7"
2
+ VERSION = "1.0.8"
3
3
  end
data/pay_dirt.thor CHANGED
@@ -2,11 +2,14 @@ module PayDirt
2
2
  class ServiceObject < Thor
3
3
  include Thor::Actions
4
4
 
5
- desc "new FILE", "create a service object"
5
+ desc "new FILE", "create a fully tested object (optionally, requires dependencies)"
6
6
  method_option :dependencies,
7
7
  type: :array,
8
8
  aliases: "-d",
9
- desc: "specify required dependencies"
9
+ desc: "specify required dependencies"
10
+ method_option :test_framework,
11
+ type: :string,
12
+ desc: "choose a testing framework"
10
13
  method_option :defaults,
11
14
  type: :hash,
12
15
  aliases: "-D",
@@ -26,73 +29,180 @@ module PayDirt
26
29
  class_names = file.split("/").map { |str| str.split("_").map{ |s| (s[0].upcase + s[1..-1]) }.join("") }
27
30
  @dependencies = options[:dependencies] || []
28
31
 
29
- # Favor 2 spaces
30
- @append = Proc.new { |depth, string| (@rets ||= "") << (" " * depth) + string }
31
-
32
- create_file "lib/service_objects/#{file}.rb" do
33
- open_class(class_names)
34
- write_initialize_method
35
- write_execute_method
36
-
37
- close_class(class_names)
38
-
39
- @rets
40
- end
32
+ create_object(file, class_names)
33
+ create_tests(file, class_names)
41
34
  end
42
35
 
43
36
  private
44
- desc "close_class CLASS_NAMES", "hide", hide: true
45
37
  def close_class(class_names)
46
- @append.call(@class_depth, "end\n") # Closes innermost class definition
38
+ append(@class_depth, "end\n") # Closes innermost class definition
47
39
 
48
- class_names[0..-1].each_with_index { |m,i| @append.call(@class_depth - (i + 1), "end\n") }
40
+ @class_depth.times { |i| append(@class_depth - (i + 1), "end\n") }
49
41
  end
50
42
 
51
- desc "open_class CLASS_NAMES", "hide", hide: true
52
43
  def open_class(class_names)
53
44
  @class_name = class_names.last
54
- @class_depth = class_names.length
55
- @inner_depth = class_names.length + 1
45
+ @class_depth = class_names.length - 1
46
+ @inner_depth = class_names.length
56
47
 
57
- @append.call(0, "require 'pay_dirt'\n\nmodule ServiceObjects\n")
58
- class_names[0..-2].each_with_index { |mod,i| @append.call(i.next, "module #{mod}\n") }
48
+ append(0, "require 'pay_dirt'\n\n")
49
+ class_names[0..-2].each_with_index { |mod,i| append(i, "module #{mod}\n") }
59
50
 
60
51
  if options[:include]
61
- @append.call(@class_depth, "class #{@class_name}\n")
62
- @append.call(@class_depth.next, "include PayDirt::UseCase\n")
52
+ append(@class_depth, "class #{@class_name}\n")
53
+ append(@inner_depth, "include PayDirt::UseCase\n")
63
54
  elsif options[:inherit]
64
- @append.call(@class_depth, "class #{@class_name} < PayDirt::Base\n")
55
+ append(@class_depth, "class #{@class_name} < PayDirt::Base\n")
65
56
  end
66
57
  end
67
58
 
68
59
  def write_execute_method
69
- # The execute! method
70
- @append.call(@inner_depth, "def execute!\n")
71
- @append.call(@inner_depth.next, "return result(true)\n")
72
- @append.call(@inner_depth, "end\n")
60
+ # The call method
61
+ append(@inner_depth, "def call\n")
62
+ append(@inner_depth.next, "return result(true)\n")
63
+ append(@inner_depth, "end\n")
73
64
  end
74
65
 
75
66
  def write_initialize_method
76
- @append.call(@inner_depth, "def initialize(options = {})\n")
67
+ append(@inner_depth, "def initialize(options = {})\n")
77
68
 
78
69
  set_defaults if options[:defaults]
79
70
  call_load_options
80
71
 
81
- @append.call(@inner_depth, "end\n\n")
72
+ append(@inner_depth, "end\n\n")
82
73
  end
83
74
 
84
75
  def call_load_options
85
- @append.call(@inner_depth.next, "load_options(")
86
- @dependencies.each { |dep| @append.call(0, ":#{dep}, ") }
87
- @append.call(0, "options)\n")
76
+ append(@inner_depth.next, "# sets instance variables from key value pairs,\n")
77
+ append(@inner_depth.next, "# will fail if any keys given before options aren't in options\n")
78
+ append(@inner_depth.next, "load_options(")
79
+ @dependencies.each { |dep| append(0, ":#{dep}, ") }
80
+ append(0, "options)\n")
88
81
  end
89
82
 
90
83
  def set_defaults
91
- @append.call(@inner_depth.next, "options = {\n")
84
+ append(@inner_depth.next, "options = {\n")
85
+
86
+ options[:defaults].each { |k,v| append(@inner_depth + 2, "#{k}: #{v}" + ",\n") }
87
+
88
+ append(@inner_depth.next, "}.merge(options)\n\n")
89
+ end
90
+
91
+ # TESTS!
92
+ def open_test_class(class_names, file)
93
+ case options[:test_framework]
94
+ when "minitest", "mini_test"
95
+ append(0, "require 'minitest_helper'\n\n")
96
+ append_to_file "test/minitest_helper.rb" do
97
+ "require \"#{file}\"\n"
98
+ end
99
+ else
100
+ append(0, "require 'test_helper'\n\n")
101
+ append_to_file "test/test_helper.rb" do
102
+ "require \"#{file}\"\n"
103
+ end
104
+ end
105
+ append(0, "describe #{ class_string(class_names) } do\n")
106
+ end
107
+
108
+ def mock_test_dependencies
109
+ append(2, "@params = {\n")
110
+
111
+ @dependencies.each do |dep|
112
+ append(3, "#{dep}: MiniTest::Mock.new,\n")
113
+ end
114
+
115
+ append(2, "}\n")
116
+ end
117
+
118
+ def add_before_hook(class_names)
119
+ append(1, "before do\n")
120
+ append(2, "@subject = #{ class_string(class_names) }\n")
121
+ mock_test_dependencies
122
+ append(1, "end\n")
123
+ end
124
+
125
+ def assert_this(assertion, asserts)
126
+ append(2, "it \"#{assertion}\" do\n")
127
+
128
+ asserts.each do |s|
129
+ append(3, "#{s}\n")
130
+ end
131
+
132
+ append(2, "end\n")
133
+ end
92
134
 
93
- options[:defaults].each { |k,v| @append.call(@inner_depth + 2, "#{k}: #{v}" + ",\n") }
135
+ def assert_error_without_dependencies
136
+ assert_this("errors when initialized without required dependencies", @dependencies.reject { |d|
137
+ options[:defaults] && options[:defaults].keys.include?(d)
138
+ }.map { |required_dep|
139
+ "-> { @subject.new(@params.reject { |k| k.to_s == '#{ required_dep }' }) }.must_raise RuntimeError"
140
+ })
141
+ end
142
+
143
+ def assert_wont_error_with_all_dependencies
144
+ assert_this("initializes properly", ["@subject.new(@params).must_respond_to :call"])
145
+ end
146
+
147
+ def assert_returns_a_successful_result
148
+ assert_this("executes successfully", [
149
+ "result = @subject.new(@params).call",
150
+ "result.successful?.must_equal true",
151
+ "result.must_be_kind_of PayDirt::Result"
152
+ ])
153
+ end
154
+
155
+ def context_class
156
+ append(1, "describe \"as a class\" do\n")
157
+ assert_wont_error_with_all_dependencies
158
+ append(0, "\n")
159
+ assert_error_without_dependencies
160
+ append(1, "end\n")
161
+ end
162
+
163
+ def context_instance
164
+ append(1, "describe \"as an instance\" do\n")
165
+ assert_returns_a_successful_result
166
+ append(1, "end\n")
167
+ end
168
+
169
+ def close_test_class
170
+ append(0, "end")
171
+ end
172
+
173
+ def create_object(file, class_names)
174
+ @rets = nil
175
+ create_file "lib/#{file}.rb" do
176
+ open_class(class_names)
177
+ write_initialize_method
178
+ write_execute_method
179
+
180
+ close_class(class_names)
181
+
182
+ @rets
183
+ end
184
+ end
185
+
186
+ def create_tests(file, class_names)
187
+ @rets = nil
188
+ create_file "test/unit/#{file}_test.rb" do
189
+ open_test_class(class_names, file)
190
+ add_before_hook(class_names)
191
+ append(0, "\n")
192
+ context_class
193
+ append(0, "\n")
194
+ context_instance
195
+ close_test_class
196
+ @rets
197
+ end
198
+ end
199
+
200
+ def append(depth, string)
201
+ (@rets ||= "") << (" " * depth) + string
202
+ end
94
203
 
95
- @append.call(@inner_depth.next, "}.merge(options)\n\n")
204
+ def class_string(names)
205
+ names.map(&:to_s).join("::")
96
206
  end
97
207
  end
98
208
  end
@@ -13,7 +13,7 @@ describe PayDirt::Base do
13
13
  load_options(:the_question, :the_secret, options)
14
14
  end
15
15
 
16
- def execute!
16
+ def call
17
17
  @the_secret == 42 and return result(true, @the_secret )
18
18
  return result(false)
19
19
  end
@@ -33,26 +33,26 @@ describe PayDirt::Base do
33
33
  end
34
34
 
35
35
  it "won't error if defaults were supplied for an omitted option" do
36
- @use_case.new(the_secret: :not_telling).must_respond_to :execute!
36
+ @use_case.new(the_secret: :not_telling).must_respond_to :call
37
37
  end
38
38
 
39
- it "can execute successfully" do
39
+ it "can be called successfully" do
40
40
  dependencies = {
41
41
  the_secret: 42
42
42
  }
43
43
 
44
- result = @use_case.new(dependencies).execute!
44
+ result = @use_case.new(dependencies).call
45
45
 
46
46
  result.successful?.must_equal true
47
47
  result.must_be_kind_of PayDirt::Result
48
48
  end
49
49
 
50
- it "can execute unsuccessfully" do
50
+ it "can be called unsuccessfully" do
51
51
  dependencies = {
52
52
  the_secret: :i_dunno
53
53
  }
54
54
 
55
- result = @use_case.new(dependencies).execute!
55
+ result = @use_case.new(dependencies).call
56
56
 
57
57
  result.successful?.must_equal false
58
58
  end
@@ -19,7 +19,7 @@ describe PayDirt::Result do
19
19
  load_options(:success, :data, options)
20
20
  end
21
21
 
22
- def execute!
22
+ def call
23
23
  result(@success, @data)
24
24
  end
25
25
  end
@@ -28,15 +28,15 @@ describe PayDirt::Result do
28
28
  end
29
29
 
30
30
  it "can succeed" do
31
- @pay_dirt.new(success: true, data: "yum").execute!.successful?.must_equal true
31
+ @pay_dirt.new(success: true, data: "yum").call.successful?.must_equal true
32
32
  end
33
33
 
34
34
  it "can be unsuccessful" do
35
- @pay_dirt.new(success: false, data: "gross").execute!.successful?.must_equal false
35
+ @pay_dirt.new(success: false, data: "gross").call.successful?.must_equal false
36
36
  end
37
37
 
38
38
  it "provides access to data" do
39
- @pay_dirt.new(success: true, data: "yum").execute!.data.must_equal "yum"
39
+ @pay_dirt.new(success: true, data: "yum").call.data.must_equal "yum"
40
40
  end
41
41
  end
42
42
  end
@@ -16,7 +16,7 @@ describe PayDirt::UseCase do
16
16
  load_options(:the_secret_to_life_the_universe_and_everything, options)
17
17
  end
18
18
 
19
- def execute!
19
+ def call
20
20
  if @the_secret_to_life_the_universe_and_everything == 42
21
21
  return PayDirt::Result.new(data: return_value, success: true)
22
22
  else
@@ -46,30 +46,30 @@ describe PayDirt::UseCase do
46
46
  end
47
47
 
48
48
  it "must not error when options with defaults are omitted" do
49
- @use_case.new({}).must_respond_to :execute!
49
+ @use_case.new({}).must_respond_to :call
50
50
  end
51
51
 
52
52
  it "loads options that are not required" do
53
- result = @use_case.new({ some_random_option: true }).execute!
53
+ result = @use_case.new({ some_random_option: true }).call
54
54
  assert result.data[:some_random_option]
55
55
  end
56
56
 
57
- it "can execute successfully" do
57
+ it "can be called successfully" do
58
58
  dependencies = {
59
59
  }
60
60
 
61
- result = @use_case.new(dependencies).execute!
61
+ result = @use_case.new(dependencies).call
62
62
 
63
63
  result.successful?.must_equal true
64
64
  result.must_be_kind_of PayDirt::Result
65
65
  end
66
66
 
67
- it "can execute unsuccessfully" do
67
+ it "can be called unsuccessfully" do
68
68
  dependencies = {
69
69
  the_secret_to_life_the_universe_and_everything: :i_dunno
70
70
  }
71
71
 
72
- result = @use_case.new(dependencies).execute!
72
+ result = @use_case.new(dependencies).call
73
73
 
74
74
  result.successful?.must_equal false
75
75
  end
@@ -86,7 +86,7 @@ describe PayDirt::UseCase do
86
86
  load_options(:required_option_with_default_value, :required_option, options)
87
87
  end
88
88
 
89
- def execute!
89
+ def call
90
90
  if !@required_option_with_default_value
91
91
  return PayDirt::Result.new(data: return_value, success: true)
92
92
  else
@@ -105,15 +105,15 @@ describe PayDirt::UseCase do
105
105
  end
106
106
 
107
107
  # Cheating by not injecting all dependencies
108
- result = SomeThing.new(required_option: true).execute! # Returns a PayDirt::Result
108
+ result = SomeThing.new(required_option: true).call # Returns a PayDirt::Result
109
109
  assert !result.successful? #=> false
110
110
  result.data[:optional_option].must_be_nil #=> nil
111
111
  # Playing nice and injecting all required dependencies
112
- result = SomeThing.new(required_option: true, required_option_with_default_value: false).execute!
112
+ result = SomeThing.new(required_option: true, required_option_with_default_value: false).call
113
113
  assert result.successful? #=> true
114
114
  result.data[:optional_option].must_be_nil #=> nil
115
115
  # Making use of an optional option
116
- result = SomeThing.new(required_option: true, optional_option: true).execute!
116
+ result = SomeThing.new(required_option: true, optional_option: true).call
117
117
  assert !result.successful? #=> false
118
118
  assert result.data[:optional_option] #=> true
119
119
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pay_dirt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.7
4
+ version: 1.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tad Hosford
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-06-30 00:00:00.000000000 Z
11
+ date: 2013-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest