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.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -1
- data/README.rdoc +77 -181
- data/Rakefile +7 -21
- data/lib/hexx/models/base_coercer.rb +36 -0
- data/lib/hexx/models.rb +12 -57
- data/lib/hexx/service/invalid.rb +48 -0
- data/lib/hexx/service/message.rb +57 -0
- data/lib/hexx/service/messages.rb +34 -0
- data/lib/hexx/service/parameters.rb +45 -0
- data/lib/hexx/service/transactions.rb +30 -0
- data/lib/hexx/service/validations.rb +18 -0
- data/lib/hexx/service.rb +21 -0
- data/lib/hexx/version.rb +2 -2
- data/lib/hexx.rb +8 -2
- data/spec/hexx/models_spec.rb +6 -9
- data/spec/hexx/service/invalid_spec.rb +51 -0
- data/spec/hexx/service/message_spec.rb +99 -0
- data/spec/hexx/service_spec.rb +183 -0
- data/spec/{support/initializers → initializers}/coveralls.rb +0 -0
- data/spec/{support/initializers → initializers}/focus.rb +0 -0
- data/spec/{support/initializers → initializers}/garbage_collection.rb +0 -0
- data/spec/{support/initializers → initializers}/i18n.rb +2 -0
- data/spec/{support/initializers → initializers}/random_order.rb +0 -0
- data/spec/{support/initializers → initializers}/rspec.rb +0 -0
- data/spec/spec_helper.rb +3 -5
- metadata +31 -117
- data/CHANGELOG.rdoc +0 -12
- data/bin/hexx +0 -54
- data/lib/generators/base.rb +0 -59
- data/lib/generators/controller/controller.rb +0 -114
- data/lib/generators/controller/templates/controller.erb +0 -10
- data/lib/generators/controller/templates/controller_action.erb +0 -7
- data/lib/generators/controller/templates/controller_action_spec.erb +0 -21
- data/lib/generators/controller/templates/controller_spec.erb +0 -20
- data/lib/generators/controller/templates/responder_controller.erb +0 -40
- data/lib/generators/controller/templates/responder_controller_spec.erb +0 -65
- data/lib/generators/controller/templates/routing_action_spec.erb +0 -13
- data/lib/generators/controller/templates/routing_spec.erb +0 -11
- data/lib/generators/controller/templates/views_errors.erb +0 -3
- data/lib/generators/controller/templates/views_messages.erb +0 -3
- data/lib/generators/dependency/dependency.rb +0 -50
- data/lib/generators/dependency/templates/dependency_setting.erb +0 -4
- data/lib/generators/dependency/templates/dependency_setting_spec.erb +0 -39
- data/lib/generators/dependency/templates/initializer.erb +0 -4
- data/lib/generators/dependency/templates/initializer_setting.erb +0 -4
- data/lib/generators/dependency/templates/module_spec.erb +0 -22
- data/lib/generators/domain/domain.rb +0 -24
- data/lib/generators/domain/templates/spec.erb +0 -84
- data/lib/generators/install/install.rb +0 -133
- data/lib/generators/install/templates/bin/rails.erb +0 -11
- data/lib/generators/install/templates/config/routes.erb +0 -6
- data/lib/generators/install/templates/json_schemas/error.erb +0 -14
- data/lib/generators/install/templates/json_schemas/get_errors.erb +0 -18
- data/lib/generators/install/templates/json_schemas/success.erb +0 -14
- data/lib/generators/install/templates/lib/engine.erb +0 -12
- data/lib/generators/install/templates/lib/lib.erb +0 -10
- data/lib/generators/install/templates/lib/task.erb +0 -60
- data/lib/generators/install/templates/lib/version.erb +0 -4
- data/lib/generators/install/templates/matchers/controllers.erb +0 -14
- data/lib/generators/install/templates/matchers/json_schema.erb +0 -10
- data/lib/generators/install/templates/root/Gemfile.erb +0 -9
- data/lib/generators/install/templates/root/Guardfile.erb +0 -14
- data/lib/generators/install/templates/root/LICENSE.erb +0 -21
- data/lib/generators/install/templates/root/README.erb +0 -68
- data/lib/generators/install/templates/root/Rakefile.erb +0 -45
- data/lib/generators/install/templates/root/coveralls.erb +0 -1
- data/lib/generators/install/templates/root/gemspec.erb +0 -33
- data/lib/generators/install/templates/root/gitignore.erb +0 -28
- data/lib/generators/install/templates/root/rspec.erb +0 -1
- data/lib/generators/install/templates/root/rubocop.erb +0 -65
- data/lib/generators/install/templates/root/travis.erb +0 -3
- data/lib/generators/install/templates/spec/caching.erb +0 -12
- data/lib/generators/install/templates/spec/coveralls.erb +0 -4
- data/lib/generators/install/templates/spec/database_cleaner.erb +0 -28
- data/lib/generators/install/templates/spec/factory_girl_rails.erb +0 -5
- data/lib/generators/install/templates/spec/focus.erb +0 -5
- data/lib/generators/install/templates/spec/garbage_collection.erb +0 -11
- data/lib/generators/install/templates/spec/i18n.erb +0 -1
- data/lib/generators/install/templates/spec/migrations.erb +0 -3
- data/lib/generators/install/templates/spec/rails.erb +0 -6
- data/lib/generators/install/templates/spec/random_order.erb +0 -4
- data/lib/generators/install/templates/spec/rspec.erb +0 -10
- data/lib/generators/install/templates/spec/spec_helper.erb +0 -9
- data/lib/generators/install/templates/spec/timecop.erb +0 -1
- data/lib/generators/request/request.rb +0 -52
- data/lib/generators/request/templates/request_spec.erb +0 -66
- data/lib/generators/use_case/templates/use_case.erb +0 -54
- data/lib/generators/use_case/templates/use_case_spec.erb +0 -74
- data/lib/generators/use_case/use_case.rb +0 -31
- data/lib/hexx/exceptions/not_found_error.rb +0 -18
- data/lib/hexx/exceptions/runtime_error.rb +0 -27
- data/lib/hexx/exceptions/use_case_invalid.rb +0 -18
- data/lib/hexx/message.rb +0 -20
- data/lib/hexx/settings.rb +0 -47
- data/lib/hexx/use_case.rb +0 -256
- data/spec/hexx/exceptions/not_found_error_spec.rb +0 -21
- data/spec/hexx/exceptions/runtime_error_spec.rb +0 -43
- data/spec/hexx/exceptions/use_case_invalid_spec.rb +0 -21
- data/spec/hexx/message_spec.rb +0 -31
- data/spec/hexx/settings_spec.rb +0 -51
- data/spec/hexx/use_case_spec.rb +0 -274
- data/spec/support/exception_matchers.rb +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eaee6ebce4becce87383d4a646ad52738b07094e
|
|
4
|
+
data.tar.gz: 843858ce7dd021925a335bcf78fe36c038605161
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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/
|
|
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
|
-
|
|
11
|
-
|
|
10
|
+
The module provides a base service objects class and some features for the
|
|
11
|
+
domain models (entities).
|
|
12
12
|
|
|
13
13
|
Provides:
|
|
14
|
-
* +
|
|
15
|
-
*
|
|
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
|
-
==
|
|
31
|
+
== A pattern
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
Service objects decouple business logics from both:
|
|
34
|
+
* Domain _models_ (entities).
|
|
35
|
+
* Delivery mechanism _controllers_ (such as Rails framework).
|
|
36
36
|
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
===
|
|
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
|
-
|
|
50
|
-
class GetItem < Hexx::Base
|
|
51
|
+
class GetItem < Hexx::Service
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
# whitelists parameters and defines corresponding attributes.
|
|
54
|
+
allow_params :name
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
# defines some validation using ActiveModel::Validations helpers.
|
|
57
|
+
validate :name, presence: true
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
validate!
|
|
61
|
-
# provide case-specific business rules
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
59
|
+
# runs a service
|
|
60
|
+
def run
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
97
|
-
(a delivery mechanism for +my_project+).</i>
|
|
70
|
+
Usage of the service (in a Rails controller):
|
|
98
71
|
|
|
99
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
109
|
-
|
|
81
|
+
# Publishes a success message
|
|
82
|
+
def success
|
|
83
|
+
render "created"
|
|
84
|
+
end
|
|
110
85
|
|
|
111
|
-
|
|
112
|
-
|
|
86
|
+
# Publishes an error messages
|
|
87
|
+
def error(messages)
|
|
88
|
+
@messages = messages
|
|
89
|
+
render "error"
|
|
90
|
+
end
|
|
113
91
|
|
|
114
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
120
|
-
end
|
|
99
|
+
=== Models and Entities
|
|
121
100
|
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
126
|
-
|
|
104
|
+
class User
|
|
105
|
+
extend Hexx::Models
|
|
106
|
+
attr_coerced :name, type: ActiveSupport::Multibyte::Chars
|
|
127
107
|
end
|
|
128
108
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
126
|
+
class User
|
|
127
|
+
extend Hexx::Models
|
|
128
|
+
attr_coerced :name, type: StrippedString
|
|
129
|
+
end
|
|
229
130
|
|
|
230
|
-
|
|
231
|
-
# =>
|
|
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
|
-
|
|
15
|
+
system "bundle exec rspec spec"
|
|
31
16
|
end
|
|
32
17
|
|
|
33
|
-
task
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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 "
|
|
1
|
+
require "hexx/models/base_coercer"
|
|
2
2
|
|
|
3
3
|
module Hexx
|
|
4
4
|
|
|
5
|
-
#
|
|
5
|
+
# Declares the +.attr_coerced+ private class method.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
7
|
+
# @example
|
|
8
8
|
#
|
|
9
9
|
# require "hexx"
|
|
10
10
|
# require_relative "attributes/string"
|
|
11
11
|
#
|
|
12
|
-
# class User
|
|
13
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|