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