evnt 2.1.0 → 2.1.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 +4 -4
- data/MIT-LICENSE +20 -0
- data/README.md +319 -0
- data/lib/evnt.rb +20 -0
- data/lib/evnt/command.rb +178 -0
- data/lib/evnt/event.rb +151 -0
- data/lib/evnt/handler.rb +90 -0
- data/lib/evnt/validator.rb +206 -0
- data/lib/evnt/version.rb +10 -0
- metadata +18 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b87af903c38d1e3c2e654750badc4eac551177f0
|
4
|
+
data.tar.gz: 435ae2a3ba9288673f3773a7cdec993117eba6a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77fa759d80d7357d7f597fe1d1e8987f3f0155af567bbb39eb2ac1fd4514b2a247e7e27cb2d41dc6e2c9135ee7bf16221d4ec388247fd487098d07e9e8e38a32
|
7
|
+
data.tar.gz: 69125321d1a8de3c3a48a9fe92539c9fb632f62f562d5a88dc62ba72d61380682ab213960f5b83f7965b835e1e36e2011334757747eb42641331bc538a6d51c5
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2017 Ideo SRL
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,319 @@
|
|
1
|
+
# Evnt
|
2
|
+
|
3
|
+
CQRS and Event Driven Development architecture for Ruby projects.
|
4
|
+
|
5
|
+
- [Installation](#installation)
|
6
|
+
- [Structure](#structure)
|
7
|
+
- [Command](#command)
|
8
|
+
- [Event](#event)
|
9
|
+
- [Handler](#handler)
|
10
|
+
- [Rails integration](#rails-integration)
|
11
|
+
- [Development](#development)
|
12
|
+
|
13
|
+
Full documentation here: https://ideonetwork.github.io/ruby-evnt/
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
To use the gem you need to add it on your Gemfile
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'evnt'
|
21
|
+
```
|
22
|
+
|
23
|
+
## Structure
|
24
|
+
|
25
|
+
Evnt is developed to be used over all kinds of projects and frameworks (like Ruby on Rails or Sinatra), so it contains only three types of entities:
|
26
|
+
|
27
|
+
- Command
|
28
|
+
- Event
|
29
|
+
- Handler
|
30
|
+
|
31
|
+
### Command
|
32
|
+
|
33
|
+
Commands are used to run single tasks on the system. It's like a controller on an MVC architecture without the communication with the client.
|
34
|
+
|
35
|
+
Every command has three steps to execute:
|
36
|
+
|
37
|
+
- The params validation which validates the parameters used to run the command.
|
38
|
+
- The logic validation which checks the command can be executed in compliance with the system rules.
|
39
|
+
- The event intialization which initializes an event object used to save the command.
|
40
|
+
|
41
|
+
An example of command should be:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class CreateOrderCommand < Evnt::Command
|
45
|
+
|
46
|
+
to_validate_params do
|
47
|
+
# check params presence
|
48
|
+
err 'User should be present' if params[:user_id].blank?
|
49
|
+
err 'Product should be present' if params[:product_id].blank?
|
50
|
+
err 'Quantity should be present' if params[:quantity].blank?
|
51
|
+
end
|
52
|
+
|
53
|
+
to_validate_logic do
|
54
|
+
@user = User.find_by(id: params[:user_id])
|
55
|
+
@product = Product.find_by(id: params[:product_id])
|
56
|
+
|
57
|
+
# check user exist
|
58
|
+
unless @user
|
59
|
+
err 'The user does not exist'
|
60
|
+
break
|
61
|
+
end
|
62
|
+
|
63
|
+
# check product exist
|
64
|
+
unless @product
|
65
|
+
err 'The product does not exist'
|
66
|
+
break
|
67
|
+
end
|
68
|
+
|
69
|
+
# check quantity exist for the product
|
70
|
+
err 'The requested quantity is not available' if @product.quantity < params[:quantity]
|
71
|
+
|
72
|
+
# check user has money to buy the product
|
73
|
+
err 'You do not have enought money' if @user.money < @product.price * params[:quantity]
|
74
|
+
end
|
75
|
+
|
76
|
+
to_initialize_events do
|
77
|
+
# generate order id
|
78
|
+
order_id = SecureRandom.uuid
|
79
|
+
|
80
|
+
# initialize event
|
81
|
+
begin
|
82
|
+
CreateOrderEvent.new(
|
83
|
+
order_id: order_id,
|
84
|
+
user_id: @user.id,
|
85
|
+
product_id: @product.id,
|
86
|
+
quantity: params[:quantity],
|
87
|
+
_user: @user,
|
88
|
+
_product: @product
|
89
|
+
)
|
90
|
+
rescue
|
91
|
+
err 'Sorry, there was an error', code: 500
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
An example of command usage should be:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
command = CreateOrderCommand.new(
|
102
|
+
user_id: 128,
|
103
|
+
product_id: 534,
|
104
|
+
quantity: 10
|
105
|
+
)
|
106
|
+
|
107
|
+
if command.completed?
|
108
|
+
puts 'Command completed'
|
109
|
+
puts command.params # -> { user_id: 128, product_id: 534, quantity: 10 }
|
110
|
+
else
|
111
|
+
puts command.errors # array of hashes with a { message, code } structure
|
112
|
+
puts command.error_messages # array of error messages
|
113
|
+
puts command.error_codes # array of error codes
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
It's also possible to use err method inside the command to raise an exception with the option **exception: true**. An example of usage should be:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
begin
|
121
|
+
command = CreateOrderCommand.new(
|
122
|
+
user_id: 128,
|
123
|
+
product_id: 534,
|
124
|
+
quantity: 10,
|
125
|
+
_options: {
|
126
|
+
excption: true
|
127
|
+
}
|
128
|
+
)
|
129
|
+
rescue => e
|
130
|
+
puts e
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
Some validations are similar for every command (like presence or paramter type), so you can also use general validations instead of **to_validate_params** block. An example of general validations should be:
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
|
138
|
+
class CreateOrderCommand < Evnt::Command
|
139
|
+
|
140
|
+
validates :user_id, type: :integer, presence: true
|
141
|
+
validates :product_id, type: :integer, presence: true
|
142
|
+
validates :quantity, type: :integer, presence: true
|
143
|
+
|
144
|
+
# ...
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
```
|
149
|
+
|
150
|
+
### Event
|
151
|
+
|
152
|
+
Events are used to save on a persistent data structure what happends on the system.
|
153
|
+
|
154
|
+
Every event has three informations:
|
155
|
+
|
156
|
+
- The **name** is an unique identifier of the event.
|
157
|
+
- The **attributes** are the list of attributes required from the event to be saved.
|
158
|
+
- The **handlers** are a list of handler objects which will be notified when the event is completed.
|
159
|
+
|
160
|
+
Every event has also a single function used to write the event information on the data structure.
|
161
|
+
|
162
|
+
An example of event should be:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
class CreateOrderEvent < Evnt::Event
|
166
|
+
|
167
|
+
name_is :create_order
|
168
|
+
|
169
|
+
attributes_are :order_id, :user_id, :product_id, :quantity
|
170
|
+
|
171
|
+
handlers_are [
|
172
|
+
ProductHandler.new,
|
173
|
+
UserNotifierHandler.new
|
174
|
+
]
|
175
|
+
|
176
|
+
to_write_event do
|
177
|
+
# save event on database
|
178
|
+
Event.create(name: name, payload: payload)
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
```
|
183
|
+
|
184
|
+
An example of event usage should be:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
event = CreateOrderEvent.new(
|
188
|
+
order_id: order_id,
|
189
|
+
user_id: @user.id,
|
190
|
+
product_id: @product.id,
|
191
|
+
quantity: params[:quantity]
|
192
|
+
)
|
193
|
+
|
194
|
+
puts event.name # -> :create_order
|
195
|
+
puts event.attributes # -> [:order_id, :user_id, :product_id, :quantity]
|
196
|
+
puts event.payload # -> { order_id: 1, user_id: 128, product_id: 534, quantity: 10, evnt: { timestamp: 2017010101, name: 'create_order' } }
|
197
|
+
```
|
198
|
+
|
199
|
+
The event payload should contain all event attributes and a reserver attributes "evnt" used to store the event timestamp and the event name.
|
200
|
+
|
201
|
+
It's also possible to give datas to the event without save them on the event payload, to do this you shuld only use a key with "_" as first character. An example should be:
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
event = CreateOrderEvent.new(
|
205
|
+
order_id: order_id,
|
206
|
+
user_id: @user.id,
|
207
|
+
product_id: @product.id,
|
208
|
+
quantity: params[:quantity],
|
209
|
+
_total_price: params[:quantity] * @product.price
|
210
|
+
)
|
211
|
+
|
212
|
+
puts event.payload # -> { order_id: 1, user_id: 128, product_id: 534, quantity: 10, evnt: { timestamp: 2017010101, name: 'create_order' } }
|
213
|
+
puts event.extras # -> { _total_price: 50 }
|
214
|
+
```
|
215
|
+
|
216
|
+
After the execution of the **to_write_event** block the event object should notify all its handlers.
|
217
|
+
|
218
|
+
Sometimes you need to reload an old event to notify handlers to re-build queries from events. To initialize a new event object with the payload of an old event you can pass the old event payload to the event constructor:
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
events = Event.where(name: 'create_order')
|
222
|
+
reloaded_event = CreateOrderEvent.new(events.sample.payload)
|
223
|
+
|
224
|
+
puts reloaded_event.reloaded? # -> true
|
225
|
+
```
|
226
|
+
|
227
|
+
### Handler
|
228
|
+
|
229
|
+
Handlers are used to listen one or more events and run tasks after their execution.
|
230
|
+
|
231
|
+
Every handler event management has two steps to execute:
|
232
|
+
|
233
|
+
- The queries update which updates temporary data structures used to read datas.
|
234
|
+
- The manage event code which run other tasks like mailers, parallel executions ecc.
|
235
|
+
|
236
|
+
An example of handler shuould be:
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
class ProductHandler < Evnt::Handler
|
240
|
+
|
241
|
+
on :create_order do
|
242
|
+
|
243
|
+
to_update_queries do
|
244
|
+
# update product quantity
|
245
|
+
product = event.extras[:_product]
|
246
|
+
product.update(quantity: product.quantity - event.payload[:quantity])
|
247
|
+
end
|
248
|
+
|
249
|
+
to_manage_event do
|
250
|
+
# this block is called only for not reloaded events
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
```
|
257
|
+
|
258
|
+
The execution of **to_update_queries** block runs after every events initialization.
|
259
|
+
The execution of **to_manage_event** block runs only for not reloaded events initialization.
|
260
|
+
|
261
|
+
Sometimes you need to run some code to manage only reloaded events. To run code only for reloaded events you can use the **to_manage_reloaded_event** block:
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
class ProductHandler < Evnt::Handler
|
265
|
+
|
266
|
+
on :create_order do
|
267
|
+
|
268
|
+
to_manage_reloaded_event do
|
269
|
+
# this block is called only for reloaded events
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
275
|
+
```
|
276
|
+
|
277
|
+
## Rails integration
|
278
|
+
|
279
|
+
Evnt can be used with Ruby on Rails to extends the MVC pattern.
|
280
|
+
|
281
|
+
To use the gem with Rails you need to create three folders inside the ./app project's path:
|
282
|
+
|
283
|
+
- **./app/commands**
|
284
|
+
- **./app/events**
|
285
|
+
- **./app/handlers**
|
286
|
+
|
287
|
+
You also need to require all files from these folders. To do this you need to edit the ./config/application.rb file like this example:
|
288
|
+
|
289
|
+
```ruby
|
290
|
+
|
291
|
+
require_relative 'boot'
|
292
|
+
|
293
|
+
require 'rails/all'
|
294
|
+
|
295
|
+
# Require the gems listed in Gemfile, including any gems
|
296
|
+
# you've limited to :test, :development, or :production.
|
297
|
+
Bundler.require(*Rails.groups)
|
298
|
+
|
299
|
+
module MyApplicationName
|
300
|
+
class Application < Rails::Application
|
301
|
+
|
302
|
+
config.autoload_paths << Rails.root.join('app/commands')
|
303
|
+
config.autoload_paths << Rails.root.join('app/events')
|
304
|
+
config.autoload_paths << Rails.root.join('app/handlers')
|
305
|
+
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
```
|
310
|
+
|
311
|
+
## Development
|
312
|
+
|
313
|
+
To update the documentation run:
|
314
|
+
|
315
|
+
```console
|
316
|
+
|
317
|
+
rdoc --op docs
|
318
|
+
|
319
|
+
```
|
data/lib/evnt.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'evnt/version'
|
4
|
+
require 'evnt/command'
|
5
|
+
require 'evnt/event'
|
6
|
+
require 'evnt/handler'
|
7
|
+
require 'evnt/validator'
|
8
|
+
|
9
|
+
##
|
10
|
+
# Evnt is a gem developed to integrate a test driven development
|
11
|
+
# and CQRS pattern inside a ruby project.
|
12
|
+
# Evnt is developed to be used over all kinds of projects and
|
13
|
+
# frameworks (like Ruby on Rails or Sinatra), so it contains
|
14
|
+
# only three types of entities:
|
15
|
+
#
|
16
|
+
# - Command
|
17
|
+
# - Event
|
18
|
+
# - Handler
|
19
|
+
##
|
20
|
+
module Evnt; end
|
data/lib/evnt/command.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Evnt
|
4
|
+
|
5
|
+
##
|
6
|
+
# Commands are used to run single tasks on the system.
|
7
|
+
# It's like a controller on an MVC architecture without the
|
8
|
+
# communication with the client.
|
9
|
+
##
|
10
|
+
class Command
|
11
|
+
|
12
|
+
##
|
13
|
+
# Attribute containings the list of command parameters.
|
14
|
+
##
|
15
|
+
attr_reader :params
|
16
|
+
|
17
|
+
##
|
18
|
+
# The constructor is used to run a new command.
|
19
|
+
#
|
20
|
+
# ==== Attributes
|
21
|
+
#
|
22
|
+
# * +params+ - The list of parameters for the command.
|
23
|
+
# * +_options+ - The list of options for the command.
|
24
|
+
#
|
25
|
+
# ==== Options
|
26
|
+
#
|
27
|
+
# * +exceptions+ - Boolean value used to activate the throw of excetions.
|
28
|
+
##
|
29
|
+
def initialize(params, _options: {})
|
30
|
+
_init_command_data(params, _options)
|
31
|
+
_run_command_steps
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public functions:
|
35
|
+
############################################################################
|
36
|
+
|
37
|
+
##
|
38
|
+
# This function returns the list of errors of the command.
|
39
|
+
# The returned object should be an array of hashes with a message and
|
40
|
+
# a code value.
|
41
|
+
# The code value of hashes should be nil if code is not defined using
|
42
|
+
# the stop() function.
|
43
|
+
##
|
44
|
+
def errors
|
45
|
+
@state[:errors]
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# This function returns the list of error messages of the command.
|
50
|
+
# The returned object should be an array of strings.
|
51
|
+
##
|
52
|
+
def error_messages
|
53
|
+
@state[:errors].map { |e| e[:message] }
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# This function returns the list of error codes of the command.
|
58
|
+
# The returned object should be an array of integers.
|
59
|
+
##
|
60
|
+
def error_codes
|
61
|
+
@state[:errors].map { |e| e[:code] }
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# This function tells if the command is completed or not.
|
66
|
+
# The returned object should be a boolean value.
|
67
|
+
##
|
68
|
+
def completed?
|
69
|
+
@state[:result]
|
70
|
+
end
|
71
|
+
|
72
|
+
# Protected functions:
|
73
|
+
############################################################################
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
##
|
78
|
+
# This function can be used to stop the command execution and
|
79
|
+
# add a new error.
|
80
|
+
# Using err inside a callback should not stop the execution but
|
81
|
+
# should avoid the call of the next callback.
|
82
|
+
# Every time you call this function, a new error should be added
|
83
|
+
# to the errors list.
|
84
|
+
# If the exceptions option is active, it should raise a new error.
|
85
|
+
#
|
86
|
+
# ==== Attributes
|
87
|
+
#
|
88
|
+
# * +message+ - The message string of the error.
|
89
|
+
# * +code+ - The error code.
|
90
|
+
##
|
91
|
+
def err(message, code: nil)
|
92
|
+
@state[:result] = false
|
93
|
+
@state[:errors].push(
|
94
|
+
message: message,
|
95
|
+
code: code
|
96
|
+
)
|
97
|
+
|
98
|
+
# raise error if command needs exceptions
|
99
|
+
raise error if @options[:exceptions]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Private functions:
|
103
|
+
############################################################################
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# This function initializes the command required data.
|
108
|
+
def _init_command_data(params, options)
|
109
|
+
# set state
|
110
|
+
@state = {
|
111
|
+
result: true,
|
112
|
+
errors: []
|
113
|
+
}
|
114
|
+
|
115
|
+
# set options
|
116
|
+
@options = {
|
117
|
+
exceptions: options[:exceptions] || false
|
118
|
+
}
|
119
|
+
|
120
|
+
# set other data
|
121
|
+
@params = params.freeze
|
122
|
+
end
|
123
|
+
|
124
|
+
# This function calls requested steps (callback) for the command.
|
125
|
+
def _run_command_steps
|
126
|
+
_validate_single_params
|
127
|
+
_validate_params if @state[:result] && defined?(_validate_params)
|
128
|
+
_validate_logic if @state[:result] && defined?(_validate_logic)
|
129
|
+
_initialize_events if @state[:result] && defined?(_initialize_events)
|
130
|
+
end
|
131
|
+
|
132
|
+
# This function validates the single parameters sets with the "validates" method.
|
133
|
+
def _validate_single_params
|
134
|
+
return if self.class._validations.nil? || self.class._validations.empty?
|
135
|
+
self.class._validations.each do |val|
|
136
|
+
result = Evnt::Validator.validates(params[val[:param]], val[:options])
|
137
|
+
unless result
|
138
|
+
err "#{val[:param].capitalize} value not accepted"
|
139
|
+
break
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Class functions:
|
145
|
+
############################################################################
|
146
|
+
|
147
|
+
# This class contain the list of settings for the command.
|
148
|
+
class << self
|
149
|
+
|
150
|
+
attr_accessor :_validations
|
151
|
+
|
152
|
+
# This function sets the single validation request for a command parameter.
|
153
|
+
def validates(param, options)
|
154
|
+
validations = instance_variable_get(:@_validations) || []
|
155
|
+
validations.push(param: param, options: options)
|
156
|
+
instance_variable_set(:@_validations, validations)
|
157
|
+
end
|
158
|
+
|
159
|
+
# This function sets the validate params function for the command.
|
160
|
+
def to_validate_params(&block)
|
161
|
+
define_method('_validate_params', &block)
|
162
|
+
end
|
163
|
+
|
164
|
+
# This function sets the validate logic function for the command.
|
165
|
+
def to_validate_logic(&block)
|
166
|
+
define_method('_validate_logic', &block)
|
167
|
+
end
|
168
|
+
|
169
|
+
# This function sets the intitialize events function for the command.
|
170
|
+
def to_initialize_events(&block)
|
171
|
+
define_method('_initialize_events', &block)
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
data/lib/evnt/event.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Evnt
|
6
|
+
|
7
|
+
##
|
8
|
+
# Events are used to save on a persistent data structure what
|
9
|
+
# happends on the system.
|
10
|
+
##
|
11
|
+
class Event
|
12
|
+
|
13
|
+
##
|
14
|
+
# Attribute containings the name of the event.
|
15
|
+
##
|
16
|
+
attr_reader :name
|
17
|
+
|
18
|
+
##
|
19
|
+
# Attribute containings the list of attributes of the event.
|
20
|
+
##
|
21
|
+
attr_reader :attributes
|
22
|
+
|
23
|
+
##
|
24
|
+
# Attribute containings the payload of the event.
|
25
|
+
##
|
26
|
+
attr_reader :payload
|
27
|
+
|
28
|
+
##
|
29
|
+
# Attribute containings the extra parameters of the event.
|
30
|
+
##
|
31
|
+
attr_reader :extras
|
32
|
+
|
33
|
+
##
|
34
|
+
# The constructor is used to initialize a new event.
|
35
|
+
# The parameters are validated and added to the payload
|
36
|
+
# of the event. The parameters with the _ in their name
|
37
|
+
# are not saved on the payload.
|
38
|
+
#
|
39
|
+
# ==== Attributes
|
40
|
+
#
|
41
|
+
# * +params+ - The list of parameters for the command.
|
42
|
+
##
|
43
|
+
def initialize(params)
|
44
|
+
_init_event_data(params)
|
45
|
+
_validate_payload
|
46
|
+
_run_event_steps
|
47
|
+
_notify_handlers
|
48
|
+
end
|
49
|
+
|
50
|
+
# Public functions:
|
51
|
+
############################################################################
|
52
|
+
|
53
|
+
##
|
54
|
+
# This function tells if the event is reloaded or not.
|
55
|
+
# The returned object should be a boolean value corresponding to the
|
56
|
+
# presence of evnt value inside the event params.
|
57
|
+
##
|
58
|
+
def reloaded?
|
59
|
+
@state[:reloaded]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Private functions:
|
63
|
+
############################################################################
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# This function initializes the event required data.
|
68
|
+
def _init_event_data(params)
|
69
|
+
# set state
|
70
|
+
@state = {
|
71
|
+
reloaded: !params[:evnt].nil?
|
72
|
+
}
|
73
|
+
|
74
|
+
# set payload
|
75
|
+
payload = params.reject { |k, _v| k[0] == '_' }
|
76
|
+
@payload = @state[:reloaded] ? payload : _generate_payload(payload)
|
77
|
+
|
78
|
+
# set other datas
|
79
|
+
@name = self.class._name
|
80
|
+
@attributes = self.class._attributes
|
81
|
+
@extras = params.select { |k, _v| k[0] == '_' }
|
82
|
+
end
|
83
|
+
|
84
|
+
# This function generates the complete event payload.
|
85
|
+
def _generate_payload(params)
|
86
|
+
# add evnt informations
|
87
|
+
params[:evnt] = {
|
88
|
+
timestamp: Time.now.to_i,
|
89
|
+
name: self.class._name
|
90
|
+
}
|
91
|
+
# return payload
|
92
|
+
params
|
93
|
+
end
|
94
|
+
|
95
|
+
# This function validates all payload and check they are completed.
|
96
|
+
def _validate_payload
|
97
|
+
return unless self.class._attributes
|
98
|
+
|
99
|
+
# check all attributes are present
|
100
|
+
check_attr = (@payload.keys - [:evnt]) == self.class._attributes
|
101
|
+
raise 'Event parameters are not correct' unless check_attr
|
102
|
+
end
|
103
|
+
|
104
|
+
# This function calls requested steps for the event.
|
105
|
+
def _run_event_steps
|
106
|
+
_write_event if defined?(_write_event)
|
107
|
+
end
|
108
|
+
|
109
|
+
# This function notify all handlers for the event.
|
110
|
+
def _notify_handlers
|
111
|
+
return unless self.class._handlers
|
112
|
+
|
113
|
+
# notify every handler
|
114
|
+
self.class._handlers.each do |handler|
|
115
|
+
handler.notify(self)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Class functions:
|
120
|
+
############################################################################
|
121
|
+
|
122
|
+
# This class contain the list of settings for the event.
|
123
|
+
class << self
|
124
|
+
|
125
|
+
attr_accessor :_name, :_attributes, :_handlers
|
126
|
+
|
127
|
+
# This function sets the name for the event.
|
128
|
+
def name_is(event_name)
|
129
|
+
instance_variable_set(:@_name, event_name)
|
130
|
+
end
|
131
|
+
|
132
|
+
# This function sets the list of attributes for the event.
|
133
|
+
def attributes_are(*attributes)
|
134
|
+
instance_variable_set(:@_attributes, attributes)
|
135
|
+
end
|
136
|
+
|
137
|
+
# This function sets the list of handlers for the event.
|
138
|
+
def handlers_are(handlers)
|
139
|
+
instance_variable_set(:@_handlers, handlers)
|
140
|
+
end
|
141
|
+
|
142
|
+
# This function sets the write event function for the event.
|
143
|
+
def to_write_event(&block)
|
144
|
+
define_method('_write_event', &block)
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
data/lib/evnt/handler.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Evnt
|
4
|
+
|
5
|
+
##
|
6
|
+
# Handlers are used to listen one or more events and run tasks after their execution.
|
7
|
+
##
|
8
|
+
class Handler
|
9
|
+
|
10
|
+
##
|
11
|
+
# Attribute containings the event that notify the handler.
|
12
|
+
##
|
13
|
+
attr_reader :event
|
14
|
+
|
15
|
+
##
|
16
|
+
# This function is called from an event to notify an handler.
|
17
|
+
#
|
18
|
+
# ==== Attributes
|
19
|
+
#
|
20
|
+
# * +event+ - The event object that call the function.
|
21
|
+
##
|
22
|
+
def notify(event)
|
23
|
+
_init_handler_data(event)
|
24
|
+
_init_handler_steps
|
25
|
+
_run_handler_steps
|
26
|
+
end
|
27
|
+
|
28
|
+
# Private functions:
|
29
|
+
############################################################################
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# This function initializes the handler required data.
|
34
|
+
def _init_handler_data(event)
|
35
|
+
@event = event
|
36
|
+
end
|
37
|
+
|
38
|
+
# This function init the handler steps.
|
39
|
+
def _init_handler_steps
|
40
|
+
self.class._events[@event.name].call
|
41
|
+
end
|
42
|
+
|
43
|
+
# This function calls requested steps for the handler.
|
44
|
+
def _run_handler_steps
|
45
|
+
_update_queries if defined?(_update_queries)
|
46
|
+
|
47
|
+
# manage event reloaded
|
48
|
+
if event.reloaded?
|
49
|
+
_manage_reloaded_event if defined?(_manage_reloaded_event)
|
50
|
+
return
|
51
|
+
end
|
52
|
+
|
53
|
+
# manage normal event
|
54
|
+
_manage_event if defined?(_manage_event)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Class functions:
|
58
|
+
############################################################################
|
59
|
+
|
60
|
+
# This class contain the list of settings for the handler.
|
61
|
+
class << self
|
62
|
+
|
63
|
+
attr_accessor :_events
|
64
|
+
|
65
|
+
# This function sets the blocks executed for a specific event.
|
66
|
+
def on(event_name, &block)
|
67
|
+
instance_variable_set(:@_events, {}) unless @_events
|
68
|
+
@_events[event_name] = block
|
69
|
+
end
|
70
|
+
|
71
|
+
# This function sets the update queries function for the event.
|
72
|
+
def to_update_queries(&block)
|
73
|
+
define_method('_update_queries', &block)
|
74
|
+
end
|
75
|
+
|
76
|
+
# This function sets the manage event function for the event.
|
77
|
+
def to_manage_event(&block)
|
78
|
+
define_method('_manage_event', &block)
|
79
|
+
end
|
80
|
+
|
81
|
+
# This function sets the manage reloaded event function for the event.
|
82
|
+
def to_manage_reloaded_event(&block)
|
83
|
+
define_method('_manage_reloaded_event', &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Evnt
|
4
|
+
|
5
|
+
##
|
6
|
+
# Validator is a class used to validates params and attributes automatically.
|
7
|
+
##
|
8
|
+
class Validator
|
9
|
+
|
10
|
+
# Class functions:
|
11
|
+
############################################################################
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
##
|
16
|
+
# This function is used to validates a parameter with some validation
|
17
|
+
# options.
|
18
|
+
#
|
19
|
+
# ==== Attributes
|
20
|
+
#
|
21
|
+
# * +param+ - The parameter to be validated.
|
22
|
+
# * +options+ - The list of options for the validations.
|
23
|
+
#
|
24
|
+
# ==== Options
|
25
|
+
#
|
26
|
+
# * +type+ - Symbol value used to tells the correct parameter type.
|
27
|
+
# * +presence+ - Boolean value used to tells if the presence is required.
|
28
|
+
##
|
29
|
+
def validates(param, options)
|
30
|
+
options.each do |key, value|
|
31
|
+
return false unless validate_option(param, key, value)
|
32
|
+
end
|
33
|
+
|
34
|
+
true
|
35
|
+
rescue StandardError => e
|
36
|
+
puts e
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# This function calls the correct validate function for a specific option.
|
42
|
+
#
|
43
|
+
# ==== Attributes
|
44
|
+
#
|
45
|
+
# * +param+ - The parameter to be validated.
|
46
|
+
# * +option_key+ - The key of the option that should be used.
|
47
|
+
# * +option_value+ - The value of the option that should be used.
|
48
|
+
##
|
49
|
+
def validate_option(param, option_key, option_value)
|
50
|
+
case option_key
|
51
|
+
when :type
|
52
|
+
validate_type(param, option_value)
|
53
|
+
when :presence
|
54
|
+
validate_presence(param, option_value)
|
55
|
+
when :blank
|
56
|
+
validate_blank(param, option_value)
|
57
|
+
when :numeric
|
58
|
+
validate_numeric(param, option_value)
|
59
|
+
else
|
60
|
+
raise 'Validator option not accepted'
|
61
|
+
end
|
62
|
+
rescue StandardError => e
|
63
|
+
puts e
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# This function validates the type of the parameter.
|
69
|
+
#
|
70
|
+
# ==== Attributes
|
71
|
+
#
|
72
|
+
# * +param+ - The parameter to be validated.
|
73
|
+
# * +value+ - The value of the type option that should be used.
|
74
|
+
##
|
75
|
+
def validate_type(param, value)
|
76
|
+
return true if param.nil?
|
77
|
+
|
78
|
+
if value.instance_of?(Symbol)
|
79
|
+
validate_type_general(param, value)
|
80
|
+
elsif value.instance_of?(String)
|
81
|
+
validate_type_custom(param, value)
|
82
|
+
else
|
83
|
+
raise 'Validator type option not accepted'
|
84
|
+
end
|
85
|
+
rescue StandardError => e
|
86
|
+
puts e
|
87
|
+
false
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# This function validates the presence of the prameter.
|
92
|
+
# A parameter is present when its value is not nil.
|
93
|
+
#
|
94
|
+
# ==== Attributes
|
95
|
+
#
|
96
|
+
# * +param+ - The parameter to be validated.
|
97
|
+
# * +value+ - The value of the presence option that should be used.
|
98
|
+
##
|
99
|
+
def validate_presence(param, value)
|
100
|
+
is_nil = param.nil?
|
101
|
+
value ? !is_nil : is_nil
|
102
|
+
rescue StandardError => e
|
103
|
+
puts e
|
104
|
+
false
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# This function validates the blank of the prameter.
|
109
|
+
# A parameter is blank when its value is nil, false, or empty.
|
110
|
+
#
|
111
|
+
# ==== Attributes
|
112
|
+
#
|
113
|
+
# * +param+ - The parameter to be validated.
|
114
|
+
# * +value+ - The value of the presence option that should be used.
|
115
|
+
##
|
116
|
+
def validate_blank(param, value)
|
117
|
+
blank = (!param || param.empty?)
|
118
|
+
value ? blank : !blank
|
119
|
+
rescue StandardError => e
|
120
|
+
puts e
|
121
|
+
false
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# This function validates some numeric options for the parameter.
|
126
|
+
#
|
127
|
+
# ==== Attributes
|
128
|
+
#
|
129
|
+
# * +param+ - The parameter to be validated.
|
130
|
+
# * +value+ - The value object with validation specs.
|
131
|
+
##
|
132
|
+
def validate_numeric(param, value)
|
133
|
+
# TODO: Continue
|
134
|
+
rescue StandardError => e
|
135
|
+
puts e
|
136
|
+
false
|
137
|
+
end
|
138
|
+
|
139
|
+
# Private functions:
|
140
|
+
##########################################################################
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
# Types validations:
|
145
|
+
##########################################################################
|
146
|
+
|
147
|
+
# This function validates a param type for general types.
|
148
|
+
def validate_type_general(param, value)
|
149
|
+
case value
|
150
|
+
when :boolean
|
151
|
+
validate_type_boolean(param)
|
152
|
+
when :string
|
153
|
+
validate_type_string(param)
|
154
|
+
when :integer
|
155
|
+
validate_type_integer(param)
|
156
|
+
when :symbol
|
157
|
+
validate_type_symbol(param)
|
158
|
+
when :float
|
159
|
+
validates_type_float(param)
|
160
|
+
when :hash
|
161
|
+
validates_type_hash(param)
|
162
|
+
when :array
|
163
|
+
validates_type_array(param)
|
164
|
+
else
|
165
|
+
raise 'Validator type option not accepted'
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# This function validates a param type for custom types.
|
170
|
+
def validate_type_custom(param, value)
|
171
|
+
param.instance_of?(Object.const_get(value))
|
172
|
+
end
|
173
|
+
|
174
|
+
def validate_type_boolean(param)
|
175
|
+
param.instance_of?(TrueClass) || param.instance_of?(FalseClass)
|
176
|
+
end
|
177
|
+
|
178
|
+
def validate_type_string(param)
|
179
|
+
param.instance_of?(String)
|
180
|
+
end
|
181
|
+
|
182
|
+
def validate_type_integer(param)
|
183
|
+
param.instance_of?(Integer)
|
184
|
+
end
|
185
|
+
|
186
|
+
def validate_type_symbol(param)
|
187
|
+
param.instance_of?(Symbol)
|
188
|
+
end
|
189
|
+
|
190
|
+
def validates_type_float(param)
|
191
|
+
param.instance_of?(Float)
|
192
|
+
end
|
193
|
+
|
194
|
+
def validates_type_hash(param)
|
195
|
+
param.instance_of?(Hash)
|
196
|
+
end
|
197
|
+
|
198
|
+
def validates_type_array(param)
|
199
|
+
param.instance_of?(Array)
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
data/lib/evnt/version.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: evnt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ideonetwork
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -25,40 +25,48 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rdoc
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '5.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '5.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '3.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '3.0'
|
55
55
|
description: CQRS and Event Driven Development architecture for Ruby
|
56
56
|
email:
|
57
57
|
- dev@ideonetwork.it
|
58
58
|
executables: []
|
59
59
|
extensions: []
|
60
60
|
extra_rdoc_files: []
|
61
|
-
files:
|
61
|
+
files:
|
62
|
+
- MIT-LICENSE
|
63
|
+
- README.md
|
64
|
+
- lib/evnt.rb
|
65
|
+
- lib/evnt/command.rb
|
66
|
+
- lib/evnt/event.rb
|
67
|
+
- lib/evnt/handler.rb
|
68
|
+
- lib/evnt/validator.rb
|
69
|
+
- lib/evnt/version.rb
|
62
70
|
homepage: http://ideonetwork.it/
|
63
71
|
licenses:
|
64
72
|
- MIT
|
@@ -79,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
87
|
version: '0'
|
80
88
|
requirements: []
|
81
89
|
rubyforge_project:
|
82
|
-
rubygems_version: 2.6.
|
90
|
+
rubygems_version: 2.6.14
|
83
91
|
signing_key:
|
84
92
|
specification_version: 4
|
85
93
|
summary: CQRS and Event Driven Development architecture for Ruby
|