hexx 2.0.2 → 2.1.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/README.rdoc +2 -1
- data/lib/hexx/service/callbacks.rb +77 -0
- data/lib/hexx/service/message.rb +19 -2
- data/lib/hexx/service/parameters.rb +7 -4
- data/lib/hexx/service/validations.rb +4 -0
- data/lib/hexx/service.rb +2 -1
- data/lib/hexx/version.rb +1 -1
- data/lib/hexx.rb +1 -0
- data/spec/hexx/service/message_spec.rb +1 -1
- data/spec/hexx/service_spec.rb +143 -100
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74a2ccdca4564ce9cef52e8eb3b3a1304f4ec783
|
4
|
+
data.tar.gz: 6906b51c7a50fec6aa501d123d9d9a1d6831b1f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 260e196ac0060995a5dc03e821fb14872013dfe689680973bad2e027e92a7c7c1e836241b83433a366fa3c8f55607031ea19e9226d0c0394256fd3fa40b13e41
|
7
|
+
data.tar.gz: 1cd7bdb303092c0cad0ab2d75c18c4f7fcd387ac0ac1556aaf21cfe328c08306bb69e885457eab8a0f799e4d495cc1238bc4bb50ee9f2047ba0ea87b49d4518a
|
data/README.rdoc
CHANGED
@@ -70,7 +70,7 @@ A typical service object is shown below:
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
Usage of the service (
|
73
|
+
Usage of the service (in a Rails controller):
|
74
74
|
|
75
75
|
class ItemsController < ActionController::Base
|
76
76
|
|
@@ -141,6 +141,7 @@ preparation:
|
|
141
141
|
== Relevant Links
|
142
142
|
|
143
143
|
* Matt Wynne's talk {Hexagonal Rails}[http://www.confreaks.com/videos/977-goruco2012-hexagonal-rails]
|
144
|
+
* { wisper }[http://www.github.com/krisleech/wisper] gem by Kris Leech.
|
144
145
|
|
145
146
|
== License
|
146
147
|
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Hexx
|
2
|
+
class Service
|
3
|
+
|
4
|
+
# Allows to expose private callbacks to notifiers.
|
5
|
+
module Callbacks
|
6
|
+
|
7
|
+
# Grants access to object methods with given prefix.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# WithCallbacks.new(object, prefix: :on)
|
11
|
+
#
|
12
|
+
# Params:
|
13
|
+
# +object+:: (required) ...a service object to access to.
|
14
|
+
# <tt>prefix:</tt>:: (optional, symbol or string, default: +nil+)
|
15
|
+
# ...prefix for private methods to be accessible.
|
16
|
+
#
|
17
|
+
class WithCallbacks
|
18
|
+
|
19
|
+
def initialize(object, prefix: nil)
|
20
|
+
@object = object
|
21
|
+
@prefix = Regexp.new(prefix ? "^#{ prefix }_" : "")
|
22
|
+
end
|
23
|
+
|
24
|
+
# Redefines the +respond_to?+ to allow access to object's methods.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# service = WithCallbacks(some_service, prefix: :on)
|
28
|
+
# service.respond_to? :on_something # => true
|
29
|
+
#
|
30
|
+
# Params:
|
31
|
+
# +method+:: a method name to check access to.
|
32
|
+
#
|
33
|
+
def respond_to?(method, *)
|
34
|
+
object.respond_to?(method) || valid_callback?(method) || super
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def method_missing(method, *args, &block)
|
40
|
+
valid_callback?(method) ? object.send(method, *args, &block) : super
|
41
|
+
end
|
42
|
+
|
43
|
+
def valid_callback?(method)
|
44
|
+
method.to_s[prefix] && object.respond_to?(method, include_all: true)
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :object, :prefix
|
48
|
+
end
|
49
|
+
|
50
|
+
# Makes private methods with given prefix public.
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# class MyService < Hexx::Service
|
54
|
+
# def call
|
55
|
+
# # ...
|
56
|
+
# other_service = OtherService.new
|
57
|
+
# other_service.subscribe(self.with_callbacks(prefix: :on))
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# private
|
61
|
+
#
|
62
|
+
# # private, but is made publicly accessible
|
63
|
+
# def on_success
|
64
|
+
# # ...
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# Params:
|
69
|
+
# <tt>prefix:</tt>:: (optional, symbol or string, default: +nil+)
|
70
|
+
# ...prefix of private methods to be accessible.
|
71
|
+
#
|
72
|
+
def with_callbacks(prefix: nil)
|
73
|
+
WithCallbacks.new(self, prefix: prefix)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/hexx/service/message.rb
CHANGED
@@ -35,19 +35,36 @@ module Hexx
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
#
|
38
|
+
# Distinguishes two messages by type and text.
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# a = Message.new(type: "a", text: "a")
|
42
|
+
# b = Message.new(type: "a", text: "a")
|
43
|
+
# c = Message.new(type: "b", text: "a")
|
44
|
+
# d = Message.new(type: "a", text: "b")
|
45
|
+
#
|
46
|
+
# a == b # => true
|
47
|
+
# a == c # => false
|
48
|
+
# a == d # => false
|
49
|
+
#
|
50
|
+
# Params:
|
51
|
+
# +other+:: other message to distinguish from.
|
52
|
+
#
|
39
53
|
def ==(other)
|
40
54
|
return false unless other.is_a? self.class
|
41
55
|
[type, text] == [other.type, other.text]
|
42
56
|
end
|
43
57
|
|
44
|
-
#
|
58
|
+
# Compares messages by type and text.
|
45
59
|
#
|
46
60
|
# @example
|
47
61
|
# ab = Message.new(type: "a", text: "b")
|
48
62
|
# ba = Message.new(type: "b", text: "a")
|
49
63
|
# ab < ba # => true
|
50
64
|
#
|
65
|
+
# Params:
|
66
|
+
# +other+:: other message to compare with.
|
67
|
+
#
|
51
68
|
def <=>(other)
|
52
69
|
fail ArgumentError unless other.is_a? self.class
|
53
70
|
[type, text] <=> [other.type, other.text]
|
@@ -8,12 +8,15 @@ module Hexx
|
|
8
8
|
# Methods to declare and allow services params.
|
9
9
|
module ClassMethods
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
# A list of allowed params for the service objects.
|
13
12
|
def params
|
14
13
|
@params ||= []
|
15
14
|
end
|
16
15
|
|
16
|
+
private
|
17
|
+
|
18
|
+
# Sets a list of allowed parameters for the class constructor and
|
19
|
+
# adds corresponding attributes to instance methods.
|
17
20
|
def allow_params(*keys)
|
18
21
|
@params = keys.map(&:to_s)
|
19
22
|
attr_accessor(*params)
|
@@ -30,7 +33,7 @@ module Hexx
|
|
30
33
|
#
|
31
34
|
def initialize(hash = {})
|
32
35
|
extract_params_from hash
|
33
|
-
params.each { |key, value|
|
36
|
+
params.each { |key, value| public_send "#{ key }=", value }
|
34
37
|
end
|
35
38
|
|
36
39
|
private
|
@@ -38,7 +41,7 @@ module Hexx
|
|
38
41
|
attr_reader :params
|
39
42
|
|
40
43
|
def extract_params_from(hash)
|
41
|
-
@params = hash.stringify_keys.slice(*(self.class.
|
44
|
+
@params = hash.stringify_keys.slice(*(self.class.params))
|
42
45
|
end
|
43
46
|
end
|
44
47
|
end
|
data/lib/hexx/service.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# Service modules the +Service+ class depends on.
|
1
2
|
def service_modules
|
2
3
|
Dir[File.expand_path("../service/*.rb", __FILE__)]
|
3
4
|
end
|
@@ -8,7 +9,7 @@ module Hexx
|
|
8
9
|
|
9
10
|
# Base class for service objects.
|
10
11
|
class Service
|
11
|
-
include Messages, Parameters, Transactions, Validations
|
12
|
+
include Messages, Parameters, Transactions, Validations, Callbacks
|
12
13
|
|
13
14
|
# Runs the service object.
|
14
15
|
#
|
data/lib/hexx/version.rb
CHANGED
data/lib/hexx.rb
CHANGED
@@ -21,7 +21,7 @@ module Hexx
|
|
21
21
|
|
22
22
|
subject { Message }
|
23
23
|
let(:service) { Service.new }
|
24
|
-
before { service.
|
24
|
+
before { service.public_send(:errors).add :base, :text }
|
25
25
|
|
26
26
|
it "extracts error messages from a record" do
|
27
27
|
message = subject.from(service).first
|
data/spec/hexx/service_spec.rb
CHANGED
@@ -3,56 +3,81 @@ require "spec_helper"
|
|
3
3
|
module Hexx
|
4
4
|
describe Service do
|
5
5
|
|
6
|
-
|
6
|
+
around :each do |example|
|
7
|
+
class Test < Service
|
8
|
+
private
|
9
|
+
attr_reader :on_something, :something
|
10
|
+
end
|
11
|
+
example.run
|
12
|
+
Hexx.send :remove_const, :Test
|
13
|
+
end
|
14
|
+
|
15
|
+
it "includes ActiveModel::Validations" do
|
16
|
+
expect(Service.ancestors).to be_include ActiveModel::Validations
|
17
|
+
end
|
7
18
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
19
|
+
it "includes Whisper::Publisher" do
|
20
|
+
expect(Service.ancestors).to be_include Wisper::Publisher
|
21
|
+
end
|
22
|
+
|
23
|
+
describe ".params" do
|
24
|
+
|
25
|
+
it "returns an array" do
|
26
|
+
expect(Test.params).to be_kind_of Array
|
12
27
|
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe ".new" do
|
13
31
|
|
14
|
-
|
32
|
+
let(:value) { "text" }
|
33
|
+
before { Test.send :allow_params, :name }
|
34
|
+
subject { Test.new(name: value, wrong: "wrong").with_callbacks }
|
15
35
|
|
16
|
-
it "
|
17
|
-
expect(subject.
|
36
|
+
it "stringifies params' keys" do
|
37
|
+
expect(subject.params["name"]).to eq value
|
38
|
+
expect(subject.params[:name]).to be_nil
|
18
39
|
end
|
19
40
|
|
20
|
-
it "
|
21
|
-
expect(subject.
|
41
|
+
it "whitelists params" do
|
42
|
+
expect(subject.params["wrong"]).to be_nil
|
22
43
|
end
|
23
44
|
|
24
|
-
|
45
|
+
it "initializes attributes from params" do
|
46
|
+
expect(subject.name).to eq value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#with_callbacks" do
|
51
|
+
|
52
|
+
context "without a prefix" do
|
25
53
|
|
26
|
-
|
27
|
-
before { subject.send :allow_params, attribute }
|
54
|
+
subject { Test.new.with_callbacks }
|
28
55
|
|
29
|
-
it "
|
30
|
-
expect(subject.
|
56
|
+
it "makes all private methods public" do
|
57
|
+
expect(subject.respond_to? :something).to be_truthy
|
58
|
+
expect(subject.respond_to? :on_something).to be_truthy
|
59
|
+
expect { subject.something }.not_to raise_error
|
60
|
+
expect { subject.on_something }.not_to raise_error
|
31
61
|
end
|
32
62
|
end
|
33
63
|
|
34
|
-
|
64
|
+
context "with a prefix" do
|
35
65
|
|
36
|
-
|
37
|
-
before { subject.send :allow_params, :name }
|
38
|
-
let!(:service) { subject.new name: value, wrong: "wrong" }
|
66
|
+
subject { Test.new.with_callbacks prefix: :on }
|
39
67
|
|
40
|
-
it "
|
41
|
-
expect(
|
42
|
-
expect
|
68
|
+
it "makes prefixed private methods public" do
|
69
|
+
expect(subject.respond_to? :on_something).to be_truthy
|
70
|
+
expect { subject.on_something }.not_to raise_error
|
43
71
|
end
|
44
72
|
|
45
|
-
it "
|
46
|
-
expect(
|
47
|
-
|
48
|
-
|
49
|
-
it "initializes attributes from params" do
|
50
|
-
expect(service.send :name).to eq value
|
73
|
+
it "doesn't make other private methods public" do
|
74
|
+
expect(subject.respond_to? :something).to be_falsey
|
75
|
+
expect { subject.something }.to raise_error
|
51
76
|
end
|
52
77
|
end
|
53
78
|
end
|
54
79
|
|
55
|
-
describe "
|
80
|
+
describe "#validate!" do
|
56
81
|
|
57
82
|
it "passes when object is valid" do
|
58
83
|
expect { subject.validate! }.not_to raise_error
|
@@ -65,118 +90,136 @@ module Hexx
|
|
65
90
|
end
|
66
91
|
end
|
67
92
|
|
68
|
-
describe "
|
93
|
+
describe "#run" do
|
69
94
|
|
70
|
-
it "
|
71
|
-
expect { subject.
|
95
|
+
it "fails with NotImplementedError" do
|
96
|
+
expect { subject.run }.to raise_error { NotImplementedError }
|
72
97
|
end
|
73
98
|
end
|
74
99
|
|
75
|
-
describe "
|
100
|
+
describe "private" do
|
76
101
|
|
77
|
-
|
78
|
-
let(:traslation) { I18n.t(:text, scope: scope, name: "name") }
|
102
|
+
subject { Service.new.with_callbacks }
|
79
103
|
|
80
|
-
|
81
|
-
expect(subject.send :t, :text, name: "name").to eq traslation
|
82
|
-
end
|
104
|
+
describe ".allow_params" do
|
83
105
|
|
84
|
-
|
85
|
-
|
106
|
+
before { Test.send :allow_params, :name }
|
107
|
+
|
108
|
+
it "sets class params" do
|
109
|
+
expect(Test.params).to eq %w(name)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "defines instance attributes" do
|
113
|
+
expect(Test.instance_methods).to be_include :name
|
114
|
+
end
|
86
115
|
end
|
87
|
-
end
|
88
116
|
|
89
|
-
|
117
|
+
describe "#params" do
|
90
118
|
|
91
|
-
|
92
|
-
|
119
|
+
it "exists" do
|
120
|
+
expect { subject.params }.not_to raise_error
|
121
|
+
end
|
93
122
|
end
|
94
|
-
end
|
95
123
|
|
96
|
-
|
124
|
+
describe "#t" do
|
97
125
|
|
98
|
-
|
99
|
-
|
126
|
+
let(:scope) { %w(activemodel messages models hexx/service) }
|
127
|
+
let(:traslation) { I18n.t(:text, scope: scope, name: "name") }
|
100
128
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
end
|
129
|
+
it "translates symbols in the service scope" do
|
130
|
+
expect(subject.t(:text, name: "name")).to eq traslation
|
131
|
+
end
|
105
132
|
|
106
|
-
|
107
|
-
|
108
|
-
|
133
|
+
it "doesn't translate the string" do
|
134
|
+
expect(subject.t("text")).to eq "text"
|
135
|
+
end
|
109
136
|
end
|
110
|
-
end
|
111
|
-
|
112
|
-
describe "##transaction" do
|
113
137
|
|
114
|
-
|
115
|
-
before { subject.subscribe listener }
|
138
|
+
describe "#messages" do
|
116
139
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
expect(result).to eq value
|
140
|
+
it "returns an array" do
|
141
|
+
expect(subject.messages).to be_kind_of Array
|
142
|
+
end
|
121
143
|
end
|
122
144
|
|
123
|
-
|
145
|
+
describe "#add_message" do
|
124
146
|
|
125
|
-
before
|
147
|
+
before { subject.add_message :info, :text }
|
148
|
+
let(:message) { subject.messages.first }
|
126
149
|
|
127
|
-
it "
|
128
|
-
|
129
|
-
|
130
|
-
expect(result).not_to eq value
|
150
|
+
it "adds a new message" do
|
151
|
+
expect(message).to be_kind_of Service::Message
|
152
|
+
expect(message.type).to eq "info"
|
131
153
|
end
|
132
154
|
|
133
|
-
it "
|
134
|
-
|
135
|
-
|
155
|
+
it "translates a symbol" do
|
156
|
+
translation = subject.t :text
|
157
|
+
expect(message.text).to eq translation
|
136
158
|
end
|
137
159
|
end
|
138
160
|
|
139
|
-
|
161
|
+
describe "#transaction" do
|
140
162
|
|
141
|
-
|
142
|
-
|
143
|
-
subject.send(:transaction) { fail Service::Invalid.new(subject) }
|
144
|
-
end
|
163
|
+
let(:listener) { double "listener" }
|
164
|
+
before { subject.subscribe listener }
|
145
165
|
|
146
|
-
it "
|
147
|
-
|
166
|
+
it "yields a block" do
|
167
|
+
value = "Hello!"
|
168
|
+
result = subject.transaction { value }
|
169
|
+
expect(result).to eq value
|
148
170
|
end
|
149
171
|
|
150
|
-
|
151
|
-
|
152
|
-
|
172
|
+
context "when a service invalid" do
|
173
|
+
|
174
|
+
before do
|
175
|
+
allow_any_instance_of(Service).to receive(:validate!) { fail }
|
176
|
+
end
|
177
|
+
|
178
|
+
it "doesn't yield a block" do
|
179
|
+
value = "Hello!"
|
180
|
+
result = subject.transaction { value }
|
181
|
+
expect(result).not_to eq value
|
182
|
+
end
|
183
|
+
|
184
|
+
it "publishes an :error" do
|
185
|
+
expect(listener).to receive(:error)
|
186
|
+
subject.transaction
|
153
187
|
end
|
154
|
-
run
|
155
188
|
end
|
156
|
-
end
|
157
189
|
|
158
|
-
|
190
|
+
context "when a block raises Service::Invalid error" do
|
159
191
|
|
160
|
-
|
192
|
+
before { subject.errors.add :base, :text }
|
193
|
+
let(:run) do
|
194
|
+
subject.transaction { fail Service::Invalid.new(subject) }
|
195
|
+
end
|
161
196
|
|
162
|
-
|
163
|
-
|
164
|
-
|
197
|
+
it "rescues" do
|
198
|
+
expect { run }.not_to raise_error
|
199
|
+
end
|
165
200
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
.to eq [Service::Message.new(type: "error", text: "text")]
|
201
|
+
it "publishes an :error with service messages" do
|
202
|
+
expect(listener).to receive(:error)
|
203
|
+
run
|
170
204
|
end
|
171
|
-
run
|
172
205
|
end
|
173
|
-
end
|
174
|
-
end
|
175
206
|
|
176
|
-
|
207
|
+
context "when a block raises StandardError" do
|
177
208
|
|
178
|
-
|
179
|
-
|
209
|
+
let(:run) { subject.transaction { fail "text" } }
|
210
|
+
|
211
|
+
it "rescues" do
|
212
|
+
expect { run }.not_to raise_error
|
213
|
+
end
|
214
|
+
|
215
|
+
it "publishes an :error with a message" do
|
216
|
+
expect(listener).to receive(:error) do |messages|
|
217
|
+
expect(messages)
|
218
|
+
.to eq [Service::Message.new(type: "error", text: "text")]
|
219
|
+
end
|
220
|
+
run
|
221
|
+
end
|
222
|
+
end
|
180
223
|
end
|
181
224
|
end
|
182
225
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hexx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kozin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-11-
|
11
|
+
date: 2014-11-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -138,6 +138,7 @@ files:
|
|
138
138
|
- lib/hexx/models.rb
|
139
139
|
- lib/hexx/models/base_coercer.rb
|
140
140
|
- lib/hexx/service.rb
|
141
|
+
- lib/hexx/service/callbacks.rb
|
141
142
|
- lib/hexx/service/invalid.rb
|
142
143
|
- lib/hexx/service/message.rb
|
143
144
|
- lib/hexx/service/messages.rb
|