hexx 2.0.2 → 2.1.0

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