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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 44ade515af4a4044dce3725cf4a30ed2aef4da8e
4
- data.tar.gz: 1c456b7fb78fe55d509019f03c0fbbce27a8f777
3
+ metadata.gz: 74a2ccdca4564ce9cef52e8eb3b3a1304f4ec783
4
+ data.tar.gz: 6906b51c7a50fec6aa501d123d9d9a1d6831b1f8
5
5
  SHA512:
6
- metadata.gz: 255b6f79bd8726ffae49218d4f66ea397766ae79d265a2cdd353306217b5beedb73286e3c620f8ee45f2e42e94bf8ebf353452d9e504bdb35340009d1ac9a629
7
- data.tar.gz: acdc38e43842a7be485378975c1b3dacc7e99b4ee0c53c500f2f35c369f70da4a60242712067d7bea8bb9dd9e692641adbce06659dc6736db7555423852c5c84
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 (with a Rails controller):
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
@@ -35,19 +35,36 @@ module Hexx
35
35
  end
36
36
  end
37
37
 
38
- # Compares two messages by type and text.
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
- # Orders messages by type and text.
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
- private
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| send "#{ 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.send :params))
44
+ @params = hash.stringify_keys.slice(*(self.class.params))
42
45
  end
43
46
  end
44
47
  end
@@ -7,6 +7,10 @@ module Hexx
7
7
  module Validations
8
8
 
9
9
  # Includes the <tt>ActiveModel::Validations</tt> module to the service.
10
+ #
11
+ # Params
12
+ # +klass+:: a class that includes the module.
13
+ #
10
14
  def self.included(klass)
11
15
  klass.include ActiveModel::Validations
12
16
  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
@@ -2,5 +2,5 @@
2
2
  module Hexx
3
3
 
4
4
  # Current release.
5
- VERSION = "2.0.2"
5
+ VERSION = "2.1.0"
6
6
  end
data/lib/hexx.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "active_model"
2
2
  require "wisper"
3
3
 
4
+ # Application files to be loaded.
4
5
  def files
5
6
  @files ||= Dir[File.expand_path("../hexx/**/*.rb", __FILE__)]
6
7
  end
@@ -21,7 +21,7 @@ module Hexx
21
21
 
22
22
  subject { Message }
23
23
  let(:service) { Service.new }
24
- before { service.send(:errors).add :base, :text }
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
@@ -3,56 +3,81 @@ require "spec_helper"
3
3
  module Hexx
4
4
  describe Service do
5
5
 
6
- describe "class methods" do
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
- around :each do |example|
9
- class TestService < Service; end
10
- example.run
11
- Hexx.send :remove_const, :TestService
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
- subject { TestService }
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 "includes ActiveModel::Validations" do
17
- expect(subject.ancestors).to be_include ActiveModel::Validations
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 "includes Whisper::Publisher" do
21
- expect(subject.ancestors).to be_include Wisper::Publisher
41
+ it "whitelists params" do
42
+ expect(subject.params["wrong"]).to be_nil
22
43
  end
23
44
 
24
- describe ".allow_params" do
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
- let(:attribute) { :name }
27
- before { subject.send :allow_params, attribute }
54
+ subject { Test.new.with_callbacks }
28
55
 
29
- it "defines attributes" do
30
- expect(subject.instance_methods).to be_include attribute
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
- describe ".new" do
64
+ context "with a prefix" do
35
65
 
36
- let(:value) { "text" }
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 "stringifies params' keys" do
41
- expect(service.send(:params)["name"]).to eq value
42
- expect(service.send(:params)[:name]).to be_nil
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 "whitelistes params" do
46
- expect(service.send(:params)["wrong"]).to be_nil
47
- end
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 "##validate!" do
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 "##params" do
93
+ describe "#run" do
69
94
 
70
- it "is defined" do
71
- expect { subject.send :params }.not_to raise_error
95
+ it "fails with NotImplementedError" do
96
+ expect { subject.run }.to raise_error { NotImplementedError }
72
97
  end
73
98
  end
74
99
 
75
- describe "##t" do
100
+ describe "private" do
76
101
 
77
- let(:scope) { %w(activemodel messages models hexx/service) }
78
- let(:traslation) { I18n.t(:text, scope: scope, name: "name") }
102
+ subject { Service.new.with_callbacks }
79
103
 
80
- it "translates symbols in the service scope" do
81
- expect(subject.send :t, :text, name: "name").to eq traslation
82
- end
104
+ describe ".allow_params" do
83
105
 
84
- it "doesn't translate the string" do
85
- expect(subject.send :t, "text").to eq "text"
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
- describe "##messages" do
117
+ describe "#params" do
90
118
 
91
- it "returns an array" do
92
- expect(subject.send :messages).to be_kind_of Array
119
+ it "exists" do
120
+ expect { subject.params }.not_to raise_error
121
+ end
93
122
  end
94
- end
95
123
 
96
- describe "##add_message" do
124
+ describe "#t" do
97
125
 
98
- before { subject.send :add_message, :info, :text }
99
- let(:message) { subject.send(:messages).first }
126
+ let(:scope) { %w(activemodel messages models hexx/service) }
127
+ let(:traslation) { I18n.t(:text, scope: scope, name: "name") }
100
128
 
101
- it "adds a new message" do
102
- expect(message).to be_kind_of Service::Message
103
- expect(message.type).to eq "info"
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
- it "translates a symbol" do
107
- translation = subject.send :t, :text
108
- expect(message.text).to eq translation
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
- let(:listener) { double "listener" }
115
- before { subject.subscribe listener }
138
+ describe "#messages" do
116
139
 
117
- it "yields a block" do
118
- value = "Hello!"
119
- result = subject.send(:transaction) { value }
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
- context "when a service invalid" do
145
+ describe "#add_message" do
124
146
 
125
- before { allow(subject).to receive(:validate!) { fail } }
147
+ before { subject.add_message :info, :text }
148
+ let(:message) { subject.messages.first }
126
149
 
127
- it "doesn't yield a block" do
128
- value = "Hello!"
129
- result = subject.send(:transaction) { value }
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 "publishes an :error" do
134
- expect(listener).to receive(:error)
135
- subject.send(:transaction)
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
- context "when a block raises Service::Invalid error" do
161
+ describe "#transaction" do
140
162
 
141
- before { subject.send(:errors).add :base, :text }
142
- let(:run) do
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 "rescues" do
147
- expect { run }.not_to raise_error
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
- it "publishes an :error with service messages" do
151
- expect(listener).to receive(:error) do |messages|
152
- expect(messages).to eq Service::Message.from(subject)
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
- context "when a block raises StandardError" do
190
+ context "when a block raises Service::Invalid error" do
159
191
 
160
- let(:run) { subject.send(:transaction) { fail "text" } }
192
+ before { subject.errors.add :base, :text }
193
+ let(:run) do
194
+ subject.transaction { fail Service::Invalid.new(subject) }
195
+ end
161
196
 
162
- it "rescues" do
163
- expect { run }.not_to raise_error
164
- end
197
+ it "rescues" do
198
+ expect { run }.not_to raise_error
199
+ end
165
200
 
166
- it "publishes an :error with a message" do
167
- expect(listener).to receive(:error) do |messages|
168
- expect(messages)
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
- describe "#run" do
207
+ context "when a block raises StandardError" do
177
208
 
178
- it "fails with NotImplementedError" do
179
- expect { subject.run }.to raise_error { NotImplementedError }
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.2
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-04 00:00:00.000000000 Z
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