typero 0.5.2 → 0.9.3

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