typero 0.5.2 → 0.9.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.version +1 -1
  3. data/lib/adapters/sequel.rb +11 -33
  4. data/lib/typero.rb +8 -189
  5. data/lib/typero/params.rb +126 -0
  6. data/lib/typero/schema.rb +154 -0
  7. data/lib/typero/type/type.rb +125 -0
  8. data/lib/typero/type/types/boolean_type.rb +26 -0
  9. data/lib/typero/type/types/currency_type.rb +21 -0
  10. data/lib/typero/type/types/date_type.rb +35 -0
  11. data/lib/typero/type/types/datetime_type.rb +16 -0
  12. data/lib/typero/type/types/email_type.rb +20 -0
  13. data/lib/typero/type/types/float_type.rb +24 -0
  14. data/lib/typero/type/types/hash_type.rb +31 -0
  15. data/lib/typero/type/types/image_type.rb +22 -0
  16. data/lib/typero/type/types/integer_type.rb +16 -0
  17. data/lib/typero/type/types/label_type.rb +20 -0
  18. data/lib/typero/type/types/locale_type.rb +13 -0
  19. data/lib/typero/type/types/model_type.rb +23 -0
  20. data/lib/typero/type/{oib.rb → types/oib_type.rb} +16 -18
  21. data/lib/typero/type/types/point_type.rb +25 -0
  22. data/lib/typero/type/types/string_type.rb +20 -0
  23. data/lib/typero/type/types/text_type.rb +10 -0
  24. data/lib/typero/type/types/time_type.rb +5 -0
  25. data/lib/typero/type/types/timezone_type.rb +15 -0
  26. data/lib/typero/type/types/url_type.rb +13 -0
  27. data/lib/typero/typero.rb +98 -0
  28. metadata +33 -27
  29. data/lib/typero/type.rb +0 -32
  30. data/lib/typero/type/array.rb +0 -55
  31. data/lib/typero/type/boolean.rb +0 -16
  32. data/lib/typero/type/currency.rb +0 -17
  33. data/lib/typero/type/date.rb +0 -6
  34. data/lib/typero/type/datetime.rb +0 -6
  35. data/lib/typero/type/email.rb +0 -27
  36. data/lib/typero/type/float.rb +0 -27
  37. data/lib/typero/type/geography.rb +0 -13
  38. data/lib/typero/type/hash.rb +0 -18
  39. data/lib/typero/type/integer.rb +0 -25
  40. data/lib/typero/type/label.rb +0 -20
  41. data/lib/typero/type/point.rb +0 -25
  42. data/lib/typero/type/string.rb +0 -29
  43. data/lib/typero/type/text.rb +0 -7
  44. data/lib/typero/type/url.rb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3abfc7ee1372098804789576ac4fb36dbfb33a2cce2e48cd2dda70b975a2a1f6
4
- data.tar.gz: 9ed9618bde6dd268b39f516428c63c07a3aa86006323a0dd0b231a421e6a4c68
3
+ metadata.gz: 2b0ee2233c2ba484ec5ff98778a8da24a876b7b22eb4d24cc6b3ba3a8886e714
4
+ data.tar.gz: 4a96d10f9c6d0e75e8ae36965d34a6cda8853684974f799e2cf5e32d6a7003c1
5
5
  SHA512:
6
- metadata.gz: 3ab235a93802052f17166cf1bda8a3cd75ee20def1c669c9b5ccf7bb4d7633ae2ddffec6cc01f57e3bf2c98aaf652731a1deb7c2450e658da76ae8092e3790a1
7
- data.tar.gz: 4430507594bdecbb95aff494ed0e8dc1a71d22111abecafec19a801d3d4445d1a32dfe5fd861b330515d7b63ba0a9867d53e088e1c2a3d206b13cf9147a09cd1
6
+ metadata.gz: 880c3e4e551ab660f85530454ad9007ae383118677ea209ffaf0efff54bcd38da220579dc42d87618bd9ea50790b04a46c63dfe3cfd6252508b2bbec6ba1d711
7
+ data.tar.gz: 6f10437b894c91f3c7f723da03b426218d60b21c8bd161111d8ae7a745bdf35565f4fba84f26552fc75aa9b40211f3902d1cc2418fc420a5e3bce912ac676bc2
data/.version CHANGED
@@ -1 +1 @@
1
- 0.5.2
1
+ 0.9.3
@@ -1,67 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'typero'
4
-
5
3
  module Sequel::Plugins::TyperoAttributes
6
4
  module ClassMethods
