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