sequent 0.1.2 → 0.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ffdbe1d8e772c148815f98b0d7baa96f32dd5870
4
- data.tar.gz: 3b7ed01b18a23c5d7b8522a4b5d0c1af311be02f
3
+ metadata.gz: 2a1582cc8d34049b1e10ed9c90391e3e7167c1ff
4
+ data.tar.gz: 5960fdac4834a4bb4f96b1643e6885da90f79587
5
5
  SHA512:
6
- metadata.gz: 5ec2840094aa3f915222047623d3f570563efa800736883c9d07b724773b0948b74602d26560337bb20455fde183a066181cbe594959ca52ebd1d73fea6a0f9e
7
- data.tar.gz: 60ac65d32eda16ce7db21cb51ba0a61b6b741b86636185569b1683c76a983c35d6ca806a744befdd195df997ecb3b8df481fd17a15e312d648a7fa816d3127a7
6
+ metadata.gz: 6f8ac133e7cbf158f48fe175527b4093c961b3637ea4a5b04d8373caeb2915dbc0ffede971c4f2a6a5d34bbc470e8cd3d9991ebaac784afd73ec610212f94be6
7
+ data.tar.gz: 1fdbf2610073c2731dfda81416a39c6d40eed6cdf4eed05ab0ac5c5813078d68c82a8a28bd8338c0ed06ef98ef0f2bc9f098c185ce6af4967e6f9ce9a900b6e1
@@ -17,6 +17,7 @@ module Sequent
17
17
  Sequent::Core::Helpers::EqualSupport,
18
18
  Sequent::Core::Helpers::ParamSupport,
19
19
  Sequent::Core::Helpers::Mergable
20
+ include Sequent::Core::Helpers::TypeConversionSupport
20
21
 
21
22
  attrs created_at: DateTime
22
23
 
@@ -71,14 +71,10 @@ module Sequent
71
71
  commands.each do |command|
72
72
  @filters.each { |filter| filter.execute(command) }
73
73
 
74
- if command.valid?
75
- @command_handlers.each do |command_handler|
76
- command_handler.handle_message command if command_handler.handles_message? command
77
- end
78
- end
79
-
80
- @repository.commit(command)
81
- raise CommandNotValid.new(command) unless command.validation_errors.empty?
74
+ raise CommandNotValid.new(command) unless command.valid?
75
+ parsed_command = command.parse_attrs_to_correct_types
76
+ @command_handlers.select { |h| h.handles_message?(parsed_command) }.each { |h| h.handle_message parsed_command }
77
+ @repository.commit(parsed_command)
82
78
  end
83
79
  end
84
80
  ensure
@@ -0,0 +1,54 @@
1
+ class Symbol
2
+
3
+ def self.deserialize_from_json(value)
4
+ value.blank? ? nil : value.try(:to_sym)
5
+ end
6
+
7
+ end
8
+
9
+ class String
10
+
11
+ def self.deserialize_from_json(value)
12
+ value
13
+ end
14
+
15
+ end
16
+
17
+ class Integer
18
+
19
+ def self.deserialize_from_json(value)
20
+ value.blank? ? nil : value.to_i
21
+ end
22
+
23
+ end
24
+
25
+ class Boolean
26
+
27
+ def self.deserialize_from_json(value)
28
+ value.nil? ? nil : (value.present? ? value : false)
29
+ end
30
+
31
+ end
32
+
33
+ class Date
34
+
35
+ def self.deserialize_from_json(value)
36
+ value.blank? ? nil : Date.iso8601(value.dup)
37
+ end
38
+
39
+ end
40
+
41
+ class DateTime
42
+
43
+ def self.deserialize_from_json(value)
44
+ value.blank? ? nil : DateTime.iso8601(value.dup)
45
+ end
46
+
47
+ end
48
+
49
+ class Array
50
+
51
+ def self.deserialize_from_json(value)
52
+ value
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ module Sequent
2
+ module Core
3
+ module Helpers
4
+ class ArrayWithType
5
+ attr_accessor :item_type
6
+
7
+ def initialize(item_type)
8
+ raise "needs a item_type" unless item_type
9
+ @item_type = item_type
10
+ end
11
+
12
+ def deserialize_from_json(value)
13
+ value.nil? ? nil : value.map { |item| item_type.deserialize_from_json(item) }
14
+ end
15
+
16
+ def to_s
17
+ "Sequent::Core::Helpers::ArrayWithType.new(#{item_type})"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -23,13 +23,31 @@ module Sequent
23
23
  associations.each do |association|
24
24
  next unless association # since ruby 2.0...?
