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