hexx 1.1.1 → 2.0.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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -1
  3. data/README.rdoc +77 -181
  4. data/Rakefile +7 -21
  5. data/lib/hexx/models/base_coercer.rb +36 -0
  6. data/lib/hexx/models.rb +12 -57
  7. data/lib/hexx/service/invalid.rb +48 -0
  8. data/lib/hexx/service/message.rb +57 -0
  9. data/lib/hexx/service/messages.rb +34 -0
  10. data/lib/hexx/service/parameters.rb +45 -0
  11. data/lib/hexx/service/transactions.rb +30 -0
  12. data/lib/hexx/service/validations.rb +18 -0
  13. data/lib/hexx/service.rb +21 -0
  14. data/lib/hexx/version.rb +2 -2
  15. data/lib/hexx.rb +8 -2
  16. data/spec/hexx/models_spec.rb +6 -9
  17. data/spec/hexx/service/invalid_spec.rb +51 -0
  18. data/spec/hexx/service/message_spec.rb +99 -0
  19. data/spec/hexx/service_spec.rb +183 -0
  20. data/spec/{support/initializers → initializers}/coveralls.rb +0 -0
  21. data/spec/{support/initializers → initializers}/focus.rb +0 -0
  22. data/spec/{support/initializers → initializers}/garbage_collection.rb +0 -0
  23. data/spec/{support/initializers → initializers}/i18n.rb +2 -0
  24. data/spec/{support/initializers → initializers}/random_order.rb +0 -0
  25. data/spec/{support/initializers → initializers}/rspec.rb +0 -0
  26. data/spec/spec_helper.rb +3 -5
  27. metadata +31 -117
  28. data/CHANGELOG.rdoc +0 -12
  29. data/bin/hexx +0 -54
  30. data/lib/generators/base.rb +0 -59
  31. data/lib/generators/controller/controller.rb +0 -114
  32. data/lib/generators/controller/templates/controller.erb +0 -10
  33. data/lib/generators/controller/templates/controller_action.erb +0 -7
  34. data/lib/generators/controller/templates/controller_action_spec.erb +0 -21
  35. data/lib/generators/controller/templates/controller_spec.erb +0 -20
  36. data/lib/generators/controller/templates/responder_controller.erb +0 -40
  37. data/lib/generators/controller/templates/responder_controller_spec.erb +0 -65
  38. data/lib/generators/controller/templates/routing_action_spec.erb +0 -13
  39. data/lib/generators/controller/templates/routing_spec.erb +0 -11
  40. data/lib/generators/controller/templates/views_errors.erb +0 -3
  41. data/lib/generators/controller/templates/views_messages.erb +0 -3
  42. data/lib/generators/dependency/dependency.rb +0 -50
  43. data/lib/generators/dependency/templates/dependency_setting.erb +0 -4
  44. data/lib/generators/dependency/templates/dependency_setting_spec.erb +0 -39
  45. data/lib/generators/dependency/templates/initializer.erb +0 -4
  46. data/lib/generators/dependency/templates/initializer_setting.erb +0 -4
  47. data/lib/generators/dependency/templates/module_spec.erb +0 -22
  48. data/lib/generators/domain/domain.rb +0 -24
  49. data/lib/generators/domain/templates/spec.erb +0 -84
  50. data/lib/generators/install/install.rb +0 -133
  51. data/lib/generators/install/templates/bin/rails.erb +0 -11
  52. data/lib/generators/install/templates/config/routes.erb +0 -6
  53. data/lib/generators/install/templates/json_schemas/error.erb +0 -14
  54. data/lib/generators/install/templates/json_schemas/get_errors.erb +0 -18
  55. data/lib/generators/install/templates/json_schemas/success.erb +0 -14
  56. data/lib/generators/install/templates/lib/engine.erb +0 -12
  57. data/lib/generators/install/templates/lib/lib.erb +0 -10
  58. data/lib/generators/install/templates/lib/task.erb +0 -60
  59. data/lib/generators/install/templates/lib/version.erb +0 -4
  60. data/lib/generators/install/templates/matchers/controllers.erb +0 -14
  61. data/lib/generators/install/templates/matchers/json_schema.erb +0 -10
  62. data/lib/generators/install/templates/root/Gemfile.erb +0 -9
  63. data/lib/generators/install/templates/root/Guardfile.erb +0 -14
  64. data/lib/generators/install/templates/root/LICENSE.erb +0 -21
  65. data/lib/generators/install/templates/root/README.erb +0 -68
  66. data/lib/generators/install/templates/root/Rakefile.erb +0 -45
  67. data/lib/generators/install/templates/root/coveralls.erb +0 -1
  68. data/lib/generators/install/templates/root/gemspec.erb +0 -33
  69. data/lib/generators/install/templates/root/gitignore.erb +0 -28
  70. data/lib/generators/install/templates/root/rspec.erb +0 -1
  71. data/lib/generators/install/templates/root/rubocop.erb +0 -65
  72. data/lib/generators/install/templates/root/travis.erb +0 -3
  73. data/lib/generators/install/templates/spec/caching.erb +0 -12
  74. data/lib/generators/install/templates/spec/coveralls.erb +0 -4
  75. data/lib/generators/install/templates/spec/database_cleaner.erb +0 -28
  76. data/lib/generators/install/templates/spec/factory_girl_rails.erb +0 -5
  77. data/lib/generators/install/templates/spec/focus.erb +0 -5
  78. data/lib/generators/install/templates/spec/garbage_collection.erb +0 -11
  79. data/lib/generators/install/templates/spec/i18n.erb +0 -1
  80. data/lib/generators/install/templates/spec/migrations.erb +0 -3
  81. data/lib/generators/install/templates/spec/rails.erb +0 -6
  82. data/lib/generators/install/templates/spec/random_order.erb +0 -4
  83. data/lib/generators/install/templates/spec/rspec.erb +0 -10
  84. data/lib/generators/install/templates/spec/spec_helper.erb +0 -9
  85. data/lib/generators/install/templates/spec/timecop.erb +0 -1
  86. data/lib/generators/request/request.rb +0 -52
  87. data/lib/generators/request/templates/request_spec.erb +0 -66
  88. data/lib/generators/use_case/templates/use_case.erb +0 -54
  89. data/lib/generators/use_case/templates/use_case_spec.erb +0 -74
  90. data/lib/generators/use_case/use_case.rb +0 -31
  91. data/lib/hexx/exceptions/not_found_error.rb +0 -18
  92. data/lib/hexx/exceptions/runtime_error.rb +0 -27
  93. data/lib/hexx/exceptions/use_case_invalid.rb +0 -18
  94. data/lib/hexx/message.rb +0 -20
  95. data/lib/hexx/settings.rb +0 -47
  96. data/lib/hexx/use_case.rb +0 -256
  97. data/spec/hexx/exceptions/not_found_error_spec.rb +0 -21
  98. data/spec/hexx/exceptions/runtime_error_spec.rb +0 -43
  99. data/spec/hexx/exceptions/use_case_invalid_spec.rb +0 -21
  100. data/spec/hexx/message_spec.rb +0 -31
  101. data/spec/hexx/settings_spec.rb +0 -51
  102. data/spec/hexx/use_case_spec.rb +0 -274
  103. data/spec/support/exception_matchers.rb +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d65195d201714af776e125147d354a379dac49f6
