command_post 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +28 -0
- data/README.md +310 -0
- data/Rakefile +1 -0
- data/command_post.gemspec +21 -0
- data/lib/command_post/command/command.rb +375 -0
- data/lib/command_post/command_post.rb +5 -0
- data/lib/command_post/db/connection.rb +12 -0
- data/lib/command_post/db/postgresql.sql +45 -0
- data/lib/command_post/event_sourcing/aggregate.rb +82 -0
- data/lib/command_post/event_sourcing/aggregate_event.rb +107 -0
- data/lib/command_post/identity/identity.rb +75 -0
- data/lib/command_post/identity/sequence_generator.rb +44 -0
- data/lib/command_post/persistence/aggregate_pointer.rb +18 -0
- data/lib/command_post/persistence/auto_load.rb +70 -0
- data/lib/command_post/persistence/data_validation.rb +113 -0
- data/lib/command_post/persistence/persistence.rb +157 -0
- data/lib/command_post/persistence/schema_validation.rb +137 -0
- data/lib/command_post/util/hash_util.rb +23 -0
- data/lib/command_post/util/string_util.rb +18 -0
- data/lib/command_post/version.rb +3 -0
- data/spec/command_post/command/command_spec.rb +0 -0
- data/spec/command_post/identity/identity_lookup_value_aggregate_id_spec.rb +89 -0
- data/spec/command_post/identity/identity_lookup_value_checksum_spec.rb +108 -0
- data/spec/command_post/identity/identity_lookup_value_field_spec.rb +83 -0
- data/spec/command_post/persistence/data_validation_spec.rb +74 -0
- data/spec/command_post/persistence/nested_remote_spec.rb +0 -0
- data/spec/command_post/persistence/schema_validation_spec.rb +269 -0
- data/spec/command_post/require.rb +9 -0
- data/spec/spec_helper.rb +0 -0
- metadata +112 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'sequel'
|
3
|
+
|
4
|
+
|
5
|
+
require_relative '../db/connection.rb'
|
6
|
+
|
7
|
+
|
8
|
+
module CommandPost
|
9
|
+
|
10
|
+
class SequenceGenerator
|
11
|
+
|
12
|
+
|
13
|
+
def self.aggregate_id
|
14
|
+
@@DB ||= Connection.db_cqrs
|
15
|
+
val = 0
|
16
|
+
@@DB.fetch("SELECT nextval('aggregate');") do |row|
|
17
|
+
val = row[row.keys.first]
|
18
|
+
end
|
19
|
+
val
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.transaction_id
|
23
|
+
@@DB ||= Connection.db_cqrs
|
24
|
+
val = 0
|
25
|
+
@@DB.fetch("SELECT nextval('transaction');") do |row|
|
26
|
+
val = row[row.keys.first]
|
27
|
+
end
|
28
|
+
val
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.misc
|
32
|
+
@@DB ||= Connection.db_cqrs
|
33
|
+
val = 0
|
34
|
+
@@DB.fetch("SELECT nextval('misc');") do |row|
|
35
|
+
val = row[row.keys.first]
|
36
|
+
end
|
37
|
+
val
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
module CommandPost
|
3
|
+
|
4
|
+
|
5
|
+
class AggregatePointer < Hash
|
6
|
+
attr_accessor :aggregate_type, :aggregate_id
|
7
|
+
|
8
|
+
def initialize hash
|
9
|
+
@aggregate_type = hash[:aggregate_type]
|
10
|
+
@aggregate_id = hash[:aggregate_id]
|
11
|
+
self[:aggregate_type] = hash[:aggregate_type]
|
12
|
+
self[:aggregate_id] = hash[:aggregate_id]
|
13
|
+
self[:class_name] = 'AggregatePointer'
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require_relative '../util/hash_util'
|
2
|
+
|
3
|
+
module CommandPost
|
4
|
+
|
5
|
+
module AutoLoad
|
6
|
+
|
7
|
+
def auto_load_fields
|
8
|
+
|
9
|
+
schema_fields.select {|key, value| value[:auto_load] == true }.keys
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def local_peristent_fields
|
14
|
+
|
15
|
+
schema_fields.select {|key, value| value[:location] == :local && value[:type].superclass == Persistence }.keys
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def populate_auto_load_fields
|
20
|
+
auto_load_fields.select {|x| @data.keys.include? x}.each do |field|
|
21
|
+
if @data[field].class == Array
|
22
|
+
if schema_fields[field][:location] == :remote
|
23
|
+
array_of_objects = Array.new
|
24
|
+
array_of_pointers = @data[field]
|
25
|
+
array_of_pointers.each do |pointer|
|
26
|
+
if pointer.respond_to? :aggregate_pointer
|
27
|
+
pointer = pointer.aggregate_pointer
|
28
|
+
end
|
29
|
+
obj = Aggregate.get_by_aggregate_id(Object.const_get(pointer[:aggregate_type]), pointer[:aggregate_id])
|
30
|
+
array_of_objects << obj
|
31
|
+
end
|
32
|
+
array_of_pointers.clear
|
33
|
+
@data[field].clear
|
34
|
+
@data[field] += array_of_objects
|
35
|
+
end
|
36
|
+
else
|
37
|
+
@data[field] = Aggregate.get_by_aggregate_id( schema_fields[field][:type], @data[field][:aggregate_id])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def to_pretty_pp
|
44
|
+
len = 0
|
45
|
+
result = ''
|
46
|
+
@data.keys.map{|key| len = key.length if (key.length > len) }
|
47
|
+
|
48
|
+
@data.keys.reject{|key| /aggregate/.match key}.each do |key|
|
49
|
+
label = "%#{len}s :" % key
|
50
|
+
if auto_load_fields.include? key
|
51
|
+
result += "\n#{label} (remote object)"
|
52
|
+
result += (@data[key]).to_s
|
53
|
+
else
|
54
|
+
result += "#{label} #{@data[key]}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
result
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def populate_local_persistent_objects
|
62
|
+
local_peristent_fields.each do |field|
|
63
|
+
klass = schema_fields[field][:type]
|
64
|
+
@data[field] = klass.load_from_hash klass, HashUtil.symbolize_keys(@data[field])
|
65
|
+
@data[field].populate_local_persitent_objects
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
module CommandPost
|
5
|
+
|
6
|
+
module DataValidation
|
7
|
+
|
8
|
+
def empty?
|
9
|
+
|
10
|
+
@data.nil? || @data == {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
|
15
|
+
verify_data.length == 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def data_errors
|
19
|
+
|
20
|
+
verify_data
|
21
|
+
end
|
22
|
+
|
23
|
+
def verify_data
|
24
|
+
|
25
|
+
errors = Array.new
|
26
|
+
|
27
|
+
schema_fields.each do |field_name, field_info|
|
28
|
+
if missing_required_field(field_name, field_info)
|
29
|
+
|
30
|
+
errors << "#{self.class}:#{field_name} - is a required field."
|
31
|
+
end
|
32
|
+
if data_type_does_not_match_declaration(field_name, field_info)
|
33
|
+
|
34
|
+
errors << "#{self.class}: #{field_name}: expected type: #{field_info[:type].name}, but received type #{@data[field_name].class.name}."
|
35
|
+
end
|
36
|
+
if allowed_values_declared_but_array_of_values_not_supplied(field_name, field_info)
|
37
|
+
|
38
|
+
errors << "#{self.class}: #{field_name}: expected type: #{field_info[:type].name}, but received type #{@data[field_name].class.name}."
|
39
|
+
end
|
40
|
+
if value_not_among_the_list_of_allowed_values(field_name, field_info)
|
41
|
+
|
42
|
+
errors << "#{self.class}: #{field_name}: The value supplied was not in the list of acceptable values."
|
43
|
+
end
|
44
|
+
if type_is_array_but_keyword___of___not_supplied(field_name, field_info)
|
45
|
+
|
46
|
+
errors << "#{self.class}: #{field_name}: is an Array, but the ':of' keyword was not set to declare the class type for objects in the array."
|
47
|
+
end
|
48
|
+
if accepted_values_supplied_but_they_are_not_all_the_same_type(field_name, field_info)
|
49
|
+
|
50
|
+
errors << "#{self.class}: #{field_name} is an Array and all objects should be of type #{expected_type} but one object was of type #{object_in_array.class}."
|
51
|
+
end
|
52
|
+
if field_is_array_of_remote_objects_but_array_has_values_other_than_persistence_or_identity(field_name, field_info)
|
53
|
+
|
54
|
+
errors << "#{self.class}: #{field_name} is an Array and all objects should be of type #{expected_type} but one object was of type #{object_in_array.class} or of AggregatePointer."
|
55
|
+
end
|
56
|
+
end
|
57
|
+
errors
|
58
|
+
end
|
59
|
+
|
60
|
+
def missing_required_field field_name, field_info
|
61
|
+
puts "field_name is #{field_name} and has class of #{field_name.class}"
|
62
|
+
@data.keys.include?(field_name)==false && field_info[:required] == true
|
63
|
+
end
|
64
|
+
|
65
|
+
def data_type_does_not_match_declaration field_name, field_info
|
66
|
+
|
67
|
+
@data[field_name] != nil && (@data[field_name].class != field_info[:type])
|
68
|
+
end
|
69
|
+
|
70
|
+
def allowed_values_declared_but_array_of_values_not_supplied field_name, field_info
|
71
|
+
|
72
|
+
(@data[field_name] != nil) && (@data[field_name].class != Array) && (field_info[:allowed_values]) && (field_info[:allowed_values].class != Array)
|
73
|
+
end
|
74
|
+
|
75
|
+
def value_not_among_the_list_of_allowed_values field_name, field_info
|
76
|
+
|
77
|
+
(@data[field_name] != nil) && (@data[field_name].class != Array) && (field_info[:allowed_values]) && (field_info[:allowed_values].include?(@data[field_name]) == false )
|
78
|
+
end
|
79
|
+
|
80
|
+
def type_is_array_but_keyword___of___not_supplied field_name, field_info
|
81
|
+
|
82
|
+
(@data[field_name.to_s] != nil) && (@data[field_name].class == Array) && (field_info[:type] == Array) && (field_info[:local] == true) && ((!field_info.keys.include?(:of)) || field_info[:of].nil?)
|
83
|
+
end
|
84
|
+
|
85
|
+
def accepted_values_supplied_but_they_are_not_all_the_same_type(field_name, field_info)
|
86
|
+
if (@data[field_name.to_s] != nil) && (@data[field_name].class == Array) && (field_info[:type] == Array) && (field_info[:local] == true)
|
87
|
+
if field_info[:location] == :local
|
88
|
+
expected_type = field_info[:of]
|
89
|
+
@data[field_name.to_s].each do |object_in_array|
|
90
|
+
if object_in_array.class != expected_type
|
91
|
+
return true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def field_is_array_of_remote_objects_but_array_has_values_other_than_persistence_or_identity(field_name, field_info)
|
99
|
+
if (@data[field_name.to_s] != nil) && (@data[field_name].class == Array) && (field_info[:type] == Array) && (field_info[:location] == :remote)
|
100
|
+
# OK, :of was declared, forge ahead and check any objects in the array for the correct type (these are all local, no worries about aggregate pointer)
|
101
|
+
expected_type = field_info[:of]
|
102
|
+
@data[field_name.to_s].each do |object_in_array|
|
103
|
+
if (object_in_array.class != expected_type) && (object_in_array.class != AggregatePointer)
|
104
|
+
return ["#{self.class}: #{field_name} is an Array and all objects should be of type #{expected_type} but one object was of type #{object_in_array.class} or of AggregatePointer."]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
|
2
|
+
require_relative '../event_sourcing/aggregate.rb'
|
3
|
+
require_relative '../event_sourcing/aggregate_event.rb'
|
4
|
+
require_relative '../persistence/persistence.rb'
|
5
|
+
require_relative './schema_validation.rb'
|
6
|
+
require_relative './data_validation.rb'
|
7
|
+
require_relative './auto_load.rb'
|
8
|
+
require_relative '../command/command.rb'
|
9
|
+
|
10
|
+
module CommandPost
|
11
|
+
|
12
|
+
class Persistence
|
13
|
+
include SchemaValidation
|
14
|
+
include DataValidation
|
15
|
+
include AutoLoad
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
|
19
|
+
@@fields ||= Hash.new
|
20
|
+
@aggregate_info_set = false
|
21
|
+
@data = Hash.new
|
22
|
+
self.class.init_schema self.class.schema
|
23
|
+
Command.auto_generate self.class
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def schema_fields
|
28
|
+
|
29
|
+
@@fields[self.class]
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def set_data data_hash
|
34
|
+
@data = data_hash
|
35
|
+
if @aggregate_info_set == false
|
36
|
+
@aggregate_info_set = true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def get_data name
|
42
|
+
if schema_fields[name][:location] == :local
|
43
|
+
if schema_fields[name][:type] == DateTime
|
44
|
+
return DateHelper.parse_date_time(@data[name])
|
45
|
+
elsif schema_fields[name][:type] == Time
|
46
|
+
DateHelper.parse_time(@data[name])
|
47
|
+
else
|
48
|
+
return @data[name]
|
49
|
+
end
|
50
|
+
else
|
51
|
+
if @data[name].class == Hash && @data[name].keys == [:aggregate_type,:aggregate_id]
|
52
|
+
Aggregate.get_by_aggregate_id(schema_fields[name][:type], @data[name][:aggregate_id])
|
53
|
+
else
|
54
|
+
@data[name]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def method_missing(nm, *args)
|
61
|
+
|
62
|
+
name = nm.to_s
|
63
|
+
if name.end_with?('=') == false
|
64
|
+
|
65
|
+
|
66
|
+
if @data.keys.include? nm
|
67
|
+
get_data nm
|
68
|
+
else
|
69
|
+
if schema_fields.keys.include? nm
|
70
|
+
return nil
|
71
|
+
else
|
72
|
+
begin
|
73
|
+
super
|
74
|
+
rescue
|
75
|
+
puts "#{nm} is not a defined field in #{self}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
else
|
80
|
+
nm = name.gsub(/\=/,'').to_sym
|
81
|
+
@data[nm] = args.first
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def aggregate_type
|
87
|
+
self.class
|
88
|
+
#@data[:aggregate_info][:aggregate_type]
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
def to_h
|
93
|
+
|
94
|
+
@data
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def self.all
|
99
|
+
|
100
|
+
Aggregate.where(self)
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
def self.init_schema fields
|
105
|
+
schema_error_messages = SchemaValidation.validate_schema(fields)
|
106
|
+
if schema_error_messages.length > 0
|
107
|
+
raise ArgumentError, "The schema for #{self} had the following error(s): #{pp schema_error_messages}"
|
108
|
+
end
|
109
|
+
@@fields[self] ||= fields
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
def self.load_from_hash the_class, string_hash
|
114
|
+
|
115
|
+
data_hash = HashUtil.symbolize_keys(string_hash)
|
116
|
+
|
117
|
+
|
118
|
+
if (data_hash.keys.include?(:aggregate_info) == false) && (the_class.included_modules.include?(CommandPost::Identity) == true)
|
119
|
+
data_hash[:aggregate_info] = Hash.new
|
120
|
+
data_hash[:aggregate_info][:aggregate_type] = the_class.to_s
|
121
|
+
data_hash[:aggregate_info][:version] = 1
|
122
|
+
data_hash[:aggregate_info][:aggregate_id] = SequenceGenerator.aggregate_id
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
object = the_class.new
|
127
|
+
object.set_data data_hash
|
128
|
+
object.populate_auto_load_fields #unless self.bypass_auto_load == true
|
129
|
+
object.populate_local_persistent_objects
|
130
|
+
if (the_class.included_modules.include?(CommandPost::Identity) == true)
|
131
|
+
object.set_aggregate_lookup_value
|
132
|
+
end
|
133
|
+
object
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
def self.bypass_auto_load
|
138
|
+
@@bypass ||= Hash.new
|
139
|
+
@@bypass[self] ||= false
|
140
|
+
@@bypass[self]
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
def self.bypass_auto_load=(value)
|
145
|
+
@@bypass ||= Hash.new
|
146
|
+
@@bypass[self]=value
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
def self.upcase? field_name
|
151
|
+
raise ArgumentError ,"field not found " if (schema.keys.include?(field_name) == false)
|
152
|
+
field_info = schema[field_name]
|
153
|
+
field_info.keys.include?(:upcase) ? field_info[:upcase] : false
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
module SchemaValidation
|
4
|
+
|
5
|
+
def self.validate_schema fields
|
6
|
+
|
7
|
+
errors = Array.new
|
8
|
+
fields.keys.each do |field_name|
|
9
|
+
errors += self.validate_field_name field_name
|
10
|
+
errors += self.validate_keywords field_name, fields[field_name]
|
11
|
+
errors += self.validate_required field_name, fields[field_name] if fields[field_name].keys.include? :required
|
12
|
+
errors += self.validate_type field_name, fields[field_name] if fields[field_name].keys.include? :type
|
13
|
+
errors += self.validate_location field_name, fields[field_name] if fields[field_name].keys.include? :location
|
14
|
+
errors += self.validate_auto_load field_name, fields[field_name] if fields[field_name].keys.include? :auto_load
|
15
|
+
errors += self.validate_allowed_values field_name, fields[field_name] if fields[field_name].keys.include? :allowed_values
|
16
|
+
end
|
17
|
+
errors
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.validate_keywords field_name, field_info
|
21
|
+
errors = Array.new
|
22
|
+
|
23
|
+
if field_name == :lookup
|
24
|
+
errors = self.validate_lookup field_name, field_info
|
25
|
+
else
|
26
|
+
keywords = [:required, :type, :of, :location, :auto_load, :allowed_values, :upcase ]
|
27
|
+
field_info.keys.each do |key|
|
28
|
+
if keywords.include?(key)==false
|
29
|
+
errors << "Field Name: #{field_name} : #{key} is an invalid keyword."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
errors
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.validate_lookup field_name, field_info
|
37
|
+
errors = Array.new
|
38
|
+
keywords = [:use ]
|
39
|
+
field_info.keys.each do |key|
|
40
|
+
if keywords.include?(key)==false
|
41
|
+
errors << "Lookup Field has invalid keyword '#{key}'."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
errors
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def self.validate_allowed_values field_name, field_info
|
49
|
+
if field_info[:allowed_values].class != Array
|
50
|
+
return ["Field Name '#{field_name}' : :allowed_values was specified but the allowed_values must be contained in an Array." ]
|
51
|
+
end
|
52
|
+
types = Hash.new
|
53
|
+
|
54
|
+
field_info.each{|x| types[x.type] = 0 }
|
55
|
+
if types.keys.count != 1
|
56
|
+
return ["Field Name '#{field_name}' : :allowed_values the values were not all of the same type - or no values were specified. " ]
|
57
|
+
end
|
58
|
+
[]
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def self.validate_auto_load field_name, field_info
|
63
|
+
if [true,false].include?(field_info[:auto_load])==false
|
64
|
+
return ["Field Name '#{field_name}' : :auto_load was specified with an invalid value. Must be 'true' or 'false'." ]
|
65
|
+
end
|
66
|
+
if (field_info[:type] == Array)
|
67
|
+
if !field_info[:of].kind_of?(CommandPost::Persistence) == false
|
68
|
+
return ["Field Name '#{field_name}' : When :auto_load is true and :type is Array, then :of must be a kind_of CommandPost::Persistence." ]
|
69
|
+
end
|
70
|
+
else
|
71
|
+
if field_info[:type].superclass.to_s != 'CommandPost::Persistence'
|
72
|
+
return ["Field Name '#{field_name}' : When :auto_load is true then :type must be kind_of CommandPost::Persistence." ]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
[]
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def self.validate_location field_name, field_info
|
80
|
+
if [:local,:remote].include?(field_info[:location]) == false
|
81
|
+
return ["Field Name '#{field_name}' : :location was specified with an invalid value. Must be ':remote' or ':local'." ]
|
82
|
+
end
|
83
|
+
if field_info[:location] == :remote
|
84
|
+
if (field_info[:type] == Array)
|
85
|
+
if !field_info[:of]
|
86
|
+
return ["Field Name '#{field_name}' : :type is Array, but :of is not present. Use ':of => <type>' to decare the type contained by Array." ]
|
87
|
+
end
|
88
|
+
if field_info[:of].included_modules.include?(CommandPost::Identity) == false
|
89
|
+
return ["Field Name '#{field_name}' : When :location is :remote and :type is Array, then :of must be a type that includes module CommandPost::Identity." ]
|
90
|
+
end
|
91
|
+
else
|
92
|
+
if field_info[:type].included_modules.include?(CommandPost::Identity) == false
|
93
|
+
return ["Field Name '#{field_name}' : When :location is :remote then :type must be a type that includes module CommandPost::Identity." ]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
else
|
97
|
+
# location is :local
|
98
|
+
if (field_info[:type] == Array)
|
99
|
+
|
100
|
+
if field_info[:of].included_modules.include?(CommandPost::Identity)
|
101
|
+
return ["Field Name '#{field_name}' : When :location is :local and :type is Array, then :of cannot be an instance of CommandPost::Identity." ]
|
102
|
+
end
|
103
|
+
else
|
104
|
+
if field_info[:type].included_modules.include?(CommandPost::Identity)
|
105
|
+
return ["Field Name '#{field_name}' : When :location is :local then :type cannot be an instance of CommandPost::Identity." ]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
[]
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
def self.validate_type field_name, field_info
|
114
|
+
if (field_info[:type] == Array) && (field_info.keys.include?(:of) == false)
|
115
|
+
return ["Field Name '#{field_name}' : :type is Array, but :of is not present. Use ':of => <type>' to decare the type contained by Array." ]
|
116
|
+
end
|
117
|
+
[]
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
def self.validate_required field_name, field_info
|
122
|
+
if [true,false].include?(field_info[:required]) == false
|
123
|
+
return ["Field Name '#{field_name}' : :required was specified with an invalid value. Must be 'true' or 'false'." ]
|
124
|
+
end
|
125
|
+
[]
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
def self.validate_field_name field_name
|
130
|
+
if field_name.class != Symbol
|
131
|
+
return ["Field Name '#{field_name}' : :field_name must be a Symbol." ]
|
132
|
+
end
|
133
|
+
[]
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
class HashUtil
|
5
|
+
|
6
|
+
|
7
|
+
def self.symbolize_keys(hash)
|
8
|
+
hash.inject({}){|result, (key, value)|
|
9
|
+
new_key = case key
|
10
|
+
when String then key.to_sym
|
11
|
+
else key
|
12
|
+
end
|
13
|
+
new_value = case value
|
14
|
+
when Hash then symbolize_keys(value)
|
15
|
+
else value
|
16
|
+
end
|
17
|
+
result[new_key] = new_value
|
18
|
+
result
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module CommandPost
|
4
|
+
class StringUtil
|
5
|
+
|
6
|
+
def self.to_camel_case(field, upcase)
|
7
|
+
string = field.to_s
|
8
|
+
upcase == true ? string.upcase : string.split('_').collect{|x| x.slice(0,1).capitalize + x.slice(1..-1) }.join()
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.to_label(field, upcase)
|
12
|
+
string = field.to_s
|
13
|
+
upcase == true ? string.upcase : string.split('_').collect{|x| x.slice(0,1).capitalize + x.slice(1..-1) }.join(' ')
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
File without changes
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../command_post/require')
|
2
|
+
|
3
|
+
|
4
|
+
class Test003Person < CommandPost::Persistence
|
5
|
+
include CommandPost::Identity
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
end
|
10
|
+
def self.schema
|
11
|
+
fields = Hash.new
|
12
|
+
fields[ :first_name ] = { :required => true, :type => String, :location => :local }
|
13
|
+
fields[ :last_name ] = { :required => true, :type => String, :location => :local }
|
14
|
+
fields[ :ssn ] = { :required => true, :type => String, :location => :local }
|
15
|
+
fields[ :lookup ] = { :use => :aggregate_id }
|
16
|
+
fields
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
describe CommandPost::Identity do
|
23
|
+
it 'as the name suggestions, provides the means for objects to have an identity that is used in retrieving it from the database' do
|
24
|
+
|
25
|
+
params = Hash.new # like a web request...
|
26
|
+
params['first_name'] = 'John' #hash key is a string to mimic a web post/put
|
27
|
+
params['last_name'] = 'Doe' #hash key is a string to mimic a web post/put
|
28
|
+
params['ssn'] = "%09d" % CommandPost::SequenceGenerator.misc #hash key is a string to mimic a web post/put
|
29
|
+
|
30
|
+
|
31
|
+
#----------------------------------------------------------------
|
32
|
+
# The code below will eventually be replaced by the
|
33
|
+
# 'handle' method of the CommandXXXXXX class.
|
34
|
+
#----------------------------------------------------------------
|
35
|
+
|
36
|
+
object = Test003Person.load_from_hash Test003Person, params
|
37
|
+
event = CommandPost::AggregateEvent.new
|
38
|
+
event.aggregate_id = object.aggregate_id
|
39
|
+
event.object = object
|
40
|
+
event.aggregate_type = Test003Person
|
41
|
+
event.event_description = 'hired'
|
42
|
+
event.user_id = 'test'
|
43
|
+
event.publish
|
44
|
+
|
45
|
+
|
46
|
+
#----------------------------------------------------------------
|
47
|
+
# Retrieve the object by both aggregate_id and aggregate_lookup_value
|
48
|
+
# Both ways should retrieve the same object and the fields of both
|
49
|
+
# should match the original values used to create the object.
|
50
|
+
#----------------------------------------------------------------
|
51
|
+
|
52
|
+
saved_person = CommandPost::Aggregate.get_by_aggregate_id Test003Person, event.aggregate_id
|
53
|
+
saved_person2 = CommandPost::Aggregate.get_aggregate_by_lookup_value Test003Person, saved_person.aggregate_lookup_value
|
54
|
+
|
55
|
+
|
56
|
+
params['first_name'].must_equal saved_person.first_name
|
57
|
+
params['last_name'].must_equal saved_person.last_name
|
58
|
+
params['ssn'].must_equal saved_person.ssn
|
59
|
+
|
60
|
+
params['first_name'].must_equal saved_person2.first_name
|
61
|
+
params['last_name'].must_equal saved_person2.last_name
|
62
|
+
params['ssn'].must_equal saved_person2.ssn
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
|