sequent 0.1.2 → 0.1.3

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