4
- data.tar.gz: 8cae2ca8e5922850e7287688dee8997c3f7a0576
3
+ metadata.gz: eaee6ebce4becce87383d4a646ad52738b07094e
4
+ data.tar.gz: 843858ce7dd021925a335bcf78fe36c038605161
5
5
  SHA512:
6
- metadata.gz: 627cba9adf41b1c286efe70c45edcc347a440bc1866453ca8ccb01bee057cbbf1a5e14a3be9c4e6203d1d304b411eb659e1357490db1740173847bf96338731b
7
- data.tar.gz: 65bdbf6099b498c3600f18c2034e49d3d6f4064ec5a6dcd124efc0cba29125e912d113d1f0cff1a569fd67166e55225a75887d2bdc00bcae1109abb0a9cf2861
6
+ metadata.gz: ec5ff9a06822630f60489fdaaea9a5ab20f01b6a130d7bfd9431a225a1f86597462adcb0d6e00ef0fb399bcf6c9c88c626d0b59c51458060ab65160e6ac45c6e
7
+ data.tar.gz: c15800df267a557a08d00945ada1ea176349cf47eee331d6c94282fd8587f295daa82a6d786946cfea191d4e0d01301e8f2d6fd3e4efdbb9ccc76df080dce3f2
data/.rubocop.yml CHANGED
@@ -22,7 +22,13 @@ Style/Documentation:
22
22
  Exclude:
