service_objects 0.0.1
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 +7 -0
- data/.coveralls.yml +1 -0
- data/.metrics +5 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +4 -0
- data/.yardopts +3 -0
- data/Gemfile +3 -0
- data/Guardfile +16 -0
- data/LICENSE +21 -0
- data/README.md +461 -0
- data/Rakefile +17 -0
- data/config/metrics/STYLEGUIDE +230 -0
- data/config/metrics/cane.yml +5 -0
- data/config/metrics/churn.yml +6 -0
- data/config/metrics/flay.yml +2 -0
- data/config/metrics/metric_fu.yml +14 -0
- data/config/metrics/pippi.yml +3 -0
- data/config/metrics/reek.yml +1 -0
- data/config/metrics/roodi.yml +24 -0
- data/config/metrics/rubocop.yml +75 -0
- data/config/metrics/saikuro.yml +3 -0
- data/config/metrics/simplecov.yml +6 -0
- data/config/metrics/yardstick.yml +37 -0
- data/lib/service_objects/base.rb +43 -0
- data/lib/service_objects/helpers/dependable.rb +63 -0
- data/lib/service_objects/helpers/exceptions.rb +64 -0
- data/lib/service_objects/helpers/messages.rb +96 -0
- data/lib/service_objects/helpers/parameterized.rb +85 -0
- data/lib/service_objects/helpers/parameters.rb +71 -0
- data/lib/service_objects/helpers/validations.rb +54 -0
- data/lib/service_objects/invalid.rb +55 -0
- data/lib/service_objects/listener.rb +97 -0
- data/lib/service_objects/message.rb +117 -0
- data/lib/service_objects/null.rb +26 -0
- data/lib/service_objects/utils/normal_hash.rb +34 -0
- data/lib/service_objects/version.rb +9 -0
- data/lib/service_objects.rb +12 -0
- data/service_objects.gemspec +28 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/tests/base_spec.rb +43 -0
- data/spec/tests/helpers/dependable_spec.rb +77 -0
- data/spec/tests/helpers/exceptions_spec.rb +112 -0
- data/spec/tests/helpers/messages_spec.rb +64 -0
- data/spec/tests/helpers/parameterized_spec.rb +136 -0
- data/spec/tests/helpers/parameters_spec.rb +71 -0
- data/spec/tests/helpers/validations_spec.rb +60 -0
- data/spec/tests/invalid_spec.rb +69 -0
- data/spec/tests/listener_spec.rb +50 -0
- data/spec/tests/message_spec.rb +191 -0
- data/spec/tests/null_spec.rb +17 -0
- data/spec/tests/utils/normal_hash_spec.rb +16 -0
- metadata +182 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b235fa6809000778410a4af3b1844d7665a600b9
|
4
|
+
data.tar.gz: 0f352e8303b2a39fd6e7ad72750b95f14b53c6dc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 34c1d9c63afd08d42d7d37102bd85acb98e8e49f53024d469ef66ce6ed1be0b5ea0522a6b86009e694539635c134b94a49484d9bd8a9be6c919c7da1dd75b5d8
|
7
|
+
data.tar.gz: 2ede947fcf3c189e3ff82cd275ad43e5aefdc83bf7de3bee4cc0e2746aa425aed85e8d4b21e608b23c9dc098de3a2fcb3c76415674e160f89212a052a96615ae
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.metrics
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
4
|
+
|
5
|
+
watch("lib/service_objects.rb") { "spec" }
|
6
|
+
|
7
|
+
watch(%r{^lib/service_objects/(.+)\.rb$}) do |m|
|
8
|
+
"spec/tests/#{ m[1] }_spec.rb"
|
9
|
+
end
|
10
|
+
|
11
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
12
|
+
|
13
|
+
watch(/^spec\/spec_helper\w*\.rb$/) { "spec" }
|
14
|
+
|
15
|
+
watch(%r{^spec/tests/.+_spec\.rb$})
|
16
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2014 Andrew Kozin, https://github.com/nepalez
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,461 @@
|
|
1
|
+
# ServiceObjects
|
2
|
+
|
3
|
+
[][gem]
|
4
|
+
[][travis]
|
5
|
+
[][gemnasium]
|
6
|
+
[][codeclimate]
|
7
|
+
[][coveralls]
|
8
|
+
[][license]
|
9
|
+
|
10
|
+
[gem]: https://rubygems.org/gems/service_objects
|
11
|
+
[travis]: https://travis-ci.org/nepalez/service_objects
|
12
|
+
[gemnasium]: https://gemnasium.com/nepalez/service_objects
|
13
|
+
[codeclimate]: https://codeclimate.com/github/nepalez/service_objects
|
14
|
+
[coveralls]: https://coveralls.io/r/nepalez/service_objects
|
15
|
+
[license]: file:LICENSE
|
16
|
+
|
17
|
+
The module implements two design patterns:
|
18
|
+
|
19
|
+
* The [Interactor pattern] to decouple business logics from both models and delivery mechanisms, such as [Rails].
|
20
|
+
* The [Observer pattern] to follow the [Tell, don't Ask] design princible.
|
21
|
+
The pattern is implemented with the help of [wisper] gem by [Kris Leech].
|
22
|
+
|
23
|
+
[Rails]: http://rubyonrails.org/
|
24
|
+
[Interactor pattern]: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
|
25
|
+
[Observer pattern]: http://reefpoints.dockyard.com/2013/08/20/design-patterns-observer-pattern.html
|
26
|
+
[Tell, don't Ask]: http://martinfowler.com/bliki/TellDontAsk.html
|
27
|
+
[wisper]: http://www.github.com/krisleech/wisper
|
28
|
+
[Kris Leech]: http://www.github.com/krisleech
|
29
|
+
|
30
|
+
The module API provides 3 classes:
|
31
|
+
|
32
|
+
* `ServiceObjects::Base` - for service objects.
|
33
|
+
* `ServiceObjects::Listener` - for decorating objects with methods called by service notificiations.
|
34
|
+
* `ServiceObjects::Message` - for messages published by service objects.
|
35
|
+
|
36
|
+
## Installation
|
37
|
+
|
38
|
+
Add this line to your application's Gemfile:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
gem "service_objects"
|
42
|
+
```
|
43
|
+
|
44
|
+
And then execute:
|
45
|
+
|
46
|
+
```
|
47
|
+
bundle
|
48
|
+
```
|
49
|
+
|
50
|
+
Or install it yourself as:
|
51
|
+
|
52
|
+
```
|
53
|
+
gem install service_objects
|
54
|
+
```
|
55
|
+
|
56
|
+
## Usage
|
57
|
+
|
58
|
+
The basic usage of the services by example of Rails controller:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
# lib/my_gem.rb
|
62
|
+
require "service_objects"
|
63
|
+
|
64
|
+
# app/services/add_foo.rb
|
65
|
+
class AddFoo < ServiceObjects::Base
|
66
|
+
|
67
|
+
# whitelists parameters and defines #params, #bar and #baz
|
68
|
+
allows_params :bar, :baz
|
69
|
+
|
70
|
+
# declares external dependencies
|
71
|
+
depends_on :find_foo, default: FindFoo
|
72
|
+
|
73
|
+
# calls the service, sorts out and reports its results
|
74
|
+
def run
|
75
|
+
run!
|
76
|
+
rescue Invalid => err
|
77
|
+
publish :error, err.messages
|
78
|
+
else
|
79
|
+
publish :created, @foo, messages
|
80
|
+
ensure
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# business logic lives here
|
87
|
+
def run!
|
88
|
+
# ... usage of the external service find_foo postponed
|
89
|
+
add_foo
|
90
|
+
end
|
91
|
+
|
92
|
+
# rescues errors and re-raises them as Invalid with list of #messages
|
93
|
+
def add_foo
|
94
|
+
escape { @foo ||= Foo.create! bar: bar, baz: baz }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
# app/controllers/foos_controller.rb
|
101
|
+
class FoosController < ApplicationController
|
102
|
+
|
103
|
+
def create
|
104
|
+
# Create the service object with necessary parameters.
|
105
|
+
# All the business logic is encapsulated inside the service,
|
106
|
+
# and the controller knows nothing what the service will do.
|
107
|
+
service = AddFoo.new params.allow(:bar, :baz)
|
108
|
+
# Subscribe the listener for the service's notifications
|
109
|
+
service.subscribe listener, prefix: :on
|
110
|
+
# Run the service
|
111
|
+
service.run
|
112
|
+
# If the service doesn't call any listener method,
|
113
|
+
# then a listener provides some default actions
|
114
|
+
listener.finalize
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
# The listener decorates the controller with methods
|
120
|
+
# to listen the service object's notifications
|
121
|
+
# (see FoosListener#otherwise method below).
|
122
|
+
def listener
|
123
|
+
@listener ||= FoosListener.new self
|
124
|
+
end
|
125
|
+
|
126
|
+
# The class to work out service object notifications
|
127
|
+
# The #render method is delegated to the controller
|
128
|
+
class FoosListener < ServiceObjects::Listener
|
129
|
+
|
130
|
+
# The method to be called when a service publishes
|
131
|
+
# the 'added' notification.
|
132
|
+
def on_added(foo, *, messages)
|
133
|
+
render "created", locals: { foo: foo, messages: messages }
|
134
|
+
end
|
135
|
+
|
136
|
+
# The method to be called when a service publishes
|
137
|
+
# the 'error' notification.
|
138
|
+
def on_error(*, messages)
|
139
|
+
render "error", locals: { messages: messages }
|
140
|
+
end
|
141
|
+
|
142
|
+
# The method is called by the #finalize in case no methods has been called
|
143
|
+
# by the service object.
|
144
|
+
#
|
145
|
+
# This allows to focuse only on a subset of service notifications above.
|
146
|
+
def otherwise
|
147
|
+
render "no_result"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
The service can notify several listeners (controller itself, mailer etc.).
|
154
|
+
|
155
|
+
## Base
|
156
|
+
|
157
|
+
The `ServiceObjects::Base` provides base class for services.
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
require "service_objects"
|
161
|
+
|
162
|
+
class AddFoo < ServiceObjects::Base
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
### Parameters declaration
|
167
|
+
|
168
|
+
Define allowed parameters for objects:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
class AddFoo < ServiceObjects::Base
|
172
|
+
allows_params :bar, :baz
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
Parameters are whitelisted and assigned to `#params` hash (all keys are *symbolized*).
|
177
|
+
|
178
|
+
Attributes are also defined as aliases for corresponding params, so that `#bar` and `#bar=` are equivalent to `#params[:bar]`, `#params[:bar]=`.
|
179
|
+
|
180
|
+
**Note**:
|
181
|
+
The service ignores parameters except for explicitly declared. The client can give relevant data to the service, and leave the latter to figure them out by itself.
|
182
|
+
|
183
|
+
### Validation
|
184
|
+
|
185
|
+
The `ServiceObject::Base` includes [ActiveModel::Validations] with methods `.validates`, `.validate`, `#errors`, `#valid?` and `#invalid?`. Use them to add action context - specific validations.
|
186
|
+
|
187
|
+
The method `#validate!` raises the `ServiceObject::Invalid` if validation fails.
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
class AddFoo < ServiceObjects::Base
|
191
|
+
allows_params :bar, :baz
|
192
|
+
|
193
|
+
validates :bar, presence: true
|
194
|
+
validates :baz, numericality: { greater_than: 1 }, allow_nil: true
|
195
|
+
|
196
|
+
# ...
|
197
|
+
|
198
|
+
def run!
|
199
|
+
# ...
|
200
|
+
validate!
|
201
|
+
# ...
|
202
|
+
end
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
206
|
+
**Note:** You aren't restricted in selecting time for validation. Prepare attributes (either "real" or [virtual]) and run `#validate!` when necessary.
|
207
|
+
|
208
|
+
[ActiveModel::Validations]: http://api.rubyonrails.org/classes/ActiveModel/Validations.html
|
209
|
+
[virtual]: http://railscasts.com/episodes/16-virtual-attributes?view=asciicast
|
210
|
+
|
211
|
+
### Dependencies declaration
|
212
|
+
|
213
|
+
As a rule, services uses each other to keep the code DRY. For example, the service that *adds* a new foo (whatever it means) can use another service to *find* an existing foo.
|
214
|
+
|
215
|
+
To made all that dependencies injectable via [setter injection], define them explicitly:
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
class AddFoo < ServiceObjects::Base
|
219
|
+
# ...
|
220
|
+
|
221
|
+
# Providing the FindFoo is available at the moment AddFoo being defined:
|
222
|
+
depends_on :find_foo, default: FindFoo
|
223
|
+
end
|
224
|
+
```
|
225
|
+
|
226
|
+
Default value can be either assigned or skipped. In the last case the [Null Object] will be assigned by default.
|
227
|
+
|
228
|
+
The class method is public to postpone the default implementation until it is defined:
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
class AddFoo < ServiceObjects::Base
|
232
|
+
# ...
|
233
|
+
depends_on :find_foo
|
234
|
+
end
|
235
|
+
|
236
|
+
# later
|
237
|
+
FindFoo = Class.new
|
238
|
+
AddFoo.depends_on :find_foo, default: FindFoo
|
239
|
+
```
|
240
|
+
|
241
|
+
This provides the instance attribute `#find_foo`. You can inject the dependency to the via setter:
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
service = AddFoo.new bar: "bar", baz: "baz"
|
245
|
+
service.find_foo = GetFoo
|
246
|
+
```
|
247
|
+
|
248
|
+
[setter injection]: http://brandonhilkert.com/blog/a-ruby-refactor-exploring-dependency-injection-options/
|
249
|
+
[Null Object]: https://robots.thoughtbot.com/rails-refactoring-example-introduce-null-object
|
250
|
+
|
251
|
+
### Run method
|
252
|
+
|
253
|
+
It is expected the `run` method to provide all the necessary staff and notify listeners via `#publish` method.
|
254
|
+
|
255
|
+
See [wisper] for details on `#publish` and `#subscribe` methods.
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
class AddFoo < ServiceObjects::Base
|
259
|
+
# ...
|
260
|
+
|
261
|
+
# The method contains the reporting logic only
|
262
|
+
def run
|
263
|
+
run!
|
264
|
+
rescue Found
|
265
|
+
publish :found, @foo, messages
|
266
|
+
rescue Invalid => err
|
267
|
+
publish :error, err.messages
|
268
|
+
else
|
269
|
+
publish :added, @foo, messages
|
270
|
+
ensure
|
271
|
+
self
|
272
|
+
end
|
273
|
+
|
274
|
+
# ...
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
Found = Class.new(RuntimeError) # the internal message
|
279
|
+
|
280
|
+
# Business logic lives here
|
281
|
+
def run!
|
282
|
+
get_foo
|
283
|
+
create_foo
|
284
|
+
end
|
285
|
+
|
286
|
+
def get_foo
|
287
|
+
# ... finds and assigns @foo somehow
|
288
|
+
fail Found if @foo
|
289
|
+
end
|
290
|
+
|
291
|
+
def add_foo
|
292
|
+
# ...
|
293
|
+
end
|
294
|
+
end
|
295
|
+
```
|
296
|
+
|
297
|
+
There are some helper available:
|
298
|
+
* `messages` - an array of collected service messages
|
299
|
+
* `add_message` - adds the new message to the array
|
300
|
+
* `escape` - rescues from `StandardErrors` and re-raises them as `ServiceObject::Invalid` with collection of `#messages`.
|
301
|
+
|
302
|
+
**Note** Following [command-query separation] the `#run` method (being a command) returns `self`.
|
303
|
+
|
304
|
+
[command-query separation]: http://en.wikipedia.org/wiki/Command-query_separation
|
305
|
+
|
306
|
+
### External services
|
307
|
+
|
308
|
+
External services should be used in just the same way as in the controller example.
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
class AddFoo < ServiceObjects::Base
|
312
|
+
depends_on :find_foo, default: FindFoo
|
313
|
+
|
314
|
+
# ...
|
315
|
+
|
316
|
+
def get_foo
|
317
|
+
service = find_foo.new params
|
318
|
+
service.subscribe listener, prefix: :on
|
319
|
+
service.run
|
320
|
+
|
321
|
+
# the method runs #otherwise callback in case
|
322
|
+
# no other notificaton has been received
|
323
|
+
listener.finalize
|
324
|
+
end
|
325
|
+
|
326
|
+
# decorates the service with methods to listen to external service
|
327
|
+
def listener
|
328
|
+
@listener ||= FindListener.new self
|
329
|
+
end
|
330
|
+
|
331
|
+
class FindListener < ServiceObjects::Listener
|
332
|
+
def on_found(foo, *)
|
333
|
+
__getobj__.foo = foo
|
334
|
+
end
|
335
|
+
|
336
|
+
def on_error(*, messages)
|
337
|
+
__getobj__.messages = messages
|
338
|
+
end
|
339
|
+
|
340
|
+
def otherwise
|
341
|
+
add_message "complain", "haven't been informed"
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
```
|
346
|
+
|
347
|
+
Here the `#get_foo` runs the external service and listens to its notifications. Instead of the long syntax above, you can use a shortcut:
|
348
|
+
|
349
|
+
```ruby
|
350
|
+
# ...
|
351
|
+
def get_foo
|
352
|
+
run_service find_foo.new(params), listener, prefix: :on
|
353
|
+
end
|
354
|
+
```
|
355
|
+
|
356
|
+
## Listener
|
357
|
+
|
358
|
+
The listener is a [decorator] that:
|
359
|
+
|
360
|
+
* defines callbacks to listen to service notifications.
|
361
|
+
* delegates all undefined methods to the encapsulated object (available via [__getobj__] instance method).
|
362
|
+
* defines the `#finalize` method to run `#otherwise` callback in case no other methods has been checked (via `#respond_to?` method).
|
363
|
+
|
364
|
+
```ruby
|
365
|
+
class FooListener < ServiceObjects::Listener
|
366
|
+
def on_success(*)
|
367
|
+
"Notified on success"
|
368
|
+
end
|
369
|
+
|
370
|
+
def otherwise
|
371
|
+
"Hasn't been notified"
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
listener = FooListener.new
|
376
|
+
listener.finalize
|
377
|
+
# => "Hasn't been notified"
|
378
|
+
|
379
|
+
listener.respond_to? :on_error
|
380
|
+
# => false
|
381
|
+
listener.finalize
|
382
|
+
# => "Hasn't been notified"
|
383
|
+
|
384
|
+
listener.respond_to? :on_success
|
385
|
+
# => true
|
386
|
+
listener.finalize
|
387
|
+
# => nil
|
388
|
+
```
|
389
|
+
|
390
|
+
[decorator]: http://nithinbekal.com/posts/ruby-decorators/
|
391
|
+
[__getobj__]: http://ruby-doc.org//stdlib-2.1.0/libdoc/delegate/rdoc/SimpleDelegator.html#method-i-__getobj__
|
392
|
+
|
393
|
+
## Message
|
394
|
+
|
395
|
+
The `ServiceObjects::Base#messages` collects messages with text, type and optional priority:
|
396
|
+
|
397
|
+
```ruby
|
398
|
+
message = ServiceObjects::Message.new priority: 0, type: "bar", text: "foo"
|
399
|
+
message.priority # => 0.0
|
400
|
+
message.type # => "info"
|
401
|
+
message.text # => "some text"
|
402
|
+
message.to_h # => { type: "bar", type: "foo" }
|
403
|
+
message.to_json # => "{\"type\":\"bar\",\"text\":"\foo\"}"
|
404
|
+
```
|
405
|
+
|
406
|
+
When a priority hasn't been defined explicitly, it is set to `-1.0` for errors, and to `0.0` otherwise. Messages are sorted by priority, type and text in a "natural" order.
|
407
|
+
|
408
|
+
Use the `#add_message` helper to add a message to the collection:
|
409
|
+
|
410
|
+
```ruby
|
411
|
+
service = ServiceObjects::Base.new
|
412
|
+
service.send :add_message type: "info", text: "some text"
|
413
|
+
service.send :messages
|
414
|
+
# => [<Message type="info" text="some text" priority=0.0>]
|
415
|
+
```
|
416
|
+
|
417
|
+
When a `text:` value is a symbol, it is translated in the scope of current service class:
|
418
|
+
|
419
|
+
```yaml
|
420
|
+
# config/locales/en.yml
|
421
|
+
---
|
422
|
+
en:
|
423
|
+
activemodel:
|
424
|
+
messages:
|
425
|
+
models:
|
426
|
+
foo:
|
427
|
+
excuse: # the type of the message
|
428
|
+
not_informed: "I haven't been informed on the %{subject}"
|
429
|
+
```
|
430
|
+
|
431
|
+
```ruby
|
432
|
+
service.send :add_message type: "excuse", text: :not_informed, subject: "issue"
|
433
|
+
# => [<Message text="I haven't been informed on the issue" ...>]
|
434
|
+
```
|
435
|
+
|
436
|
+
## Compatibility
|
437
|
+
|
438
|
+
Tested under MRI rubies >= 2.1
|
439
|
+
|
440
|
+
RSpec 3.0+ used for testing
|
441
|
+
|
442
|
+
Collection of testing, debugging and code metrics is defined
|
443
|
+
in the [hexx-suit](https://github.com/nepalez/hexx-suit) gem.
|
444
|
+
|
445
|
+
To run tests use `rake test`, to run code metrics use `rake check`. All the metric settings are collected in the `config/metrics` folder.
|
446
|
+
|
447
|
+
## Contributing
|
448
|
+
|
449
|
+
* Fork the project.
|
450
|
+
* Read the [Styleguide](file:config/metrics/STYLEGUIDE).
|
451
|
+
* Make your feature addition or bug fix.
|
452
|
+
* Add tests for it. This is important so I don't break it in a
|
453
|
+
future version unintentionally.
|
454
|
+
* Commit, do not mess with Rakefile or version
|
455
|
+
(if you want to have your own version, that is fine but bump version
|
456
|
+
in a commit by itself I can ignore when I pull)
|
457
|
+
* Send me a pull request. Bonus points for topic branches.
|
458
|
+
|
459
|
+
## License
|
460
|
+
|
461
|
+
See [MIT LICENSE][license]
|