jet_set 0.4.0 → 0.5.0

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
  SHA256:
3
- metadata.gz: 4c2b21527242c8574e604c1ece2b26ec71d59c7712b76cdfa9e88fdc6a47265b
4
- data.tar.gz: bdcba0c32be1c7db5a0b63f206dd7c5e354c30a9af14eb1b0b204a55ea4ce349
3
+ metadata.gz: 41fe70bc858f2949d8cf010b6af40066a84bdfdf481f0fef13dfdb49fca1de46
4
+ data.tar.gz: 02c77cd6a4741a0be81caa52e3c3b63b75722cddffbd289497a194701d60c447
5
5
  SHA512:
6
- metadata.gz: b6e155d9e2c20157beb3689764a42f8618159fb443b11c41d2ae81a5405fc3fadb55e31a562fcbf58fb0e06a78bf8611ea10eb36378c2b14e4c3e59c6e5b409b
7
- data.tar.gz: 076a5aa11cb87fa6773faca5d3b92e218e5e939a6808d1825486c8cc1c8428cbeb1b480fe152f69a3e6f09b9f5431b4ebbec70873c567a44e91c6439f8150120
6
+ metadata.gz: 99ca355278cad5427c3b69228806f7405e4725a2d2acf1c5902fdf488a7e2acf5a29ee509e93ace75cdae86ba6a596fa79493d848b62acff90977cf00a04e864
7
+ data.tar.gz: 4008d0a706a8c416d7958c4276b3a494f5190a41df6fc4d4e69b669edf9d5e2f97d4dda73091a3692659a526da060aead126b4c44c747b368bbfa1162c857d2d
data/README.md CHANGED
@@ -28,7 +28,7 @@ Open DB connection, see [Sequel docs](https://sequel.jeremyevans.net/rdoc/files/
28
28
  @connection = Sequel.connect('sqlite:/') # you can connect to any DB supported by Sequel
29
29
  ```
30
30
 
31
- Create a mapping of your model, a details described [here]:
31
+ Create a mapping of your model, details described [here]:
32
32
  ```ruby
33
33
  class Mapping
34
34
  def self.load_mapping
@@ -137,8 +137,41 @@ json = JSON.generate(data: result)
137
137
  In other words, following [CQS](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation) approach you can
138
138
  load your model for a command but not for a query.
139
139
 
140
+ ### Validation
141
+ Simple validation is optional feature provided by JetSet out of the box. To add this to your domain object you just need
142
+ to include module `JetSet::Validations` and use `validate` statements:
143
+
144
+ ```ruby
145
+ require 'jet_set/validations'
146
+
147
+ class User
148
+ include JetSet::Validations
149
+ validate :name, 'cannot be empty', -> (value) {!value.nil? && !value.empty?}
150
+ validate :email, type: :string, presence: true
151
+ validate :email, 'should be valid email address', -> (value) {value.match(...)}
152
+
153
+ def initialize(attrs = {})
154
+ @name = attrs[:name]
155
+ end
156
+ end
157
+ ```
158
+ JetSet uses such validations automatically on saving objects in the database. Also you can invoke validation manually,
159
+ i.e. in unit tests, using `validate!` method:
160
+
161
+ ```ruby
162
+ user = User.new(name: nil)
163
+ user.validate! # raises JetSet::ValidationError
164
+ ```
165
+
166
+ `JetSet::ValidationError` contains details regarding invalid items like:
167
+
168
+ ```ruby
169
+ error.invalid_items # => {name: 'cannot be empty'}
170
+ ```
171
+
172
+
173
+
140
174
  You can find more interesting examples in [JetSet integration tests](https://github.com/cylon-v/jet_set/tree/master/spec/integration).
141
- Also for the details please visit our [wiki].
142
175
 
143
176
  ## Development
144
177
 
data/jet_set.gemspec CHANGED
@@ -30,11 +30,11 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ['lib']
32
32
 
33
- spec.add_dependency 'sequel', '~> 5.4.0'
34
- spec.add_dependency 'hypo', '~> 0.10.0'
35
- spec.add_development_dependency 'bundler', '~> 1.15'
36
- spec.add_development_dependency 'rake', '~> 10.0'
37
- spec.add_development_dependency 'rspec', '~> 3.0'
38
- spec.add_development_dependency 'sqlite3', '~> 1.3'
39
- spec.add_development_dependency 'simplecov', '~> 0.16'
33
+ spec.add_dependency 'sequel', '>= 5.4.0'
34
+ spec.add_dependency 'hypo', '>= 1.0.0'
35
+ spec.add_development_dependency 'bundler', '>= 2.1'
36
+ spec.add_development_dependency 'rake', '>= 10.0'
37
+ spec.add_development_dependency 'rspec', '>= 3.0'
38
+ spec.add_development_dependency 'sqlite3', '>= 1.3'
39
+ spec.add_development_dependency 'simplecov', '>= 0.16'
40
40
  end
@@ -1,5 +1,6 @@
1
1
  require 'jet_set/mixin/identity'
2
2
  require 'jet_set/mixin/entity'
3
+ require 'jet_set/validations'
3
4
 
4
5
  module JetSet
5
6
  # A converter of a pure Ruby object to JetSet trackable object.
@@ -22,6 +23,7 @@ module JetSet
22
23
 
23
24
  object.extend(Identity)
24
25
  object.extend(Entity)
26
+ object.extend(Validations)
25
27
  end
26
28
  end
27
29
  end
@@ -15,7 +15,8 @@ module JetSet
15
15
  @container = container
16
16
 
17
17
  @mapping.entity_mappings.values.each do |entity_mapping|
18
- container.register(entity_mapping.type)
18
+ entity_name = "jet_set__#{entity_mapping.type.name.underscore}".to_sym
19
+ container.register(entity_mapping.type, entity_name)
19
20
  end
20
21
  end
21
22
 
@@ -29,6 +30,7 @@ module JetSet
29
30
  # "SELECT u.name AS customer__name from users u"
30
31
  def map(type, row_hash, session, prefix = type.name.underscore)
31
32
  entity_name = type.name.underscore.to_sym
33
+ resolve_name = "jet_set__#{type.name.underscore}".to_sym
32
34
  entity_mapping = @mapping.get(entity_name)
33
35
  row = Row.new(row_hash, entity_mapping.fields, prefix)
34
36
 
@@ -43,7 +45,7 @@ module JetSet
43
45
  end
44
46
  end
45
47
 
46
- object = @container.resolve(entity_name, row.attributes_hash.merge(reference_hash))
48
+ object = @container.resolve(resolve_name, row.attributes_hash.merge(reference_hash))
47
49
  entity = @entity_builder.create(object)
48
50
  entity.load_attributes!(row.attributes)
49
51
 
@@ -77,6 +77,8 @@ module JetSet
77
77
  # Parameters:
78
78
  # +sequel+:: Sequel sequel
79
79
  def flush(sequel)
80
+ validate! if respond_to? :validate!
81
+
80
82
  table_name = self.class.name.underscore.pluralize.to_sym
81
83
  table = sequel[table_name]
82
84
  entity_name = self.class.name.underscore.to_sym
@@ -113,12 +115,14 @@ module JetSet
113
115
  values << value.instance_variable_get('@id')
114
116
  end
115
117
  end
118
+
116
119
  new_id = table.insert(fields, values)
117
120
  @__attributes['@id'] = Attribute.new('@id', new_id)
118
121
  @id = new_id
119
122
  elsif dirty?
120
123
  attributes = {}
121
124
  dirty_attributes.each {|attribute| attributes[attribute.name.sub('@', '')] = instance_variable_get(attribute.name)}
125
+
122
126
  if attributes.keys.length > 0
123
127
  table.where(id: @id).update(attributes)
124
128
  end
@@ -103,9 +103,15 @@ module JetSet
103
103
  dirty_objects = @objects.select {|object| object.dirty?}
104
104
  ordered_objects = @dependency_graph.order(dirty_objects)
105
105
 
106
- if ordered_objects.length > 0
107
- @sequel.transaction do
108
- ordered_objects.each{|obj| obj.flush(@sequel)}
106
+ begin
107
+ if ordered_objects.length > 0
108
+ @sequel.transaction do
109
+ ordered_objects.each{|obj| obj.flush(@sequel)}
110
+ end
111
+ end
112
+ ensure
113
+ @mutex.synchronize do
114
+ @objects = []
109
115
  end
110
116
  end
111
117
  end
@@ -0,0 +1,4 @@
1
+ module JetSet
2
+ class ValidationDefinitionError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,10 @@
1
+ module JetSet
2
+ class ValidationError < StandardError
3
+ attr_reader :invalid_items
4
+
5
+ def initialize(message, invalid_items = {})
6
+ super(message)
7
+ @invalid_items = invalid_items
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,89 @@
1
+ require 'jet_set/validation_error'
2
+
3
+ module JetSet
4
+ # Optional validation decorator. Adds validation logic to pure Ruby objects.
5
+ module Validations
6
+ # The method runs all validations declared in the model
7
+ def validate!
8
+ validations = self.class.class_variable_defined?(:@@validations) ? self.class.class_variable_get(:@@validations) : {}
9
+ attributes = validations.keys
10
+ invalid_items = []
11
+
12
+ attributes.each do |attribute|
13
+ attribute_validations = validations[attribute] || []
14
+
15
+ error = nil
16
+ attribute_validations.each do |validation|
17
+ value = instance_variable_get("@#{attribute}")
18
+ if validation[:func].call(value) == false
19
+ error = validation[:message]
20
+ break
21
+ end
22
+ end
23
+ invalid_items << {"#{attribute}": error} if error
24
+ end
25
+
26
+ raise ValidationError.new("#{self.class.name} is invalid", invalid_items) if invalid_items.length > 0
27
+ end
28
+
29
+ def self.included(base)
30
+ base.extend(ClassMethods)
31
+ end
32
+
33
+ module ClassMethods
34
+ # Adds a validation to an attribute of the entity
35
+ # Parameters:
36
+ # +attribute_name+:: +Symbol+ attribute name
37
+ # +options+ || +message+:: validation options {type, presence, message, custom} or just a message
38
+ # +func+:: boolean proc with a check for validity
39
+ def validate(attribute_name, options, func = nil)
40
+ validations = self.class_variable_defined?(:@@validations) ? self.class_variable_get(:@@validations) : {}
41
+ validations[attribute_name] ||= []
42
+
43
+ if options.is_a?(Hash)
44
+ if options.has_key?(:type)
45
+ validations[attribute_name] << validate_type(options[:type])
46
+ end
47
+
48
+ if options[:presence] == true
49
+ validations[attribute_name] << validate_presence
50
+ end
51
+
52
+ message = options[:message]
53
+ elsif options.is_a?(String)
54
+ message = options
55
+ else
56
+ raise ValidationDefinitionError, "Validation definition of attribute #{attribute_name} is incorrect."
57
+ end
58
+
59
+ func ||= options[:custom]
60
+
61
+ unless func.nil?
62
+ validations[attribute_name] << {func: func, message: message}
63
+ end
64
+
65
+ self.class_variable_set(:@@validations, validations)
66
+ end
67
+
68
+ private
69
+
70
+ def validate_presence
71
+ {message: 'cannot be blank', func: -> (value) {!value.nil? && value != ''}}
72
+ end
73
+
74
+ def validate_type(type)
75
+ unless [:numeric, :string, :boolean].include?(type)
76
+ raise ValidationDefinitionError, "the type should be :numeric, :string or :boolean"
77
+ end
78
+
79
+ checks = {
80
+ numeric: -> (value) {value.is_a?(Numeric)},
81
+ string: -> (value) {value.is_a?(String)},
82
+ boolean: -> (value) {!!value == value}
83
+ }
84
+
85
+ {message: "should be #{type}", func: -> (value) {value.nil? || checks[type].call(value)}}
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,3 +1,3 @@
1
1
  module JetSet
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/jet_set.rb CHANGED
@@ -3,6 +3,9 @@ require 'sequel'
3
3
  require 'sequel/extensions/inflector'
4
4
  require 'jet_set/environment'
5
5
  require 'jet_set/mapping'
6
+ require 'jet_set/validations'
7
+ require 'jet_set/validation_error'
8
+ require 'jet_set/validation_definition_error'
6
9
  require 'jet_set/version'
7
10
 
8
11
  module JetSet
metadata CHANGED
@@ -1,111 +1,111 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jet_set
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Kalinkin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-09-03 00:00:00.000000000 Z
11
+ date: 2020-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 5.4.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 5.4.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: hypo
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.10.0
33
+ version: 1.0.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 0.10.0
40
+ version: 1.0.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '1.15'
47
+ version: '2.1'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '1.15'
54
+ version: '2.1'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '10.0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '10.0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '3.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '3.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: sqlite3
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: '1.3'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '1.3'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: simplecov
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
103
  version: '0.16'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0.16'
111
111
  description: ''
@@ -143,6 +143,9 @@ files:
143
143
  - lib/jet_set/reference.rb
144
144
  - lib/jet_set/row.rb
145
145
  - lib/jet_set/session.rb
146
+ - lib/jet_set/validation_definition_error.rb
147
+ - lib/jet_set/validation_error.rb
148
+ - lib/jet_set/validations.rb
146
149
  - lib/jet_set/version.rb
147
150
  homepage: https://github.com/cylon-v/jet_set
148
151
  licenses:
@@ -164,8 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
167
  - !ruby/object:Gem::Version
165
168
  version: '0'
166
169
  requirements: []
167
- rubyforge_project:
168
- rubygems_version: 2.7.7
170
+ rubygems_version: 3.1.2
169
171
  signing_key:
170
172
  specification_version: 4
171
173
  summary: JetSet is a microscopic ORM for DDD projects.