25
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])
26
+ if value && incorrect_type?(value, record, association)
27
27
  record.errors[association] = "is not of type #{record.attributes[association]}"
28
28
  elsif value && value.kind_of?(Array)
29
- record.errors[association] = "is invalid" if value.any? { |v| not v.valid? }
29
+ item_type = record.class.type_for(association).item_type
30
+ record.errors[association] = "is invalid" if all_valid?(value, item_type)
30
31
  else
31
32
  record.errors[association] = "is invalid" if value && value.invalid?
33
+ end
34
+ end
35
+ end
36
+
37
+ private
32
38
 
39
+ def incorrect_type?(value, record, association)
40
+ !value.kind_of?(Array) && record.respond_to?(:attributes) && !value.kind_of?(record.attributes[association])
41
+ end
42
+
43
+ def all_valid?(value, item_type)
44
+ value.any? do |v|
45
+ if v.nil?
46
+ true
47
+ elsif v.respond_to? :valid?
48
+ not v.valid?
49
+ else
50
+ not Sequent::Core::Helpers::ValueValidators.for(item_type).valid_value?(v)
33
51
  end
34
52
  end
35
53
  end
@@ -1,47 +1,7 @@
1
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
2
+ require_relative '../ext/ext'
3
+ require_relative 'array_with_type'
4
+ require_relative 'default_validators'
45
5
 
46
6
  module Sequent
47
7
  module Core
@@ -73,13 +33,30 @@ module Sequent
73
33
  end
74
34
  end
75
35
 
36
+ def type_for(name)
37
+ @types.find { |k, _| k == name }.last
38
+ end
39
+
76
40
  def attrs(args)
77
41
  @types ||= {}
78
42
  @types.merge!(args)
79
- args.each do |attribute, _|
43
+ associations = []
44
+ args.each do |attribute, type|
80
45
  attr_accessor attribute
81
- end
46
+ if included_modules.include?(Sequent::Core::Helpers::TypeConversionSupport)
47
+ Sequent::Core::Helpers::DefaultValidators.for(type).add_validations_for(self, attribute)
48
+ end
82
49
 
50
+ if type.class == Sequent::Core::Helpers::ArrayWithType
51
+ associations << attribute
52
+ elsif included_modules.include?(ActiveModel::Validations) &&
53
+ type.included_modules.include?(Sequent::Core::Helpers::AttributeSupport)
54
+ associations << attribute
55
+ end
56
+ end
57
+ if included_modules.include?(ActiveModel::Validations) && associations.present?
58
+ validates_with Sequent::Core::Helpers::AssociationValidator, associations: associations
59
+ end
83
60
  # Generate method that sets all defined attributes based on the attrs hash.
84
61
  class_eval <<EOS
85
62
  def update_all_attributes(attrs)
@@ -148,57 +125,8 @@ EOS
148
125
  prefix ? HashWithIndifferentAccess[result.map { |k, v| ["#{prefix}_#{k}", v] }] : result
149
126
  end
150
127
 
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
128
  end
185
129
 
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
130
 
203
131
  end
204
132
  end
@@ -0,0 +1,20 @@
1
+ require 'active_model'
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ # Validates DateTimes
7
+ # Automatically included when using a
8
+ #
9
+ # attrs value: DateTime
10
+ class DateTimeValidator < ActiveModel::EachValidator
11
+ def validate_each(subject, attribute, value)
12
+ return if value.is_a?(DateTime)
13
+ DateTime.deserialize_from_json(value)
14
+ rescue
15
+ subject.errors.add attribute, :invalid_date_time
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ require 'active_model'
2
+ require_relative 'value_validators'
3
+
4
+ module Sequent
5
+ module Core
6
+ module Helpers
7
+ # Validates Dates
8
+ # Automatically included when using a
9
+ #
10
+ # attrs value: Date
11
+ class DateValidator < ActiveModel::EachValidator
12
+ def validate_each(subject, attribute, value)
13
+ subject.errors.add attribute, :invalid_date unless Sequent::Core::Helpers::ValueValidators.for(Date).valid_value?(value)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ module Sequent
2
+ module Core
3
+ module Helpers
4
+ class DefaultValidators
5
+ VALIDATORS = {
6
+ Integer => ->(klass, field) { klass.validates_numericality_of field, only_integer: true, allow_nil: true, allow_blank: true },
7
+ Date => ->(klass, field) { klass.validates field, "sequent::Core::Helpers::Date" => true },
8
+ DateTime => ->(klass, field) { klass.validates field, "sequent::Core::Helpers::DateTime" => true }
9
+ }
10
+
11
+ def self.for(type)
12
+ new(type)
13
+ end
14
+
15
+ def initialize(type)
16
+ @type = type
17
+ end
18
+
19
+ def add_validations_for(klass, field)
20
+ validator = VALIDATORS[@type]
21
+ validator.call(klass, field) if validator
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,9 +1,13 @@
1
1
  require_relative 'uuid_helper'