23
23
  - 'spec/**/*'
24
24
 
25
- Style/EmptyLinesAroundBody:
25
+ Style/EmptyLinesAroundClassBody:
26
+ Enabled: false
27
+
28
+ Style/EmptyLinesAroundMethodBody:
29
+ Enabled: false
30
+
31
+ Style/EmptyLinesAroundModuleBody:
26
32
  Enabled: false
27
33
 
28
34
  Style/EmptyLineBetweenDefs:
data/README.rdoc CHANGED
@@ -7,18 +7,18 @@
7
7
  {<img src="http://img.shields.io/coveralls/nepalez/hexx.svg?style=flat" alt="Coverage Status" />}[https://coveralls.io/r/nepalez/hexx]
8
8
  {<img src="http://img.shields.io/badge/license-MIT-blue.svg?style=flat" alt="License" />}[https://github.com/nepalez/hexx/blob/master/LICENSE.rdoc]
9
9
 
10
- A part of framework for rapid Rails API development following the hexagonal
11
- architecture.
10
+ The module provides a base service objects class and some features for the
11
+ domain models (entities).
12
12
 
13
13
  Provides:
14
- * +UseCase+ base class for decoupling a domain business logics from a controller
15
- * Thor generators for scaffolding use cases, controllers and routes.
14
+ * +Service+ base class for decoupling a domain business logics from a controller
15
+ * +Models+ module for extending domain models.
16
16
 
17
17
  == Installation
18
18
 
19
19
  Add this line to your application's Gemfile:
20
20
 
21
- gem "hexx"
21
+ gem "hexx", "~> 2.0"
22
22
 
23
23
  And then execute:
24
24
 
@@ -28,217 +28,113 @@ Or install it yourself as:
28
28
 
29
29
  $ gem install hexx
30
30
 
31
- == Usage
31
+ == A pattern
32
32
 
33
- Controllers are expected to serve as a thin delivery layer between a client
34
- and a use case. They should only orchestrate interactions of use case with its
35
- listeners: the controller itself, mailers and other outer services.
33
+ Service objects decouple business logics from both:
34
+ * Domain _models_ (entities).
35
+ * Delivery mechanism _controllers_ (such as Rails framework).
36
36
 
37
- In a typical case the controller should constist from:
37
+ Following object oriented architecture service objects exploit the
38
+ **Observer** (Listener) pattern, using the
39
+ { Wisper }[https://github.com/krisleech/wisper] gem. A service doesn't
40
+ return their result to caller but notifies its subscribers instead.
38
41
 
39
- * actions serving as <b>outer ports</b> to client, listening to routes and
40
- calling corresponding use cases
41
- * methods servinge as <b>inner ports</b> called by use case notifications
42
- (see wisper[https://github.com/krisleech/wisper.git] for more details).
42
+ Some examples of this pattern implementation are available at the
43
+ { wisper gem wiki }[https://github.com/krisleech/wisper/wiki].
43
44
 
44
- === Use case
45
+ === Service
46
+
47
+ A typical service object is shown below:
45
48
 
46
- # app/my_project/use_cases/get_item.rb
47
49
  require 'hexx'
48
50
 
49
- module MyProject
50
- class GetItem < Hexx::Base
51
+ class GetItem < Hexx::Service
51
52
 
52
- # whitelists parameters taken by an initializer
53
- allow_params :id
53
+ # whitelists parameters and defines corresponding attributes.
54
+ allow_params :name
54
55
 
55
- # runs some validation (on the basis of ActiveModel::Validations)
56
- validate :id, presence: true
56
+ # defines some validation using ActiveModel::Validations helpers.
57
+ validate :name, presence: true
57
58
 
58
- # runs a use case
59
- def run!
60
- validate!
61
- # provide case-specific business rules
62
- end
63
- end
64
- end
59
+ # runs a service
60
+ def run
65
61
 
66
- === Rails controller
67
-
68
- # app/controllers/my_project/rails/api/v1/items_controller.rb
69
- require 'my_project'
70
- require 'rails'
71
-
72
- module MyProject
73
- module Rails
74
- module Api
75
- module V1
76
- class ItemsController < ActionController::Base
77
-
78
- # An action (outer port) of the controller
79
- def show
80
- use_case = GetItem.new params.allow(:id)
81
- # subscribes the controller for listening to the use case
82
- # notifications via the controller's inner ports.
83
- use_case.subscribe self
84
- use_case.run
85
- end
86
-
87
- # Inner port to be called by a use case
88
- def found(item)
89
- render json: { success: true, item: item }, status: 200
90
- end
91
- end
62
+ # runs some
63
+ transaction
64
+ MyModel.save! name: name # name is defined by allow_params helper.
65
+ publish :success # notifies its listeners.
92
66
  end
93
67
  end
94
68
  end
95
69
 
96
- <i>Note the module above is not +my_project+, but +my_project-rails+
97
- (a delivery mechanism for +my_project+).</i>
70
+ Usage of the service (in a Rails controller):
98
71
 
99
- === Models and Entities
100
-
101
- The module also defines module <tt>Hexx::Models</tt> for including to
102
- entities or active_record models. It defines a +validate!+ method:
72
+ class ItemsController < ActionController::Base
103
73
 
104
- class User < ActiveRecord::Base
105
- include Hexx::Models
106
- end
74
+ # Creates an item with given name
75
+ def show
76
+ service = GetItem.new params.allow(:name)
77
+ service.subscribe self, prefix: :on
78
+ service.run
79
+ end
107
80
 
108
- User.new.validate!
109
- # => raises Hexx::RecordInvalid error if the record isn't valid.
81
+ # Publishes a success message
82
+ def success
83
+ render "created"
84
+ end
110
85
 
111
- It also redefines +attr_accessor+, +attr_reader+ and +attr_writer+ helpers
112
- to allow attribute coercion.
86
+ # Publishes an error messages
87
+ def error(messages)
88
+ @messages = messages
89
+ render "error"
90
+ end
113
91
 
114
- require "attributes/string"
92
+ Note the controller knows nothing about the action itself. It responsible only
93
+ for selection of proper service and responding to its results.
115
94
 
116
- class User < ActiveRecord::Base
117
- include Hexx::Models
95
+ Also note any controller action does one thing only. The +show+ action
96
+ are requested by a client. The +on_success+ and +on_error+ are called by
97
+ a service and reports to a client.
118
98
 
119
- attr_accessor :name, type: Attributes::String
120
- end
99
+ === Models and Entities
121
100
 
122
- This coerces the name with an Attributes::String class object as if its
123
- setter and getter were:
101
+ The module also defines module <tt>Hexx::Models</tt> to extend domain models
102
+ (entities). This allows coercion of model attributes with +attr_coerced+ helper:
124
103
 
125
- def name=(value)
126
- super Attributes::String.new(value)
104
+ class User
105
+ extend Hexx::Models
106
+ attr_coerced :name, type: ActiveSupport::Multibyte::Chars
127
107
  end
128
108
 
129
- def name
130
- Attributes::String.new(super)
131
- end
132
-
133
- == Generators
134
-
135
- The module contains generators, written on the basis of the Thor, namely:
136
-
137
- * request specs (acceptance tests);
138
- * controllers with their specs (unit tests)
139
- * routing specs (unit tests);
140
- * domain specs (acceptance tests for use cases and models);
141
- * use_cases with their specs (unit tests);
142
-
143
- The scaffolded specs contains examples for writing tests rapidly following
144
- some conventions.
145
-
146
- === Request spec (acceptance test for the API)
147
-
148
- Request specs used to test API from a user view - by sending requests
149
- and checkign their results. Their should be written on the basis of
150
- API blueprint. Their shouldn't check the internal states of application, such
151
- as models, use_cases etc.
152
-
153
- Task is called with 1 argument, that consists from two parts divided by colon:
154
-
155
- * request type (get, post etc.)
156
- * requests relative address
157
-
158
- Example:
159
-
160
- $ hexx request get:items/{id}
161
- # => spec/requests/my_module/api/v1/get_items_id_spec.rb
162
-
163
- API namespace (those /my_module/api/v1) will be added by default.
164
-
165
- === Controller action with a corresponding route
109
+ user = User.new name: "Ivan"
110
+
111
+ user.name
112
+ # => "Ivan"
113
+
114
+ user.name.class
115
+ # => ActiveSupport::Multibyte::Chars
166
116
 
167
- A scaffolder provides tests for both a controller and its router. It
168
- also scaffolds a controller action itself. The router action should be
169
- added manually.
117
+ The method defines (or redefines) both attribute getter and setter. You can use
118
+ it to transform values by default:
170
119
 
171
- Except for the requests specs (above) the controller spec is an unit test
172
- that checks outer and inner controller action in isolation from both
173
- router and a use case. It only checks that outer actions calls proper use cases,
174
- and inner actions returns proper responce to a user.
175
-
176
- Task is called with 3 argumets:
177
-
178
- * name of the controller
179
- * request type and action name, divided by colon
180
- * name of the use case for the action
181
-
182
- Example:
183
-
184
- $ hexx controller items get:show get_item
185
- # => spec/routing/my_module/api/v1/items_routing_spec.rb
186
- # => spec/controllers/my_module/api/v1/items_controller_spec.rb
187
- # => app/controllers/my_module/api/v1/items_controller.rb
188
-
189
- ==== Acceptance spec for the domain
190
-
191
- The domain spec checks the inner part of the domain (use_cases and models) as
192
- a whole. Because the domain interacts with outer word through the use cases
193
- only, the acceptance test should call use_cases and check its results.
194
- It should not check inner states of the domain (models, entities, repositories,
195
- databases etc.)
196
-
197
- Task is called with 1 argument:
198
-
199
- * name of the use_case.
200
-
201
- Example:
202
-
203
- $ hexx domain get_item
204
- # => spec/my_module/domain/get_item_spec.rb
205
-
206
- ==== UseCase
207
-
208
- The unit tests for checking a use_case in isolation.
209
-
210
- Task is called with 1 argument:
211
-
212
- * name of the use_case.
213
-
214
- Example:
215
-
216
- $ hexx use_case get_item
217
- # => spec/my_module/use_cases/get_item_spec.rb
218
- # => app/my_module/use_cases/get_item.rb
219
-
220
- ==== Dependency
221
-
222
- Scaffolds the external dependency setting with a corresponding specs.
223
-
224
- Task is called with 1 argument:
225
-
226
- * name of the dependency.
120
+ class StrippedString < String
121
+ def initialize(value)
122
+ super value.strip
123
+ end
124
+ end
227
125
 
228
- Example:
126
+ class User
127
+ extend Hexx::Models
128
+ attr_coerced :name, type: StrippedString
129
+ end
229
130
 
230
- $hexx dependency some_module
231
- # => spec/my_module_spec.rb
232
- # => lib/my_module.rb
131
+ user = User.name " Ivan "
132
+ user.name # => "Ivan"
233
133
 
234
134
  == Relevant Links
235
135
 
236
136
  1:: Matt Wynne's talk {Hexagonal Rails}[http://www.confreaks.com/videos/977-goruco2012-hexagonal-rails]
237
137
 
238
- == Changelog
239
-
240
- To review changes between versions see the {CHANGELOG}[CHANGELOG.rdoc].
241
-
242
138
  == License
243
139
 
244
140
  The project is distributed under the {MIT LICENSE}[LICENSE.rdoc].
data/Rakefile CHANGED
@@ -4,34 +4,20 @@ rescue LoadError
4
4
  puts "You must `gem install bundler` and `bundle install` to run rake tasks"
5
5
  end
6
6
 
7
- begin
8
- require "rdoc/task"
9
- rescue LoadError
10
- require "rdoc/rdoc"
11
- require "rake/rdoctask"
12
- RDoc::Task = Rake::RDocTask
13
- end
14
-
15
- RDoc::Task.new(:rdoc) do |rdoc|
16
- rdoc.rdoc_dir = "rdoc"
17
- rdoc.title = "Hexx"
18
- rdoc.options << "--line-numbers"
19
- rdoc.rdoc_files.include("README.rdoc")
20
- rdoc.rdoc_files.include("lib/**/*.rb")
21
- end
22
-
23
7
  Bundler::GemHelper.install_tasks
24
8
 
25
9
  require "bundler/gem_tasks"
26
10
  require "rspec/core/rake_task"
27
11
 
28
12
  RSpec::Core::RakeTask.new(:spec)
13
+
29
14
  task :default do
30
- sh "bundle exec rspec spec"
15
+ system "bundle exec rspec spec"
31
16
  end
32
17
 
33
- task full: [:default] do
34
- sh "rubocop"
35
- sh "metric_fu"
36
- sh "inch"
18
+ task :check do
19
+ system "coveralls report"
20
+ system "rubocop"
21
+ system "inch --pedantic"
22
+ system "metric_fu"
37
23
  end
@@ -0,0 +1,36 @@
1
+ module Hexx
2
+ module Models
3
+
4
+ # Coerces PORO attribute's getter and setter with given type.
5
+ class BaseCoercer < Struct.new(:klass, :name, :type)
6
+
7
+ # Coerces class attribute's getter and setter.
8
+ def coerce
9
+ coerce_setter
10
+ coerce_getter
11
+ end
12
+
13
+ private
14
+
15
+ def coerce_setter
16
+ klass.class_eval(
17
+ "def #{ name };
18
+ #{ type_name }.new(@#{ name });
19
+ end"
20
+ )
21
+ end
22
+
23
+ def coerce_getter
24
+ klass.class_eval(
25
+ "def #{ name }=(value);
26
+ @#{ name } = #{ type_name }.new(value);
27
+ end"
28
+ )
29
+ end
30
+
31
+ def type_name
32
+ @type_name ||= type.name
33
+ end
34
+ end
35
+ end
36
+ end
data/lib/hexx/models.rb CHANGED
@@ -1,74 +1,29 @@
1
- require "active_support"
1
+ require "hexx/models/base_coercer"
2
2
 
3
3
  module Hexx
4
4
 
5
- # Contains +attr_coerced+ public class method.
5
+ # Declares the +.attr_coerced+ private class method.
6
6
  #
7
- # Include the module into the Rails model:
7
+ # @example
8
8
  #
9
9
  # require "hexx"
10
10
  # require_relative "attributes/string"
11
11
  #
12
- # class User < ActiveRecord::Base
13
- # include Hexx::Models
14
- #
15
- # # coerces attributes
16
- # attr_coerced :name, login, type: Attributes::String
12
+ # class User
13
+ # extend Hexx::Models
14
+ # attr_coerced :name, type: ActiveSupport::Multibyte::Chars
17
15
  # end
18
16
  #
19
17
  module Models
20
- extend ActiveSupport::Concern
21
-
22
- # Model class helpers for attributes coercion.
23
- module ClassMethods
24
-
25
- def attr_coerced(*names, type:)
26
- names.each { |name| _attr_coerced(name, type) }
27
- end
28
-
29
- private
30
18
 
31
- def _attr_coerced(name, type)
32
- if ancestors.map(&:name).include? "ActiveRecord::Base"
33
- coerce_activerecord_reader name, type
34
- coerce_activerecord_writer name, type
35
- else
36
- coerce_simple_reader name, type
37
- coerce_simple_writer name, type
38
- end
39
- end
19
+ private
40
20
 
41
- def coerce_simple_reader(name, type)
42
- class_eval(
43
- "def #{ name };
44
- #{ type.name }.new(@#{ name });
45
- end"
46
- )
47
- end
48
-
49
- def coerce_simple_writer(name, type)
50
- class_eval(
51
- "def #{ name }=(value);
52
- @#{ name } = #{ type.name }.new(value);
53
- end"
54
- )
55
- end
56
-
57
- def coerce_activerecord_reader(name, type)
58
- class_eval(
59
- "def #{ name };
60
- #{ type.name }.new read_attribute(:#{ name });
61
- end"
62
- )
63
- end
21
+ def attr_coerced(*names, type:)
22
+ names.each { |name| coercer.new(self, name, type).coerce }
23
+ end
64
24
 
65
- def coerce_activerecord_writer(name, type)
66
- class_eval(
67
- "def #{ name }=(value);
68
- write_attribute :#{ name }, #{ type.name }.new(value);
69
- end"
70
- )
71
- end
25
+ def coercer
26
+ @coercer ||= BaseCoercer
72
27
  end
73
28
  end
74
29
  end
@@ -0,0 +1,48 @@
1
+ module Hexx
2
+ class Service
3
+
4
+ # Exception to be raised by invalid services.
5
+ class Invalid < ::RuntimeError
6
+
7
+ # An invalid service object with error messages.
8
+ attr_reader :service
9
+
10
+ # Initializes the exception.
11
+ #
12
+ # @example
13
+ # fail Hexx::Service::Invalid.new(service)
14
+ #
15
+ # Params:
16
+ # +service+:: a Hexx::Service object containing error messages.
17
+ #
18
+ def initialize(service)
19
+ @service = service
20
+ fail ArgumentError unless self.service.is_a? Hexx::Service
21
+ end
22
+
23
+ # Returns a default text message for the exception.
24
+ #
25
+ # @example
26
+ # error = Hexx::Service::Invalid.new service
27
+ # error.message # => "Service invalid: #<Hexx::Service... >"
28
+ #
29
+ def message
30
+ "Service invalid: #{ service.inspect }"
31
+ end
32
+
33
+ # Returns a list of <tt>Hexx::Service::Message</tt> messages of
34
+ # +error+ type.
35
+ #
36
+ # @example
37
+ # error = Hexx::Service::Invalid.new service
38
+ # error.messages # => [#<Hexx::Message... >, ...]
39
+ # error.messages.first.type # => "error"
40
+ #
41
+ def messages
42
+ service.errors.values.flatten.map do |text|
43
+ Message.new type: "error", text: text
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,57 @@
1
+ module Hexx
2
+ class Service
3
+
4
+ # A message to be returned by services.
5
+ class Message
6
+ include Comparable
7
+
8
+ # A stringified type and a text of the message.
9
+ attr_reader :type, :text
10
+
11
+ # Initializes the message.
12
+ #
13
+ # @example
14
+ # Message.new type: "success", text: "Object created!"
15
+ #
16
+ # Params:
17
+ # <tt>:type</tt>:: string/symbolic message type (:error, :info, :success).
18
+ # <tt>:text</tt>:: message text.
19
+ #
20
+ def initialize(type:, text:)
21
+ @type, @text = type.to_s, text.to_s
22
+ end
23
+
24
+ # Extracts error messages from ActiveRecord or ActiveModel objects.
25
+ #
26
+ # @example
27
+ # Message.from(object) # => [#<Hexx::Service::Message ...>]
28
+ #
29
+ # Params:
30
+ # +object+:: an object to extract messages from.
31
+ #
32
+ def self.from(object)
33
+ object.errors.values.flatten.map do |text|
34
+ new type: "error", text: text
35
+ end
36
+ end
37
+
38
+ # Compares two messages by type and text.
39
+ def ==(other)
40
+ return false unless other.is_a? self.class
41
+ [type, text] == [other.type, other.text]
42
+ end
43
+
44
+ # Orders messages by type and text.
45
+ #
46
+ # @example
47
+ # ab = Message.new(type: "a", text: "b")
48
+ # ba = Message.new(type: "b", text: "a")
49
+ # ab < ba # => true
50
+ #
51
+ def <=>(other)
52
+ fail ArgumentError unless other.is_a? self.class
53
+ [type, text] <=> [other.type, other.text]
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,34 @@
1
+ require "hexx/service/message"
2
+
3
+ module Hexx
4
+ class Service
5
+
6
+ # Declares methods for creation messages by a service:
7
+ #
8
+ # <tt>t(text, options = {})</tt>:: translates text in the service's scope.
9
+ # <tt>messages</tt>:: returns an array of service's messages.
10
+ # <tt>add_message(type, text, options = {})</tt>:: adds a message to array.
11
+ #
12
+ module Messages
13
+
14
+ private
15
+
16
+ # Translates given key in current service's scope.
17
+ def t(key, options = {})
18
+ return key unless key.is_a? Symbol
19
+ scope = %w(activemodel messages models) << self.class.name.underscore
20
+ I18n.t key, options.merge(scope: scope)
21
+ end
22
+
23
+ # Returns the array of service's messages.
24
+ def messages
25
+ @messages ||= []
26
+ end
27
+
28
+ # Adds the translated message to the messages array.
29
+ def add_message(type, text, options = {})
30
+ messages << Message.new(type: type, text: t(text, options))
31
+ end
32
+ end
33
+ end
34
+ end