7
- def attributes opts={}, &block
8
- instance_variable_set :@typero, Typero.new(&block)
9
-
10
- # attributes migrate: true do ...
11
- AutoMigrate.typero to_s.tableize.to_sym if opts[:migrate] && Lux.config.migrate
12
- end
13
-
14
5
  def typero
15
- instance_variable_get :@typero
6
+ Typero.schema self
16
7
  end
17
8
  end
18
9
 
19
10
  module InstanceMethods
20
11
  # calling typero! on any object will validate all fields
21
- def typero! field_name=nil
22
- typero = self.class.typero || return
12
+ def validate
13
+ super
23
14
 
24
- typero.validate(self) do |name, err|
15
+ schema = Typero.schema(self.class) || return
16
+
17
+ schema.validate(self) do |name, err|
25
18
  errors.add(name, err) unless (errors.on(name) || []).include?(err)
26
19
  end
27
20
 
28
21
  # this are rules unique to database, so we check them here
29
- typero.rules.each do |field, rule|
22
+ schema.rules.each do |field, rule|
30
23
  # check uniqe fields
31
- if rule[:unique]
24
+ if unique = rule.dig(:meta, :unique)
32
25
  id = self[:id] || 0
33
26
  value = self[field]
34
27
 
35
28
  # we only check if field is changed
36
29
  if value.present? && column_changed?(field) && self.class.xwhere('LOWER(%s)=LOWER(?) and id<>?' % field, value, id).first
