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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/README.rdoc +63 -48
- data/lib/hexx.rb +1 -0
- data/lib/hexx/coercible.rb +2 -2
- data/lib/hexx/configurable.rb +1 -1
- data/lib/hexx/{helpers → creators}/base.rb +11 -15
- data/lib/hexx/{helpers → creators}/coercion.rb +8 -8
- data/lib/hexx/{helpers → creators}/dependency.rb +8 -8
- data/lib/hexx/{helpers → creators}/module_dependency.rb +2 -2
- data/lib/hexx/{helpers → creators}/parameter.rb +1 -1
- data/lib/hexx/dependable.rb +1 -1
- data/lib/hexx/helpers/exceptions.rb +53 -0
- data/lib/hexx/helpers/messages.rb +28 -0
- data/lib/hexx/helpers/parameters.rb +47 -0
- data/lib/hexx/helpers/validations.rb +21 -0
- data/lib/hexx/message.rb +79 -0
- data/lib/hexx/null.rb +0 -4
- data/lib/hexx/service.rb +259 -208
- data/lib/hexx/service_invalid.rb +73 -0
- data/lib/hexx/version.rb +1 -1
- data/spec/hexx/helpers/exceptions_spec.rb +96 -0
- data/spec/hexx/helpers/messages_spec.rb +56 -0
- data/spec/hexx/helpers/parameters_spec.rb +96 -0
- data/spec/hexx/helpers/validations_spec.rb +32 -0
- data/spec/hexx/message_spec.rb +83 -0
- data/spec/hexx/service_invalid_spec.rb +46 -0
- data/spec/hexx/service_spec.rb +41 -242
- data/spec/spec_helper.rb +1 -1
- data/spec/{initializers → support/initializers}/focus.rb +0 -0
- data/spec/{initializers → support/initializers}/garbage_collection.rb +0 -0
- data/spec/{initializers → support/initializers}/i18n.rb +0 -0
- data/spec/{initializers → support/initializers}/random_order.rb +0 -0
- data/spec/{initializers → support/initializers}/rspec.rb +0 -0
- data/spec/support/matchers/methods.rb +11 -0
- metadata +37 -23
- data/lib/hexx/service/invalid.rb +0 -76
- data/lib/hexx/service/message.rb +0 -81
- data/spec/hexx/service/invalid_spec.rb +0 -52
- data/spec/hexx/service/message_spec.rb +0 -85
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79228f50ba0633896a58f7e29909a69db9084327
|
4
|
+
data.tar.gz: 796d2e839958e2e93faf062bda2678d514d06c6e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2bf8701306d0e731b6ef3789bdbcb4e3e5540985e769694cadc218693eb7516946912467a9489b68758a889143e5962e489f5d67f13d576e5dabe02330916e36
|
7
|
+
data.tar.gz: e202c8efe9ad52e3cff511cfc9934f73e46057d1db467c4979b7afb895e2918a86c63fcfabd40dd7e801e6ff4e1f60c53acea6550bbda965304dae54d70afc2b
|
data/.rubocop.yml
CHANGED
data/README.rdoc
CHANGED
@@ -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::
|
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
|
-
#
|
166
|
-
|
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
|
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::
|
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::
|
238
|
+
=== Hexx::Message
|
241
239
|
|
242
240
|
The messages published by the service has two attributes: +type+ and +text+.
|
243
241
|
|
244
|
-
message = Hexx::
|
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::
|
249
|
+
messages # => [#<Hexx::Message @type="error", @text="text" >]
|
252
250
|
|
253
|
-
=== Hexx::
|
251
|
+
=== Hexx::Dependable
|
254
252
|
|
255
|
-
The
|
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
|
-
|
258
|
-
|
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
|
-
|
258
|
+
class AddItem < Hexx::Service
|
259
|
+
extend Hexx::Dependable
|
262
260
|
|
263
|
-
|
264
|
-
Hexx::Null && true # => true
|
265
|
-
|
266
|
-
# But:
|
267
|
-
!!Hexx::Null && true # => false
|
261
|
+
depends_on :get_item, default: GetItem
|
268
262
|
|
269
|
-
|
263
|
+
# ...
|
270
264
|
|
271
|
-
|
272
|
-
|
265
|
+
def find_item
|
266
|
+
run_service get_item, :on_item, name: name
|
267
|
+
end
|
268
|
+
end
|
273
269
|
|
274
|
-
|
275
|
-
implementation:
|
270
|
+
Now the dependency can be injected afterwards:
|
276
271
|
|
277
|
-
|
278
|
-
|
272
|
+
# The default implementation
|
273
|
+
service = AddItem.new
|
274
|
+
service.get_item # => GetItem
|
279
275
|
|
280
|
-
|
281
|
-
|
282
|
-
|
276
|
+
# Change it to other implementation
|
277
|
+
service.get_item = FindItem
|
278
|
+
service.get_item # => FindItem
|
283
279
|
|
284
|
-
|
280
|
+
# Reset it to default by assigning +nil+
|
281
|
+
service.get_item = nil
|
282
|
+
service.get_item # => GetItem
|
285
283
|
|
286
|
-
|
284
|
+
It is possible to test a service in isolation from its dependencies.
|
287
285
|
|
288
|
-
#
|
289
|
-
|
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
|
-
|
292
|
-
|
293
|
-
|
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
|
-
|
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
|
-
#
|
300
|
-
|
301
|
-
|
312
|
+
# Though:
|
313
|
+
Hexx::Null && true # => true
|
314
|
+
|
315
|
+
# But:
|
316
|
+
!!Hexx::Null && true # => false
|
302
317
|
|
303
318
|
== License
|
304
319
|
|
data/lib/hexx.rb
CHANGED
data/lib/hexx/coercible.rb
CHANGED
@@ -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::
|
38
|
+
# @return [Class] attribute coercion (Hexx::Creators::Coercion by default)
|
39
39
|
def coercion
|
40
|
-
Hexx::
|
40
|
+
Hexx::Creators::Coercion
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
data/lib/hexx/configurable.rb
CHANGED
@@ -3,13 +3,9 @@
|
|
3
3
|
module Hexx
|
4
4
|
|
5
5
|
# @api hide
|
6
|
-
# The module contains
|
7
|
-
#
|
8
|
-
|
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::
|
29
|
-
# @return [Hexx::
|
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::
|
48
|
-
# @raise (see Hexx::
|
49
|
-
# @raise (see Hexx::
|
50
|
-
# @return [Hexx::
|
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::
|
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::
|
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
|
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::
|
26
|
-
# @param (see Hexx::
|
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::
|
29
|
-
# @return [Hexx::
|
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::
|
47
|
-
# @raise (see Hexx::
|
48
|
-
# @return [Hexx::
|
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
|
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::
|
18
|
+
# @param (see Hexx::Creators::Base)
|
19
19
|
# @param [Module] default The default implementation for the dependency.
|
20
|
-
# @raise (see Hexx::
|
21
|
-
# @return [Hexx::
|
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::
|
39
|
-
# @raise (see Hexx::
|
40
|
-
# @return [Hexx::
|
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
|
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::
|
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.
|
data/lib/hexx/dependable.rb
CHANGED
@@ -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
|
-
|
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
|