sequent 0.1.1 → 0.1.2
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/lib/sequent/core/aggregate_repository.rb +94 -0
- data/lib/sequent/core/aggregate_root.rb +87 -0
- data/lib/sequent/core/base_command_handler.rb +39 -0
- data/lib/sequent/core/base_event_handler.rb +51 -0
- data/lib/sequent/core/command.rb +79 -0
- data/lib/sequent/core/command_record.rb +26 -0
- data/lib/sequent/core/command_service.rb +118 -0
- data/lib/sequent/core/core.rb +15 -0
- data/lib/sequent/core/event.rb +62 -0
- data/lib/sequent/core/event_record.rb +34 -0
- data/lib/sequent/core/event_store.rb +110 -0
- data/lib/sequent/core/helpers/association_validator.rb +39 -0
- data/lib/sequent/core/helpers/attribute_support.rb +207 -0
- data/lib/sequent/core/helpers/boolean_support.rb +36 -0
- data/lib/sequent/core/helpers/copyable.rb +25 -0
- data/lib/sequent/core/helpers/equal_support.rb +41 -0
- data/lib/sequent/core/helpers/helpers.rb +9 -0
- data/lib/sequent/core/helpers/mergable.rb +21 -0
- data/lib/sequent/core/helpers/param_support.rb +80 -0
- data/lib/sequent/core/helpers/self_applier.rb +45 -0
- data/lib/sequent/core/helpers/string_support.rb +22 -0
- data/lib/sequent/core/helpers/uuid_helper.rb +17 -0
- data/lib/sequent/core/record_sessions/active_record_session.rb +92 -0
- data/lib/sequent/core/record_sessions/record_sessions.rb +2 -0
- data/lib/sequent/core/record_sessions/replay_events_session.rb +306 -0
- data/lib/sequent/core/tenant_event_store.rb +18 -0
- data/lib/sequent/core/transactions/active_record_transaction_provider.rb +16 -0
- data/lib/sequent/core/transactions/no_transactions.rb +13 -0
- data/lib/sequent/core/transactions/transactions.rb +2 -0
- data/lib/sequent/core/value_object.rb +48 -0
- data/lib/sequent/migrations/migrate_events.rb +53 -0
- data/lib/sequent/migrations/migrations.rb +7 -0
- data/lib/sequent/sequent.rb +3 -0
- data/lib/sequent/test/command_handler_helpers.rb +101 -0
- data/lib/sequent/test/test.rb +1 -0
- data/lib/version.rb +3 -0
- metadata +38 -3
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'oj'
|
2
|
+
require_relative 'event_record'
|
3
|
+
|
4
|
+
module Sequent
|
5
|
+
module Core
|
6
|
+
class EventStoreConfiguration
|
7
|
+
attr_accessor :record_class, :event_handlers
|
8
|
+
|
9
|
+
def initialize(record_class = Sequent::Core::EventRecord, event_handlers = [])
|
10
|
+
@record_class = record_class
|
11
|
+
@event_handlers = event_handlers
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class EventStore
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_accessor :configuration,
|
19
|
+
:instance
|
20
|
+
end
|
21
|
+
|
22
|
+
# Creates a new EventStore and overwrites all existing config.
|
23
|
+
# The new EventStore can be retrieved via the +EventStore.instance+ method.
|
24
|
+
#
|
25
|
+
# If you don't want a singleton you can always instantiate it yourself using the +EventStore.new+.
|
26
|
+
def self.configure
|
27
|
+
self.configuration = EventStoreConfiguration.new
|
28
|
+
yield(configuration) if block_given?
|
29
|
+
EventStore.instance = EventStore.new(configuration)
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(configuration = EventStoreConfiguration.new)
|
33
|
+
@record_class = configuration.record_class
|
34
|
+
@event_handlers = configuration.event_handlers
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Stores the events in the EventStore and publishes the events
|
39
|
+
# to the registered event_handlers.
|
40
|
+
#
|
41
|
+
def commit_events(command, events)
|
42
|
+
store_events(command, events)
|
43
|
+
publish_events(events, @event_handlers)
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Returns all events for the aggregate ordered by sequence_number
|
48
|
+
#
|
49
|
+
def load_events(aggregate_id)
|
50
|
+
event_types = {}
|
51
|
+
@record_class.connection.select_all("select event_type, event_json from #{@record_class.table_name} where aggregate_id = '#{aggregate_id}' order by sequence_number asc").map! do |event_hash|
|
52
|
+
event_type = event_hash["event_type"]
|
53
|
+
event_json = Oj.strict_load(event_hash["event_json"])
|
54
|
+
unless event_types.has_key?(event_type)
|
55
|
+
event_types[event_type] = Class.const_get(event_type.to_sym)
|
56
|
+
end
|
57
|
+
event_types[event_type].deserialize_from_json(event_json)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Replays all events in the event store to the registered event_handlers.
|
63
|
+
#
|
64
|
+
# @param block that returns the event stream.
|
65
|
+
def replay_events
|
66
|
+
event_stream = yield
|
67
|
+
event_types = {}
|
68
|
+
event_stream.each do |event_hash|
|
69
|
+
event_type = event_hash["event_type"]
|
70
|
+
payload = Oj.strict_load(event_hash["event_json"])
|
71
|
+
unless event_types.has_key?(event_type)
|
72
|
+
event_types[event_type] = Class.const_get(event_type.to_sym)
|
73
|
+
end
|
74
|
+
event = event_types[event_type].deserialize_from_json(payload)
|
75
|
+
@event_handlers.each do |handler|
|
76
|
+
handler.handle_message event
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
protected
|
82
|
+
def record_class
|
83
|
+
@record_class
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def publish_events(events, event_handlers)
|
89
|
+
events.each do |event|
|
90
|
+
event_handlers.each do |handler|
|
91
|
+
handler.handle_message event
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_events(event_records)
|
97
|
+
event_records.map(&:event)
|
98
|
+
end
|
99
|
+
|
100
|
+
def store_events(command, events = [])
|
101
|
+
command_record = Sequent::Core::CommandRecord.create!(:command => command)
|
102
|
+
events.each do |event|
|
103
|
+
@record_class.create!(:command_record => command_record, :event => event)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
module Sequent
|
3
|
+
module Core
|
4
|
+
module Helpers
|
5
|
+
#
|
6
|
+
# Validator for associations. Typically used in Sequent::Core::Command,
|
7
|
+
# Sequent::Core::Event and Sequent::Core::ValueObjects.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# class RegisterForTrainingCommand < UpdateCommand
|
12
|
+
# attrs trainee: Person
|
13
|
+
#
|
14
|
+
# validates_presence_of :trainee
|
15
|
+
# validates_with Sequent::Core::Helpers::AssociationValidator, associations: [:trainee]
|
16
|
+
#
|
17
|
+
# end
|
18
|
+
class AssociationValidator < ActiveModel::Validator
|
19
|
+
|
20
|
+
def validate(record)
|
21
|
+
associations = options[:associations]
|
22
|
+
associations = [associations] unless associations.instance_of?(Array)
|
23
|
+
associations.each do |association|
|
24
|
+
next unless association # since ruby 2.0...?
|
25
|
+
value = record.instance_variable_get("@#{association.to_s}")
|
26
|
+
if value && !value.kind_of?(Array) && record.respond_to?(:attributes) && !value.kind_of?(record.attributes[association])
|
27
|
+
record.errors[association] = "is not of type #{record.attributes[association]}"
|
28
|
+
elsif value && value.kind_of?(Array)
|
29
|
+
record.errors[association] = "is invalid" if value.any? { |v| not v.valid? }
|
30
|
+
else
|
31
|
+
record.errors[association] = "is invalid" if value && value.invalid?
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
# TODO: Move this into a separate core_ext folder like for instance Rails does.
|
3
|
+
# WARNING: Monkey patches below...
|
4
|
+
class Symbol
|
5
|
+
def self.deserialize_from_json(value)
|
6
|
+
value.try(:to_sym)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class String
|
11
|
+
def self.deserialize_from_json(value)
|
12
|
+
value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Integer
|
17
|
+
def self.deserialize_from_json(value)
|
18
|
+
value.blank? ? nil : value.to_i
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Boolean
|
23
|
+
def self.deserialize_from_json(value)
|
24
|
+
value.nil? ? nil : (value.present? ? value : false)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Date
|
29
|
+
def self.deserialize_from_json(value)
|
30
|
+
value.nil? ? nil : Date.iso8601(value.dup)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class DateTime
|
35
|
+
def self.deserialize_from_json(value)
|
36
|
+
value.nil? ? nil : DateTime.iso8601(value.dup)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Array
|
41
|
+
def self.deserialize_from_json(value)
|
42
|
+
value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module Sequent
|
47
|
+
module Core
|
48
|
+
module Helpers
|
49
|
+
# Provides functionality for defining attributes with their types
|
50
|
+
#
|
51
|
+
# Since our Commands and ValueObjects are not backed by a database like e.g. rails
|
52
|
+
# we can not infer their types. We need the types to be able to parse from and to json.
|
53
|
+
# We could have stored te type information in the json, but we didn't.
|
54
|
+
#
|
55
|
+
# You typically do not need to include this module in your classes. If you extend from
|
56
|
+
# Sequent::Core::ValueObject, Sequent::Core::Event or Sequent::Core::BaseCommand you will
|
57
|
+
# get this functionality for free.
|
58
|
+
#
|
59
|
+
module AttributeSupport
|
60
|
+
# module containing class methods to be added
|
61
|
+
module ClassMethods
|
62
|
+
|
63
|
+
def types
|
64
|
+
@types ||= {}
|
65
|
+
if @merged_types
|
66
|
+
@merged_types
|
67
|
+
else
|
68
|
+
@merged_types = is_a?(Class) && superclass.respond_to?(:types) ? @types.merge(superclass.types) : @types
|
69
|
+
included_modules.select { |m| m.include? Sequent::Core::Helpers::AttributeSupport }.each do |mod|
|
70
|
+
@merged_types.merge!(mod.types)
|
71
|
+
end
|
72
|
+
@merged_types
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def attrs(args)
|
77
|
+
@types ||= {}
|
78
|
+
@types.merge!(args)
|
79
|
+
args.each do |attribute, _|
|
80
|
+
attr_accessor attribute
|
81
|
+
end
|
82
|
+
|
83
|
+
# Generate method that sets all defined attributes based on the attrs hash.
|
84
|
+
class_eval <<EOS
|
85
|
+
def update_all_attributes(attrs)
|
86
|
+
super if defined?(super)
|
87
|
+
#{@types.map { |attribute, _|
|
88
|
+
"@#{attribute} = attrs[:#{attribute}]"
|
89
|
+
}.join("\n ")}
|
90
|
+
self
|
91
|
+
end
|
92
|
+
EOS
|
93
|
+
|
94
|
+
class_eval <<EOS
|
95
|
+
def update_all_attributes_from_json(attrs)
|
96
|
+
super if defined?(super)
|
97
|
+
#{@types.map { |attribute, type|
|
98
|
+
"@#{attribute} = #{type}.deserialize_from_json(attrs['#{attribute}'])"
|
99
|
+
}.join("\n ")}
|
100
|
+
end
|
101
|
+
EOS
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Allows you to define something is an array of a type
|
106
|
+
# Example:
|
107
|
+
#
|
108
|
+
# attrs trainees: array(Person)
|
109
|
+
#
|
110
|
+
def array(type)
|
111
|
+
ArrayWithType.new(type)
|
112
|
+
end
|
113
|
+
|
114
|
+
def deserialize_from_json(args)
|
115
|
+
unless args.nil?
|
116
|
+
obj = allocate()
|
117
|
+
obj.update_all_attributes_from_json(args)
|
118
|
+
obj
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
def numeric?(object)
|
124
|
+
true if Float(object) rescue false
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
# extend host class with class methods when we're included
|
130
|
+
def self.included(host_class)
|
131
|
+
host_class.extend(ClassMethods)
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
# needed for active module JSON serialization
|
136
|
+
def attributes
|
137
|
+
self.class.types
|
138
|
+
end
|
139
|
+
|
140
|
+
def validation_errors(prefix = nil)
|
141
|
+
result = errors.to_hash
|
142
|
+
self.class.types.each do |field|
|
143
|
+
value = self.instance_variable_get("@#{field[0]}")
|
144
|
+
if value.respond_to? :validation_errors
|
145
|
+
value.validation_errors.each { |k, v| result["#{field[0].to_s}_#{k.to_s}".to_sym] = v }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
prefix ? HashWithIndifferentAccess[result.map { |k, v| ["#{prefix}_#{k}", v] }] : result
|
149
|
+
end
|
150
|
+
|
151
|
+
# If you have a Date object validate it with this method when the unparsed input is a String
|
152
|
+
# This scenario is typically when a date is posted from the web.
|
153
|
+
#
|
154
|
+
# Example
|
155
|
+
#
|
156
|
+
# class Person < Sequent::Core::ValueObject
|
157
|
+
# attrs date_of_birth: Date
|
158
|
+
#
|
159
|
+
# validate :valid_date
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# If the date_of_birth is a valid date it will be parsed into a proper Date object.
|
163
|
+
# This implementation currently only support dd-mm-yyyy format.
|
164
|
+
def valid_date
|
165
|
+
self.class.types.each do |name, clazz|
|
166
|
+
if clazz == Date
|
167
|
+
return if self.instance_variable_get("@#{name}").kind_of? Date
|
168
|
+
unless self.instance_variable_get("@#{name}").blank?
|
169
|
+
if (/\d{2}-\d{2}-\d{4}/ =~ self.instance_variable_get("@#{name}")).nil?
|
170
|
+
@errors.add(name.to_s, :invalid_date) if (/\d{2}-\d{2}-\d{4}/ =~ self.instance_variable_get("@#{name}")).nil?
|
171
|
+
else
|
172
|
+
begin
|
173
|
+
self.instance_variable_set "@#{name}", Date.strptime(self.instance_variable_get("@#{name}"), "%d-%m-%Y")
|
174
|
+
rescue
|
175
|
+
@errors.add(name.to_s, :invalid_date)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
class ArrayWithType
|
187
|
+
attr_accessor :item_type
|
188
|
+
|
189
|
+
def initialize(item_type)
|
190
|
+
raise "needs a item_type" unless item_type
|
191
|
+
@item_type = item_type
|
192
|
+
end
|
193
|
+
|
194
|
+
def deserialize_from_json(value)
|
195
|
+
value.nil? ? nil : value.map { |item| item_type.deserialize_from_json(item) }
|
196
|
+
end
|
197
|
+
|
198
|
+
def to_s
|
199
|
+
"Sequent::Core::Helpers::ArrayWithType.new(#{item_type})"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Sequent
|
2
|
+
module Core
|
3
|
+
module Helpers
|
4
|
+
#
|
5
|
+
# Parses the strings "true", "false" and nil to true, false, false
|
6
|
+
#
|
7
|
+
# You need to include this module explicitly when working with booleans.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# class Registration < Sequent::Core::ValueObject
|
12
|
+
# include Sequent::Core::Helpers::BooleanSupport
|
13
|
+
# attrs accepted_terms: Boolean
|
14
|
+
# end
|
15
|
+
module BooleanSupport
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
base.before_validation :parse_booleans
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_booleans
|
22
|
+
attributes.each do |name, type|
|
23
|
+
if type == Boolean
|
24
|
+
raw_value = self.instance_variable_get("@#{name}")
|
25
|
+
return if raw_value.kind_of? Boolean
|
26
|
+
return unless [nil, "true", "false"].include?(raw_value)
|
27
|
+
bool_value = raw_value == "true" ? true : false
|
28
|
+
self.instance_variable_set "@#{name}", bool_value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Sequent
|
2
|
+
module Core
|
3
|
+
module Helpers
|
4
|
+
# Make a deep clone of an object that include AttributeSupport
|
5
|
+
#
|
6
|
+
# person = Person.new(name: 'Ben').copy(name: 'Kim')
|
7
|
+
#
|
8
|
+
# You typically do not need to include this module in your classes. If you extend from
|
9
|
+
# Sequent::Core::ValueObject, Sequent::Core::Event or Sequent::Core::BaseCommand you will
|
10
|
+
# get this functionality for free.
|
11
|
+
#
|
12
|
+
module Copyable
|
13
|
+
def copy(attrs = {})
|
14
|
+
the_copy = Marshal.load(Marshal.dump(self))
|
15
|
+
attrs.each do |name, value|
|
16
|
+
the_copy.send("#{name}=", value)
|
17
|
+
end
|
18
|
+
the_copy
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Sequent
|
2
|
+
module Core
|
3
|
+
module Helpers
|
4
|
+
#
|
5
|
+
# You typically do not need to include this module in your classes. If you extend from
|
6
|
+
# Sequent::Core::ValueObject, Sequent::Core::Event or Sequent::Core::BaseCommand you will
|
7
|
+
# get this functionality for free.
|
8
|
+
#
|
9
|
+
module EqualSupport
|
10
|
+
def ==(other)
|
11
|
+
return false if other == nil
|
12
|
+
return false if self.class != other.class
|
13
|
+
self.class.types.each do |name, _|
|
14
|
+
self_value = self.send(name)
|
15
|
+
other_value = other.send(name)
|
16
|
+
if self_value.class == DateTime && other_value.class == DateTime
|
17
|
+
# we don't care about milliseconds. If you know a better way of checking for equality please improve.
|
18
|
+
return false unless (self_value.iso8601 == other_value.iso8601)
|
19
|
+
else
|
20
|
+
return false unless (self_value == other_value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def hash
|
27
|
+
hash = 17
|
28
|
+
self.class.types.each do |name, _|
|
29
|
+
hash = hash * 31 + self.send(name).hash
|
30
|
+
end
|
31
|
+
hash
|
32
|
+
end
|
33
|
+
|
34
|
+
def eql?(other)
|
35
|
+
self == other
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require_relative 'uuid_helper'
|
2
|
+
require_relative 'copyable'
|
3
|
+
require_relative 'attribute_support'
|
4
|
+
require_relative 'equal_support'
|
5
|
+
require_relative 'param_support'
|
6
|
+
require_relative 'mergable'
|
7
|
+
require_relative 'association_validator'
|
8
|
+
require_relative 'string_support'
|
9
|
+
require_relative 'boolean_support'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Sequent
|
2
|
+
module Core
|
3
|
+
module Helpers
|
4
|
+
# Looks like Copyable but changes this instance
|
5
|
+
#
|
6
|
+
# ben = Person.new(name: 'Ben').merge!(name: 'Ben Vonk')
|
7
|
+
#
|
8
|
+
module Mergable
|
9
|
+
|
10
|
+
def merge!(attrs = {})
|
11
|
+
attrs.each do |name, value|
|
12
|
+
self.send("#{name}=", value)
|
13
|
+
end
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
module Sequent
|
4
|
+
module Core
|
5
|
+
module Helpers
|
6
|
+
# Class to support binding from a params hash like the one from Sinatra
|
7
|
+
#
|
8
|
+
# You typically do not need to include this module in your classes. If you extend from
|
9
|
+
# Sequent::Core::ValueObject, Sequent::Core::Event or Sequent::Core::BaseCommand you will
|
10
|
+
# get this functionality for free.
|
11
|
+
#
|
12
|
+
module ParamSupport
|
13
|
+
module ClassMethods
|
14
|
+
def from_params(params = {})
|
15
|
+
result = allocate
|
16
|
+
params = HashWithIndifferentAccess.new(params)
|
17
|
+
result.attributes.each do |attribute, type|
|
18
|
+
value = params[attribute]
|
19
|
+
|
20
|
+
next if value.blank?
|
21
|
+
if type.respond_to? :from_params
|
22
|
+
value = type.from_params(value)
|
23
|
+
elsif type.is_a? Sequent::Core::Helpers::ArrayWithType
|
24
|
+
value = value.map { |v| type.item_type.from_params(v) }
|
25
|
+
elsif type <= Date
|
26
|
+
value = Date.strptime(value, "%d-%m-%Y") if value
|
27
|
+
end
|
28
|
+
result.instance_variable_set(:"@#{attribute}", value)
|
29
|
+
end
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
# extend host class with class methods when we're included
|
35
|
+
def self.included(host_class)
|
36
|
+
host_class.extend(ClassMethods)
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_params(root)
|
40
|
+
make_params root, as_params
|
41
|
+
end
|
42
|
+
|
43
|
+
def as_params
|
44
|
+
hash = HashWithIndifferentAccess.new
|
45
|
+
self.class.types.each do |field|
|
46
|
+
value = self.instance_variable_get("@#{field[0]}")
|
47
|
+
next if field[0] == "errors"
|
48
|
+
if value.respond_to?(:as_params) && value.kind_of?(ValueObject)
|
49
|
+
value = value.as_params
|
50
|
+
elsif value.kind_of?(Array)
|
51
|
+
value = value.map { |val| val.kind_of?(ValueObject) ? val.as_params : val }
|
52
|
+
elsif value.kind_of? Date
|
53
|
+
value = value.strftime("%d-%m-%Y") if value
|
54
|
+
end
|
55
|
+
hash[field[0]] = value
|
56
|
+
end
|
57
|
+
hash
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def make_params(root, hash)
|
62
|
+
result={}
|
63
|
+
hash.each do |k, v|
|
64
|
+
key = "#{root}[#{k}]"
|
65
|
+
if v.is_a? Hash
|
66
|
+
make_params(key, v).each do |k, v|
|
67
|
+
result[k] = v.nil? ? "" : v.to_s
|
68
|
+
end
|
69
|
+
elsif v.is_a? Array
|
70
|
+
result[key] = v
|
71
|
+
else
|
72
|
+
result[key] = v.nil? ? "" : v.to_s
|
73
|
+
end
|
74
|
+
end
|
75
|
+
result
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Sequent
|
2
|
+
module Core
|
3
|
+
module Helpers
|
4
|
+
##
|
5
|
+
# Creates ability to use DSL like:
|
6
|
+
# class MyEventHandler < Sequent::Core::BaseEventHandler
|
7
|
+
#
|
8
|
+
# on MyEvent do |event|
|
9
|
+
# do_some_logic
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# You typically do not need to include this module in your classes. If you extend from
|
14
|
+
# Sequent::Core::AggregateRoot, Sequent::Core::BaseEventHandler or Sequent::Core::BaseCommandHandler
|
15
|
+
# you will get this functionality for free.
|
16
|
+
#
|
17
|
+
module SelfApplier
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
|
21
|
+
def on(*message_classes, &block)
|
22
|
+
@message_mapping ||= {}
|
23
|
+
message_classes.each { |message_class| @message_mapping[message_class] = block }
|
24
|
+
end
|
25
|
+
|
26
|
+
def message_mapping
|
27
|
+
@message_mapping || {}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.included(host_class)
|
32
|
+
host_class.extend(ClassMethods)
|
33
|
+
end
|
34
|
+
|
35
|
+
def handle_message(message)
|
36
|
+
handler = self.class.message_mapping[message.class]
|
37
|
+
self.instance_exec(message, &handler) if handler
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Sequent
|
2
|
+
module Core
|
3
|
+
module Helpers
|
4
|
+
#
|
5
|
+
# You typically do not need to include this module in your classes. If you extend from
|
6
|
+
# Sequent::Core::ValueObject, Sequent::Core::Event or Sequent::Core::BaseCommand you will
|
7
|
+
# get this functionality for free.
|
8
|
+
#
|
9
|
+
module StringSupport
|
10
|
+
def to_s
|
11
|
+
s = "#{self.class.name}: "
|
12
|
+
self.instance_variables.each do |name|
|
13
|
+
value = self.instance_variable_get("#{name}")
|
14
|
+
s += "#{name}=[#{value}], "
|
15
|
+
end
|
16
|
+
"{" + s.chomp(", ") + "}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|