hexx 6.0.3 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
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