hexx 6.0.3 → 7.0.0

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/README.rdoc +63 -48
  4. data/lib/hexx.rb +1 -0
  5. data/lib/hexx/coercible.rb +2 -2
  6. data/lib/hexx/configurable.rb +1 -1
  7. data/lib/hexx/{helpers → creators}/base.rb +11 -15
  8. data/lib/hexx/{helpers → creators}/coercion.rb +8 -8
  9. data/lib/hexx/{helpers → creators}/dependency.rb +8 -8
  10. data/lib/hexx/{helpers → creators}/module_dependency.rb +2 -2
  11. data/lib/hexx/{helpers → creators}/parameter.rb +1 -1
  12. data/lib/hexx/dependable.rb +1 -1
  13. data/lib/hexx/helpers/exceptions.rb +53 -0
  14. data/lib/hexx/helpers/messages.rb +28 -0
  15. data/lib/hexx/helpers/parameters.rb +47 -0
  16. data/lib/hexx/helpers/validations.rb +21 -0
  17. data/lib/hexx/message.rb +79 -0
  18. data/lib/hexx/null.rb +0 -4
  19. data/lib/hexx/service.rb +259 -208
  20. data/lib/hexx/service_invalid.rb +73 -0
  21. data/lib/hexx/version.rb +1 -1
  22. data/spec/hexx/helpers/exceptions_spec.rb +96 -0
  23. data/spec/hexx/helpers/messages_spec.rb +56 -0
  24. data/spec/hexx/helpers/parameters_spec.rb +96 -0
  25. data/spec/hexx/helpers/validations_spec.rb +32 -0
  26. data/spec/hexx/message_spec.rb +83 -0
  27. data/spec/hexx/service_invalid_spec.rb +46 -0
  28. data/spec/hexx/service_spec.rb +41 -242
  29. data/spec/spec_helper.rb +1 -1
  30. data/spec/{initializers → support/initializers}/focus.rb +0 -0
  31. data/spec/{initializers → support/initializers}/garbage_collection.rb +0 -0
  32. data/spec/{initializers → support/initializers}/i18n.rb +0 -0
  33. data/spec/{initializers → support/initializers}/random_order.rb +0 -0
  34. data/spec/{initializers → support/initializers}/rspec.rb +0 -0
  35. data/spec/support/matchers/methods.rb +11 -0
  36. metadata +37 -23
  37. data/lib/hexx/service/invalid.rb +0 -76
  38. data/lib/hexx/service/message.rb +0 -81
  39. data/spec/hexx/service/invalid_spec.rb +0 -52
  40. data/spec/hexx/service/message_spec.rb +0 -85
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 861d8373aad89fc2f9409572c87782223dd042e4
4
- data.tar.gz: 40061fbf81bdf2ff8f57679e328d71c944260a71
3
+ metadata.gz: 79228f50ba0633896a58f7e29909a69db9084327
4
+ data.tar.gz: 796d2e839958e2e93faf062bda2678d514d06c6e
5
5
  SHA512:
6
- metadata.gz: 509e8e294856e4f6509c0d68a188533f33646152b1567114d60943fe84e3ab98ec5d21d55aad33e8a3c38706fd875fef57ff0eb9e37605727e3f0d1d3f081896
7
- data.tar.gz: b3f38e5997f22130d83050d1a137110424a4dfee5d7544311f0ec75c9fb8524accb0566bc8d99fe2f780f06443b9f915bab6b845fd55c27888c550873c354015
6
+ metadata.gz: 2bf8701306d0e731b6ef3789bdbcb4e3e5540985e769694cadc218693eb7516946912467a9489b68758a889143e5962e489f5d67f13d576e5dabe02330916e36
7
+ data.tar.gz: e202c8efe9ad52e3cff511cfc9934f73e46057d1db467c4979b7afb895e2918a86c63fcfabd40dd7e801e6ff4e1f60c53acea6550bbda965304dae54d70afc2b
@@ -55,6 +55,9 @@ Style/SpecialGlobalVars:
55
55
  Style/StringLiterals:
56
56
  EnforcedStyle: double_quotes
57
57
 
58
+ Stype/SingleSpaceBeforeFirstArg:
59
+ Enabled: false
60
+
58
61
  Style/TrivialAccessors:
59
62
  Exclude:
60
63
  - 'spec/**/*'
@@ -13,7 +13,7 @@ The base library for domain models.
13
13
 
14
14
  Includes classes and modules as below:
15
15
  <tt>Hexx::Service</tt>:: The base class for service objects.