2
2
  require_relative 'copyable'
3
+ require_relative 'value_validators'
4
+ require_relative 'string_to_value_parsers'
3
5
  require_relative 'attribute_support'
4
6
  require_relative 'equal_support'
5
7
  require_relative 'param_support'
6
8
  require_relative 'mergable'
7
9
  require_relative 'association_validator'
8
10
  require_relative 'string_support'
9
- require_relative 'boolean_support'
11
+ require_relative 'type_conversion_support'
12
+ require_relative 'date_validator'
13
+ require_relative 'date_time_validator'
@@ -22,8 +22,6 @@ module Sequent
22
22
  value = type.from_params(value)
23
23
  elsif type.is_a? Sequent::Core::Helpers::ArrayWithType
24
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
25
  end
28
26
  result.instance_variable_set(:"@#{attribute}", value)
29
27
  end
@@ -49,8 +47,10 @@ module Sequent
49
47
  value = value.as_params
50
48
  elsif value.kind_of?(Array)
51
49
  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
50
+ elsif value.is_a? DateTime
51
+ value = value.iso8601
52
+ elsif value.is_a? Date
53
+ value = value.strftime("%d-%m-%Y") # TODO Remove to TypeConverter
54
54
  end
55
55
  hash[field[0]] = value
56
56
  end
@@ -0,0 +1,73 @@
1
+ require_relative '../ext/ext'
2
+ require_relative 'array_with_type'
3
+
4
+ module Sequent
5
+ module Core
6
+ module Helpers
7
+ class StringToValueParsers
8
+ PARSERS = {
9
+ ::Symbol => ->(value) { Symbol.deserialize_from_json(value) },
10
+ ::String => ->(value) { value },
11
+ ::Integer => ->(value) { parse_to_integer(value) },
12
+ ::Boolean => ->(value) { parse_to_bool(value) },
13
+ ::Date => ->(value) { parse_to_date(value) },
14
+ ::DateTime => ->(value) { parse_to_date_time(value) },
15
+ ::Sequent::Core::Helpers::ArrayWithType => ->(values, type_in_array) { parse_array(values, type_in_array) }
16
+ }
17
+
18
+ def self.parse_to_integer(value)
19
+ Integer(value) unless value.blank?
20
+ end
21
+
22
+ def self.parse_to_bool(value)
23
+ if value.blank? && !(value.is_a?(TrueClass) || value.is_a?(FalseClass))
24
+ nil
25
+ else
26
+ (value.is_a?(TrueClass) || value == "true")
27
+ end
28
+ end
29
+
30
+ def self.parse_to_date(value)
31
+ return if value.blank?
32
+ value.is_a?(Date) ? value : Date.strptime(value, "%d-%m-%Y")
33
+ end
34
+
35
+ def self.parse_to_date_time(value)
36
+ value.is_a?(DateTime) ? value : DateTime.deserialize_from_json(value)
37
+ end
38
+
39
+ def self.parse_array(values, type_in_array)
40
+ values.map do |item|
41
+ if item.respond_to?(:parse_attrs_to_correct_types)
42
+ item.parse_attrs_to_correct_types
43
+ else
44
+ Sequent::Core::Helpers::StringToValueParsers.for(type_in_array).parse_from_string(item)
45
+ end
46
+ end
47
+ end
48
+
49
+ def self.for(klass)
50
+ new(klass)
51
+ end
52
+
53
+ def initialize(klass)
54
+ if klass.is_a? Sequent::Core::Helpers::ArrayWithType
55
+ @array_with_type = klass
56
+ @klass = klass.class
57
+ else
58
+ @klass = klass
59
+ end
60
+ end
61
+
62
+ def parse_from_string(value)
63
+ parser = PARSERS[@klass]
64
+ if @array_with_type
65
+ parser.call(value, @array_with_type.item_type)
66
+ else
67
+ parser.call(value)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,28 @@
1
+ require 'active_model'
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ # Will parse all values to the correct types.
7
+ # The raw values are typically posted from the web and are therefor mostly strings.
8
+ # To parse a raw value your class must have a parse_from_string method that returns the parsed values.
9
+ # See sequent/core/ext for currently supported classes.
10
+ module TypeConversionSupport
11
+ def parse_attrs_to_correct_types
12
+ the_copy = dup
13
+ the_copy.attributes.each do |name, type|
14
+ raw_value = the_copy.send("#{name}")
15
+ next if raw_value.nil?
16
+ if raw_value.respond_to?(:parse_attrs_to_correct_types)
17
+ the_copy.send("#{name}=", raw_value.parse_attrs_to_correct_types)
18
+ else
19
+ parsed_value = Sequent::Core::Helpers::StringToValueParsers.for(type).parse_from_string(raw_value)
20
+ the_copy.send("#{name}=", parsed_value)
21
+ end
22
+ end
23
+ the_copy
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,53 @@
1
+ require_relative '../ext/ext'
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ class ValueValidators
7
+ VALIDATORS = {
8
+ ::Symbol => ->(_) { true },
9
+ ::String => ->(value) { value.nil? || value.is_a?(String) },
10
+ ::Integer => ->(value) { valid_integer?(value) },
11
+ ::Boolean => ->(value) { valid_bool?(value) },
12
+ ::Date => ->(value) { valid_date?(value) },
13
+ ::DateTime => ->(value) { valid_date_time?(value) }
14
+ }
15
+
16
+ def self.valid_integer?(value)
17
+ value.blank? || Integer(value)
18
+ rescue
19
+ false
20
+ end
21
+
22
+ def self.valid_bool?(value)
23
+ return true if value.blank?
24
+ value.is_a?(TrueClass) || value.is_a?(FalseClass) || value == "true" || value == "false"
25
+ end
26
+
27
+ def self.valid_date?(value)
28
+ return true if value.blank?
29
+ return true if value.is_a?(Date)
30
+ return false unless value =~ /\d{2}-\d{2}-\d{4}/
31
+ !!Date.strptime(value, "%d-%m-%Y") rescue false
32
+ end
33
+
34
+ def self.valid_date_time?(value)
35
+ return true if value.blank?
36
+ value.is_a?(DateTime) || !!DateTime.iso8601(value.dup) rescue false
37
+ end
38
+
39
+ def self.for(klass)
40
+ new(klass)
41
+ end
42
+
43
+ def initialize(klass)
44
+ @klass = klass
45
+ end
46
+
47
+ def valid_value?(value)
48
+ VALIDATORS[@klass].call(value)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -33,6 +33,7 @@ module Sequent
33
33
  Sequent::Core::Helpers::ParamSupport,