37
- error = rule[:unique].class == TrueClass ? %[Value '"#{value}"' for #{field} allready exists] : rule[:unique]
30
+ error = unique.class == TrueClass ? %[Value '"#{value}"' for #{field} allready exists] : unique
38
31
  errors.add(field, error) unless (errors.on(field) || []).include?(error)
39
32
  end
40
33
  end
41
34
 
42
35
  # check protected fields
43
- if rule[:protected] && self[:id]
36
+ if prot = rule.dig(:meta, :protected) && self[:id]
44
37
  if column_changed?(field)
45
- error = rule[:protected].class == TrueClass ? "value once defined can't be overwritten." : rule[:protected]
38
+ error = prot.class == TrueClass ? "value once defined can't be overwritten." : prot
46
39
  errors.add(field, error) unless (errors.on(field) || []).include?(error)
47
40
  end
48
41
  end
49
42
  end
50
-
51
- # check single field if single field given
52
- if field_name
53
- raise ArgumentError.new 'Field :%s not found in %s' % [field_name, self] unless self[field_name]
54
- return unless errors.on(field_name)
55
-
56
- errors.on(field_name).join(', ')
57
- end
58
-
59
- true
60
- end
61
-
62
- def validate
63
- typero!
64
- super
65
43
  end
66
44
  end
67
45
 
@@ -1,194 +1,13 @@
1
- # rules = Typero.new do
2
- # set :name, String, req: true
3
- # set :email, :email, req: true
4
- # set :skills, [:email], min: 2
5
- # end
6
- #
7
- # or
8
- #
9
- # rules = Typero.new do
10
- # string :name, req: true
11
- # email :email, req: true
12
- # email [:skills], min: 2
13
- # end
14
- #
15
- # errors = rules.validate @object
16
- # rules.valid?
17
- # rules.validate(@object) { |errors| ... }
1
+ # require 'hash_wia'
18
2
 
19
- class Typero
20
- attr_reader :rules
3
+ # base libs
4
+ require_relative 'typero/typero'
5
+ require_relative 'typero/schema'
6
+ require_relative 'typero/params'
7
+ require_relative 'typero/type/type'
21
8
 
22
- VERSION = File.read File.expand_path '../.version', File.dirname(__FILE__)
23
-
24
- class << self
25
- # validate single value in type
26
- def validate type, value, opts={}
27
- field = type.to_s.tableize.singularize.to_sym
28
-
29
- # we need to have pointer to hash, so value can be changed (coerced) if needed
30
- h = { field => value }
31
-
32
- rule = new
33
- rule.set field, type, opts
34
-
35
- if error = rule.validate(h)[field]
36
- block_given? ? yield(error) : raise(TypeError.new(error))
37
- end
38
-
39
- h[field]
40
- end
41
-
42
- # check and coerce value
43
- # Typero.set(:label, 'Foo bar') -> "foo-bar"
44
- def set type, value, opts={}
45
- check = Typero::Type.load(type).new value, opts
46
- check.value
47
- end
48
- end
49
-
50
- ###
51
-
52
- # accepts dsl block to
53
- def initialize hash={}, &block
54
- @rules = {}
55
- @db = []
56
- hash.each { |k, v| set(k, v) }
57
- instance_exec &block if block
58
- end
59
-
60
- # validates any instance object or object with hash variable interface
61
- # it also coarces values
62
- def validate instance
63
- @errors = {}
64
-
65
- @rules.each do |field, opts|
66
- # set value to default if value is blank and default given
67
- instance[field] = opts[:default] if opts[:default] && instance[field].blank?
68
-
69
- # get field value
70
- value = instance[field]
71
-
72
- if value.present?
73
- klass = 'Typero::%sType' % safe_type(opts[:type])
74
- check = klass.constantize.new value, opts
75
- check.value = check.default if check.value.nil?
76
-
77
- unless check.value.nil?
78
- begin
79
- check.set
80
- check.validate
81
- instance[field] = check.value
82
- rescue TypeError => e
83
- add_error field, e.message
84
- end
85
- end
86
- elsif opts[:required]
87
- msg = opts[:required].class == TrueClass ? 'is required' : opts[:req]
88
- add_error field, msg
89
- end
90
- end
91
-
92
- if @errors.keys.length > 0 && block_given?
93
- @errors.each { |k,v| yield(k, v) }
94
- end
95
-
96
- @errors
97
- end
98
-
99
- def valid? instance
100
- errors = validate instance
101
- errors.keys.length == 0
102
- end
103
-
104
- # returns field, db_type, db_opts
105
- def db_schema
106
- out = @rules.inject([]) do |total, (field, opts)|
107
- type, opts = Typero::Type.load(opts[:type]).new(nil, opts).db_field
108
- total << [type, field, opts]
109
- end
110
-
111
- out += @db if @db[0]
112
-
113
- out
114
- end
115
-
116
- private
117
-
118
- # adds error to array or prefixes with field name
119
- def add_error field, msg
120
- if @errors[field]
121
- @errors[field] += ', %s' % msg
122
- else
123
- if msg[0,1].downcase == msg[0,1]
124
- field_name = field.to_s.sub(/_id$/,'').humanize
125
- msg = '%s %s' % [field_name, msg]
126
- end
127
-
128
- @errors[field] = msg
129
- end
130
- end
131
-
132
- # used in dsl to define value
133
- def set field, type=String, opts={}
134
- opts = type.is_a?(Hash) ? type : opts.merge(type: type)
135
- opts[:type] ||= :string
136
- opts[:req] = true if opts[:null].class == FalseClass
137
- klass = Typero::Type.load opts[:type]
138
- @rules[field] = parse_option opts
139
- end
140
-
141
- def safe_type type
142
- type.to_s.gsub(/[^\w]/,'').classify
143
- end
144
-
145
- # coerce opts values
146
- def parse_option opts
147
- opts[:type] ||= 'string'
148
-
149
- if opts[:type].is_a?(Array)
150
- opts[:array_type] = opts[:type][0] if opts[:type][0]
151
- opts[:type] = 'array'
152
- end
153
-
154
- opts[:type] = opts[:type].to_s.downcase
155
-
156
- opts[:required] = opts.delete(:req) unless opts[:req].nil?
157
- opts[:unique] = opts.delete(:uniq) unless opts[:uniq].nil?
158
- opts[:description] = opts.delete(:desc) unless opts[:desc].nil?
159
-
160
- opts
161
- end
162
-
163
-
164
- # pass values for db_schema only
165
- # db :timestamps
166
- # db :add_index, :code -> t.add_index :code
167
- def db *args
168
- @db.push args
169
- end
170
-
171
- # set :age, type: :integer -> integer :age
172
- # email :email
173
- # set :email, [:emails]
174
- # email [:emails]
175
- def method_missing name, *args, &block
176
- field = args.shift
177
-
178
- if field.class == Array
179
- field = field.first
180
- name = [name]
181
- end
182
-
183
- name = args.shift if name == :set
184
-
185
- set field, type=name, *args
186
- end
187
- end
188
-
189
- require_relative 'typero/type'
190
-
191
- Dir['%s/typero/type/*.rb' % __dir__].each do |file|
9
+ # checker types
10
+ Dir['%s/typero/type/types/*.rb' % __dir__].each do |file|
192
11
  require file
193
12
  end
194
13
 
@@ -0,0 +1,126 @@
1
+ # Base class for schema validation
2
+
3
+ module Typero
4
+ class Params
5
+ attr_reader :db_rules
6
+
7
+ def initialize &block
8
+ @db_rules = []
9
+ @rules = {}
10
+ instance_exec &block
11
+ end
12
+
13
+ def rules
14
+ @rules.dup
15
+ end
16
+
17
+ private
18
+
19
+ # used in dsl to define schema field options
20
+ def set field, *args, &block
21
+ raise "Field name not given (Typero)" unless field
22
+
23
+ if args.first.is_a?(Hash)
24
+ opts = args.first || {}
25
+ else
26
+ opts = args[1] || {}
27
+ opts[:type] ||= args[0]
28
+ end
29
+
30
+ opts[:type] = :string if opts[:type].nil?
31
+
32
+ field = field.to_s
33
+
34
+ if field.include?('!')
35
+ if block
36
+ field = field.sub('!', '')
37
+ @block_type = field.to_sym
38
+ instance_exec &block
39
+ @block_type = nil
40
+ return
41
+ else
42
+ raise ArgumentError.new 'If you use ! you have to provide a block'
43
+ end
44
+ end
45
+
46
+ # name? - opional name
47
+ if field.include?('?')
48
+ field = field.sub('?', '')
49
+ opts[:required] = false
50
+ end
51
+
52
+ if value = opts.delete(:req)
53
+ opts[:required] = value
54
+ else
55
+ opts[:required] = true if opts[:required].nil?
56
+ end
57
+
58
+ # array that allows duplicates
59
+ if opts[:type].is_a?(Array)
60
+ opts[:type] = opts[:type].first
61
+ opts[:array] = true
62
+ end
63
+
64
+ # no duplicates array
65
+ if opts[:type].is_a?(Set)
66
+ opts[:type] = opts[:type].to_a.first
67
+ opts[:array] = true
68
+ end
69
+
70
+ opts[:type] = @block_type if @block_type
71
+
72
+ # Boolean
73
+ if opts[:type].is_a?(TrueClass) || opts[:type] == :true
74
+ opts[:required] = false
75
+ opts[:default] = true
76
+ opts[:type] = :boolean
77
+ elsif opts[:type].is_a?(FalseClass) || opts[:type] == :false
78
+ opts[:required] = false
79
+ opts[:default] = false
80
+ opts[:type] = :boolean
81
+ end
82
+
83
+ opts[:model] = opts.delete(:schema) if opts[:schema]
84
+ opts[:type] = :model if opts[:model]
85
+
86
+ if block_given?
87
+ opts[:type] = :model
88
+ opts[:model] = Typero.schema &block
89
+ end
90
+
91
+ opts[:type] ||= 'string'
92
+ opts[:type] = opts[:type].to_s.downcase.to_sym
93
+
94
+ opts[:description] = opts.delete(:desc) unless opts[:desc].nil?
95
+
96
+ # chek alloed params, all optional should go in meta
97
+ result = opts.keys - Typero::Type::OPTS_KEYS
98
+ raise ArgumentError.new('Unallowed Type params found: "%s", allowed: %s' % [result.join(' and '), Typero::Type::OPTS_KEYS.sort]) if result.length > 0
99
+
100
+ field = field.to_sym
101
+
102
+ db :add_index, field if opts.delete(:index)
103
+
104
+ # trigger error if type not found
105
+ Typero::Type.load opts[:type]
106
+
107
+ @rules[field] = opts
108
+ end
109
+
110
+ # pass values for db_schema only
111
+ # db :timestamps
112
+ # db :add_index, :code -> t.add_index :code
113
+ def db *args
114
+ @db_rules.push args
115
+ end
116
+
117
+ # set :age, type: :integer -> integer :age
118
+ # email :email
119
+ #
120
+ # set :emails, Array[:email]
121
+ # email Array[:emails]
122
+ def method_missing field, *args, &block
123
+ set field, *args, &block
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,154 @@
1
+ module Typero
2
+ class Schema
3
+ SCHEMAS = {}
4
+ TYPES = {}
5
+
6
+ # accepts dsl block to
7
+ def initialize &block
8
+ raise "Params not defined" unless block_given?
9
+ @schema = Params.new &block
10
+ end
11
+
12
+ # validates any instance object with hash variable interface
13
+ # it also coarces values
14
+ def validate object, options=nil
15
+ @options = options || {}
16
+ @object = object
17
+ @errors = {}
18
+
19
+ # remove undefined keys if Hash provided
20
+ if @options[:strict] && object.is_a?(Hash)
21
+ undefined = object.keys.map(&:to_s) - @schema.rules.keys.map(&:to_s)
22
+ object.delete_if { |k, _| undefined.include?(k.to_s) }
23
+ end
24
+
25
+ @schema.rules.each do |field, opts|
26
+ for k in opts.keys
27
+ opts[k] = @object.instance_exec(&opts[k]) if opts[k].is_a?(Proc)
28
+ end
29
+
30
+ # set value to default if value is blank and default given
31
+ @object[field] = opts[:default] if opts[:default] && @object[field].blank?
32
+
33
+ # get field value
34
+ value = @object[field]
35
+
36
+ if opts[:array]
37
+ unless value.respond_to?(:each)
38
+ opts[:delimiter] ||= /\s*[,\n]\s*/
39
+ value = value.to_s.split(opts[:delimiter])
40
+ end
41
+
42
+ value = value
43
+ .flatten
44
+ .map { |el| el.to_s == '' ? nil : check_filed_value(field, el, opts) }
45
+ .compact
46
+
47
+ value = Set.new(value).to_a unless opts[:duplicates]
48
+
49
+ opts[:max_count] ||= 100
50
+ add_error(field, 'Max number of array elements is %d, you have %d' % [opts[:max_count], value.length], opts) if value && value.length > opts[:max_count]
51
+ add_error(field, 'Min number of array elements is %d, you have %d' % [opts[:min_count], value.length], opts) if value && opts[:min_count] && value.length < opts[:min_count]
52
+
53
+ add_required_error field, value.first, opts
54
+ else
55
+ value = nil if value.to_s == ''
56
+
57
+ # if value is not list of allowed values, raise error
58
+ allowed = opts[:allow] || opts[:allowed] || opts[:values]
59
+ if value && allowed && !allowed.include?(value)
60
+ add_error field, 'Value "%s" is not allowed' % value, opts
61
+ end
62
+
63
+ value = check_filed_value field, value, opts
64
+ add_required_error field, value, opts
65
+ end
66
+
67
+ # present empty string values as nil
68
+ @object[field] = value.to_s.sub(/\s+/, '') == '' ? nil : value
69
+ end
70
+
71
+ if @errors.keys.length > 0 && block_given?
72
+ @errors.each { |k, v| yield(k, v) }
73
+ end
74
+
75
+ @errors
76
+ end
77
+
78
+ def valid? object
79
+ errors = validate object
80
+ errors.keys.length == 0
81
+ end
82
+
83
+ # returns field, db_type, db_opts
84
+ def db_schema
85
+ out = @schema.rules.inject([]) do |total, (field, opts)|
86
+ # get db filed schema
87
+ type, opts = Typero::Type.load(opts[:type]).new(nil, opts).db_field
88
+
89
+ # add array true to field it ont defined in schema
90
+ schema_opts = @schema.rules[field]
91
+ opts[:array] = true if schema_opts[:array]
92
+
93
+ total << [type, field, opts]
94
+ end
95
+
96
+ out += @schema.db_rules
97
+
98
+ out
99
+ end
100
+
101
+ # iterate trough all the ruels via block interface
102
+ # schema.rules do |field, opts|
103
+ # schema.rules(:url) do |field, opts|
104
+ def rules filter = nil, &block
105
+ return @schema.rules unless filter
106
+ out = @schema.rules
107
+ out = out.select { |k, v| v[:type].to_s == filter.to_s || v[:array_type].to_s == filter.to_s } if filter
108
+ return out unless block_given?
109
+
110
+ out.each { |k, v| yield k, v }
111
+ end
112
+ alias :to_h :rules
113
+
114
+ private
115
+
116
+ # adds error to array or prefixes with field name
117
+ def add_error field, msg, opts
118
+ if @errors[field]
119
+ @errors[field] += ", %s" % msg
120
+ else
121
+ if msg && msg[0, 1].downcase == msg[0, 1]
122
+ field_name = opts[:name] || field.to_s.sub(/_id$/, "").capitalize
123
+ msg = "%s %s" % [field_name, msg]
124
+ end
125
+
126
+ @errors[field] = msg
127
+ end
128
+ end
129
+
130
+ def safe_type type
131
+ type.to_s.gsub(/[^\w]/, "").classify
132
+ end
133
+
134
+ def add_required_error field, value, opts
135
+ return unless opts[:required] && value.nil?
136
+ msg = opts[:required].class == TrueClass ? "is required" : opts[:required]
137
+ add_error field, msg, opts
138
+ end
139
+
140
+ def check_filed_value field, value, opts
141
+ klass = "Typero::%sType" % safe_type(opts[:type])
142
+ check = klass.constantize.new value, opts
143
+ check.get
144
+ rescue TypeError => e
145
+ if e.message[0] == '{'
146
+ for key, msg in JSON.parse(e.message)
147
+ add_error [field, key].join('.'), msg, opts
148
+ end
149
+ else
150
+ add_error field, e.message, opts
151
+ end
152
+ end
153
+ end
154
+ end