16
- <tt>Hexx::Service::Message</tt>:: The message provided by service objects.
16
+ <tt>Hexx::Message</tt>:: The message provided by service objects.
17
17
  <tt>Hexx::Null</tt>:: The Null object.
18
18
  <tt>Hexx::Coercible</tt>:: The module that makes model attributes coercible.
19
19
  <tt>Hexx::Configurable</tt>:: The module to convert a core domain module to the
@@ -125,7 +125,6 @@ The class implements a set of patterns:
125
125
  * The {Service object pattern}[http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/] used to decouple business logics from both the models and web delivery mechanism (such as +Rails+).
126
126
  * The {Observer pattern}[http://reefpoints.dockyard.com/2013/08/20/design-patterns-observer-pattern.html] to follow the {Tell, don't ask}[http://martinfowler.com/bliki/TellDontAsk.html] design princible.
127
127
  The pattern is implemented with the help of {wisper}[http://www.github.com/krisleech/wisper] gem by Kris Leech.
128
- * The {Setter-based dependency injection}[http://brandonhilkert.com/blog/a-ruby-refactor-exploring-dependency-injection-options/] to decouple the service from another services it uses.
129
128
 
130
129
  A typical service object is shown below:
131
130
 
@@ -133,11 +132,6 @@ A typical service object is shown below:
133
132
  require 'hexx'
134
133
  class AddItem < Hexx::Service
135
134
 
136
- # Injects the dependency from the service for getting an item.
137
- # Provides default implementation for the dependency, that could be
138
- # redefined later (in a test suite etc.).
139
- depends_on :get_item, default: GetItem
140
-
141
135
  # Whitelists parameters and defines corresponding attributes.
142
136
  # For example, the #name attribute is avalable.
143
137
  allow_params :name
@@ -162,8 +156,12 @@ A typical service object is shown below:
162
156
 
163
157
  attr_accessor :item
164
158
 
165
- # Errors to be raised by the #run! method call and captured in a #run.
166
- class Found < StandardError; end
159
+ # Declares specific exceptions to be raised by the #run! method
160
+ # and processed by the #run differently.
161
+ #
162
+ # All other exceptions will be re-raised as Hexx::Service::Invalid
163
+ # and processed by publishing the :error notification.
164
+ raises :Found
167
165
 
168
166
  # The sequence of the service steps. Any step can raise error to
169
167
  # be rescued in #run with publishing a corresponding notification.
@@ -176,7 +174,7 @@ A typical service object is shown below:
176
174
  # The method runs another service and listens to its notifications
177
175
  # via private callback methods available to that service only.
178
176
  # The callback names should start from given prefix (:on_item_).
179
- run_service get_item, :on_item, name: name
177
+ run_service GetItem, :on_item, name: name
180
178
  end
181
179
 
182
180
  # The callback to listen to :found notification of the 'get_item' service.
@@ -199,7 +197,7 @@ A typical service object is shown below:
199
197
 
200
198
  def add_item
201
199
  # The escape re-raises any error as the Hexx::Service::Invalid
202
- # with the array of Hexx::Service::Message messages.
200
+ # with the array of Hexx::Message messages.
203
201
  escape { @item = Item.create! name: name }
204
202
  end
205
203
  end
@@ -237,68 +235,85 @@ A typical usage of the service (in a Rails controller):
237
235
  The controller knows nothing about the action itself. It only needs to
238
236
  send the request to a corresponding service and sort out the notifications.
239
237
 
240
- === Hexx::Service::Message
238
+ === Hexx::Message
241
239
 
242
240
  The messages published by the service has two attributes: +type+ and +text+.
243
241
 
244
- message = Hexx::Service::Message.new type: :error, text: "some error message"
242
+ message = Hexx::Message.new type: :error, text: "some error message"
245
243
  message.type # => "error"
246
244
  message.text # => "some error message"
247
245
 
248
246
  Inside a service use the +add_message+ to add message to the +messages+ array:
249
247
 
250
248
  add_message "error", "text"
251
- messages # => [#<Hexx::Service::Message @type="error", @text="text" >]
249
+ messages # => [#<Hexx::Message @type="error", @text="text" >]
252
250
 
253
- === Hexx::Null
251
+ === Hexx::Dependable
254
252
 
255
- The class implements the {Null object}[http://robots.thoughtbot.com/rails-refactoring-example-introduce-null-object] pattern. The object:
253
+ The module provides the +depends_on+ class helper for {setter-based dependency injection}[http://brandonhilkert.com/blog/a-ruby-refactor-exploring-dependency-injection-options/]. It allows decoupling the service from another services it uses.
256
254
 
257
- * responds like +nil+ to <tt><=></tt>, +eq?+, +nil?+, +false?+, +true?+, +to_s+,
258
- +to_i+, +to_f+, +to_c+, +to_r+, +to_nil+
259
- * responds with +self+ to any other method call
255
+ Extend the service class and declare the dependencies with an optional default
256
+ implementation (see example above):
260
257
 
261
- Providing {this problem}[http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/], use double negation in logical expressions:
258
+ class AddItem < Hexx::Service
259
+ extend Hexx::Dependable
262
260
 
263
- # Though:
264
- Hexx::Null && true # => true
265
-
266
- # But:
267
- !!Hexx::Null && true # => false
261
+ depends_on :get_item, default: GetItem
268
262
 
269
- === Hexx::Dependable
263
+ # ...
270
264
 
271
- This module is a part of <tt>Hexx::Service</tt> that provides
272
- setter dependency declaration +depends_on+.
265
+ def find_item
266
+ run_service get_item, :on_item, name: name
267
+ end
268
+ end
273
269
 
274
- Extend the class and declare the dependency with optional default
275
- implementation:
270
+ Now the dependency can be injected afterwards:
276
271
 
277
- class MyClass
278
- extend Hexx::Dependable
272
+ # The default implementation
273
+ service = AddItem.new
274
+ service.get_item # => GetItem
279
275
 
280
- depends_on :another_class, default: AhotherClass
281
- depends_on :one_more_class
282
- end
276
+ # Change it to other implementation
277
+ service.get_item = FindItem
278
+ service.get_item # => FindItem
283
279
 
284
- Now the dependency can be injected afterwards:
280
+ # Reset it to default by assigning +nil+
281
+ service.get_item = nil
282
+ service.get_item # => GetItem
285
283
 
286
- object = MyClass
284
+ It is possible to test a service in isolation from its dependencies.
287
285
 
288
- # The default implementation
289
- object.another_class # => AnotherClass
286
+ # spec/services/my_service_spec.rb
287
+ describe AddService do
288
+
289
+ describe "#run" do
290
+
291
+ # Mock a service objects to publish expected notifications
292
+ let(:object) { Hexx::Service }
293
+ before { allow(object).to receive(:run) { publish :not_found } }
294
+
295
+ # Inject a class dependency
296
+ before { service.get_item = class_double "Hexx::Service", new: object }
297
+
298
+ # ...
299
+ end
300
+ end
301
+
302
+ === Hexx::Null
303
+
304
+ The class implements the {Null object}[http://robots.thoughtbot.com/rails-refactoring-example-introduce-null-object] pattern. The object:
290
305
 
291
- # Inject another implementation
292
- object.another_class = NewClass
293
- object.another_class # => NewClass
306
+ * responds like +nil+ to <tt><=></tt>, +eq?+, +nil?+, +false?+, +true?+, +to_s+,
307
+ +to_i+, +to_f+, +to_c+, +to_r+, +to_nil+
308
+ * responds with +self+ to any other method call
294
309
 
295
- # Reset it to default
296
- object.another_class = nil
297
- object.another_class # => AnotherClass
310
+ Providing {this problem}[http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/], use double negation in logical expressions:
298
311
 
299
- # Implementation is needed
300
- object.one_more_class
301
- # fails with a NotImplementedError
312
+ # Though:
313
+ Hexx::Null && true # => true
314
+
315
+ # But:
316
+ !!Hexx::Null && true # => false
302
317
 
303
318
  == License
304
319
 
@@ -5,6 +5,7 @@ require "wisper"
5
5
  # Loading gem code (the order is essential).
6
6
  root = File.expand_path "../hexx", __FILE__
7
7
  Dir[
8
+ "#{ root }/creators/*.rb",
8
9
  "#{ root }/helpers/*.rb",
9
10
  "#{ root }/models/**/*.rb",
10
11
  "#{ root }/service/**/*.rb",
@@ -35,9 +35,9 @@ module Hexx
35
35
  # @api hide
36
36
  # The method returns a coercion creator for PORO object only.
37
37
  # To be reloaded for ActiveRecord models
38
- # @return [Class] attribute coercion (Hexx::Helpers::Coercion by default)
38
+ # @return [Class] attribute coercion (Hexx::Creators::Coercion by default)
39
39
  def coercion
40
- Hexx::Helpers::Coercion
40
+ Hexx::Creators::Coercion
41
41
  end
42
42
  end
43
43
  end
@@ -94,7 +94,7 @@ module Hexx
94
94
  # for the dependency.
95
95
  def depends_on(*names, default: nil)
96
96
  names.flatten.each do |name|
97
- Helpers::ModuleDependency.add self, name, default
97
+ Creators::ModuleDependency.add self, name, default
98
98
  end
99
99
  end
100
100
  end
@@ -3,13 +3,9 @@
3
3
  module Hexx
4
4
 
5
5
  # @api hide
6
- # The module contains helper classes and modules.
7
- #
8
- # All the helpers injects some code into corresponding class or module.
9
- #
10
- # The content of the module is not a part of API, but its implementation
11
- # details.
12
- module Helpers
6
+ # The module contains classes that adds specific attributes
7
+ # to given classes or modules.
8
+ module Creators
13
9
 
14
10
  # @api hide
15
11
  # @abstract
@@ -25,8 +21,8 @@ module Hexx
25
21
  #
26
22
  # @param [Module] target The class or module to inject the attribute to.
27
23
  # @param [String, Symbol] name The name of the attribute.
28
- # @raise (see Hexx::Helpers::Base#validate)
29
- # @return [Hexx::Helpers::Base] the base class object.
24
+ # @raise (see Hexx::Creators::Base#validate)
25
+ # @return [Hexx::Creators::Base] the base class object.
30
26
  def self.add(*args)
31
27
  new(*args).validate.add_getter.add_setter
32
28
  end
@@ -44,10 +40,10 @@ module Hexx
44
40
  # @api hide
45
41
  # Validates parameters of the dependency declaration.
46
42
  #
47
- # @raise (see Hexx::Helpers::Base#check_target)
48
- # @raise (see Hexx::Helpers::Base#check_name_type)
49
- # @raise (see Hexx::Helpers::Base#check_name_value)
50
- # @return [Hexx::Helpers::Base] +self+
43
+ # @raise (see Hexx::Creators::Base#check_target)
44
+ # @raise (see Hexx::Creators::Base#check_name_type)
45
+ # @raise (see Hexx::Creators::Base#check_name_value)
46
+ # @return [Hexx::Creators::Base] +self+
51
47
  def validate
52
48
  check_target
53
49
  check_name_type
@@ -57,7 +53,7 @@ module Hexx
57
53
 
58
54
  # @api hide
59
55
  # Adds the parameter getter to the +target+ instance
60
- # @return [Hexx::Helpers::Base] +self+
56
+ # @return [Hexx::Creators::Base] +self+
61
57
  def add_getter
62
58
  target.class_eval getter
63
59
  self
@@ -65,7 +61,7 @@ module Hexx
65
61
 
66
62
  # @api hide
67
63
  # Adds the parameter setter to the +target+ instance
68
- # @return [Hexx::Helpers::Base] +self+
64
+ # @return [Hexx::Creators::Base] +self+
69
65
  def add_setter
70
66
  target.class_eval setter
71
67
  self
@@ -2,7 +2,7 @@
2
2
  require_relative "base"
3
3
 
4
4
  module Hexx
5
- module Helpers
5
+ module Creators
6
6
 
7
7
  # @api hide
8
8
  # Coerces class attribute getter and setter with given type.
@@ -22,11 +22,11 @@ module Hexx
22
22
  # Reloads the Base class initializer by adding the type of the attribute
23
23
  # to coerce with.
24
24
  #
25
- # @example (see Hexx::Helpers::Coercion)
26
- # @param (see Hexx::Helpers::Base.add)
25
+ # @example (see Hexx::Creators::Coercion)
26
+ # @param (see Hexx::Creators::Base.add)
27
27
  # @param [Module] type The type of the attribute.
28
- # @raise (see Hexx::Helpers::Coercion#validate)
29
- # @return [Hexx::Helpers::Coercion] the coercion object.
28
+ # @raise (see Hexx::Creators::Coercion#validate)
29
+ # @return [Hexx::Creators::Coercion] the coercion object.
30
30
  def initialize(target, name, type)
31
31
  super target, name
32
32
  @type = type
@@ -43,9 +43,9 @@ module Hexx
43
43
  # Adds validation of the coercion type to the
44
44
  # {Hexx::Helper::Base#validate}.
45
45
  #
46
- # @raise (see Hexx::Helpers::Base.validate)
47
- # @raise (see Hexx::Helpers::Coercion#check_type)
48
- # @return [Hexx::Helpers::Coercion] +self+
46
+ # @raise (see Hexx::Creators::Base.validate)
47
+ # @raise (see Hexx::Creators::Coercion#check_type)
48
+ # @return [Hexx::Creators::Coercion] +self+
49
49
  def validate
50
50
  check_type
51
51
  super
@@ -2,7 +2,7 @@
2
2
  require_relative "base"
3
3
 
4
4
  module Hexx
5
- module Helpers
5
+ module Creators
6
6
 
7
7
  # @api hide
8
8
  # @abstract
@@ -15,10 +15,10 @@ module Hexx
15
15
  # Reloads the Base class initializer by adding the default implementation
16
16
  # for the dependency.
17
17
  #
18
- # @param (see Hexx::Helpers::Base)
18
+ # @param (see Hexx::Creators::Base)
19
19
  # @param [Module] default The default implementation for the dependency.
20
- # @raise (see Hexx::Helpers::Dependency#validate)
21
- # @return [Hexx::Helpers::Dependency] the dependency object.
20
+ # @raise (see Hexx::Creators::Dependency#validate)
21
+ # @return [Hexx::Creators::Dependency] the dependency object.
22
22
  def initialize(target, name, default)
23
23
  super target, name
24
24
  @default = default
@@ -35,9 +35,9 @@ module Hexx
35
35
  # Adds validation of the dependency default implementation to the
36
36
  # {Hexx::Helper::Base#validate}.
37
37
  #
38
- # @raise (see Hexx::Helpers::Base#validate)
39
- # @raise (see Hexx::Helpers::Dependency#check_default)
40
- # @return [Hexx::Helpers::Dependency] +self+
38
+ # @raise (see Hexx::Creators::Base#validate)
39
+ # @raise (see Hexx::Creators::Dependency#check_default)
40
+ # @return [Hexx::Creators::Dependency] +self+
41
41
  def validate
42
42
  check_default
43
43
  super
@@ -64,7 +64,7 @@ module Hexx
64
64
  @#{ name } = value;
65
65
  #{ name };
66
66
  else;
67
- fail TypeError.new \"DI: value.inspect is not a module\";
67
+ fail TypeError.new \"DI: \#{ value.inspect } is not a module\";
68
68
  end;
69
69
  end"
70
70
  end
@@ -2,7 +2,7 @@
2
2
  require_relative "dependency"
3
3
 
4
4
  module Hexx
5
- module Helpers
5
+ module Creators
6
6
 
7
7
  # @api hide
8
8
  # Module dependency constructor.
@@ -24,7 +24,7 @@ module Hexx
24
24
 
25
25
  # @api hide
26
26
  # Adds the dependency to given module
27
- # @example (see Hexx::Helpers::ModuleDependency)
27
+ # @example (see Hexx::Creators::ModuleDependency)
28
28
  # @param [Module] target The module to declare the dependency of.
29
29
  # @param [String, Symbol] name The name of the dependency.
30
30
  # @param [Module] default The default implementation of the dependency.
@@ -2,7 +2,7 @@
2
2
  require_relative "base"
3
3
 
4
4
  module Hexx
5
- module Helpers
5
+ module Creators
6
6
 
7
7
  # @api hide
8
8
  # Adds setter and getter for the instance parameter.
@@ -45,7 +45,7 @@ module Hexx
45
45
  # @option options [String, Symbol, Class] :default (nil) Optional default
46
46
  # implementation for the dependency.
47
47
  def depends_on(name, default: nil)
48
- Helpers::Dependency.add self, name, default
48
+ Creators::Dependency.add self, name, default
49
49
  end
50
50
  end
51
51
  end
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+ require_relative "messages"
3
+
4
+ module Hexx
5
+ module Helpers
6
+
7
+ # @api hide
8
+ # The module declares instance helper methods to provide validations
9
+ module Exceptions
10
+ extend ActiveSupport::Concern
11
+ include ActiveModel::Validations
12
+ include Messages
13
+
14
+ # @api hide
15
+ # Class helper methods
16
+ module ClassMethods
17
+
18
+ def raises(*names)
19
+ fail ArgumentError if (types = names.flatten.map(&:to_s)) == []
20
+ types.each { |type| add_exception(type) }
21
+ end
22
+
23
+ private
24
+
25
+ def add_exception(type)
26
+ check type
27
+ class_eval "class #{ type } < StandardError; end"
28
+ end
29
+
30
+ def check(type)
31
+ return if type[/([A-Z][a-z]+)+/]
32
+ fail TypeError.new(
33
+ %(Wrong exception name "#{ type }". Use words in camel-case only.)
34
+ )
35
+ end
36
+ end
37
+
38
+ def on_error(messages)
39
+ messages.map(&:text).each { |text| errors.add :base, text }
40
+ fail Hexx::ServiceInvalid.new(self)
41
+ end
42
+
43
+ def escape
44
+ yield
45
+ rescue Hexx::ServiceInvalid => err
46
+ raise err
47
+ rescue => err
48
+ errors.add :base, err.message
49
+ raise Hexx::ServiceInvalid.new(self)
50
+ end
51
+ end
52
+ end
53
+ end