34
34
  ActiveModel::Serializers::JSON,
35
35
  ActiveModel::Validations
36
+ include Sequent::Core::Helpers::TypeConversionSupport
36
37
 
37
38
  self.include_root_in_json=false
38
39
 
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Sequent
2
- VERSION = '0.1.2'
2
+ VERSION = '0.1.3'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lars Vonk
@@ -131,17 +131,24 @@ files:
131
131
  - lib/sequent/core/event.rb
132
132
  - lib/sequent/core/event_record.rb
133
133
  - lib/sequent/core/event_store.rb
134
+ - lib/sequent/core/ext/ext.rb
135
+ - lib/sequent/core/helpers/array_with_type.rb
134
136
  - lib/sequent/core/helpers/association_validator.rb
135
137
  - lib/sequent/core/helpers/attribute_support.rb
136
- - lib/sequent/core/helpers/boolean_support.rb
137
138
  - lib/sequent/core/helpers/copyable.rb
139
+ - lib/sequent/core/helpers/date_time_validator.rb
140
+ - lib/sequent/core/helpers/date_validator.rb
141
+ - lib/sequent/core/helpers/default_validators.rb
138
142
  - lib/sequent/core/helpers/equal_support.rb
139
143
  - lib/sequent/core/helpers/helpers.rb
140
144
  - lib/sequent/core/helpers/mergable.rb
141
145
  - lib/sequent/core/helpers/param_support.rb
142
146
  - lib/sequent/core/helpers/self_applier.rb
143
147
  - lib/sequent/core/helpers/string_support.rb
148
+ - lib/sequent/core/helpers/string_to_value_parsers.rb
149
+ - lib/sequent/core/helpers/type_conversion_support.rb
144
150
  - lib/sequent/core/helpers/uuid_helper.rb
151
+ - lib/sequent/core/helpers/value_validators.rb
145
152
  - lib/sequent/core/record_sessions/active_record_session.rb
146
153
  - lib/sequent/core/record_sessions/record_sessions.rb
147
154
  - lib/sequent/core/record_sessions/replay_events_session.rb
@@ -181,3 +188,4 @@ signing_key:
181
188
  specification_version: 4
182
189
  summary: Event sourcing framework for Ruby
183
190
  test_files: []
191
+ has_rdoc:
@@ -1,36 +0,0 @@
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