command_post 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|
+
|