typero 0.8.0 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.version +1 -1
  3. data/lib/adapters/sequel.rb +10 -27
  4. data/lib/typero.rb +3 -0
  5. data/lib/typero/params.rb +57 -12
  6. data/lib/typero/schema.rb +165 -0
  7. data/lib/typero/type/type.rb +68 -10
  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 +38 -0
  11. data/lib/typero/type/types/datetime_type.rb +19 -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.rb → image_type.rb} +4 -10
  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/types/{oib.rb → oib_type.rb} +7 -10
  21. data/lib/typero/type/types/point_type.rb +25 -0
  22. data/lib/typero/type/types/string_type.rb +23 -0
  23. data/lib/typero/type/types/{text.rb → text_type.rb} +3 -5
  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 +72 -141
  28. metadata +26 -21
  29. data/lib/typero/type/types/boolean.rb +0 -16
  30. data/lib/typero/type/types/currency.rb +0 -19
  31. data/lib/typero/type/types/date.rb +0 -10
  32. data/lib/typero/type/types/datetime.rb +0 -10
  33. data/lib/typero/type/types/email.rb +0 -21
  34. data/lib/typero/type/types/float.rb +0 -21
  35. data/lib/typero/type/types/hash.rb +0 -22
  36. data/lib/typero/type/types/integer.rb +0 -21
  37. data/lib/typero/type/types/label.rb +0 -15
  38. data/lib/typero/type/types/point.rb +0 -29
  39. data/lib/typero/type/types/string.rb +0 -23
  40. data/lib/typero/type/types/url.rb +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba656a56b8d2e5d3d977a2973f6de881eefd3257397a4a4ebe908a7abaed70a7
4
- data.tar.gz: 8374f340b6af235c274ceaa9aace33f0c462b5a203d667a199fa519c1f56f53e
3
+ metadata.gz: 0f8d03e15d714e38774a8f817aaf02c710e3ddca8b65f9d413aefb01678bd31b
4
+ data.tar.gz: ebd4cb0d01d43f7a93c3e2575daa832ba960dd8ece2a284676c763f4c43ef30c
5
5
  SHA512:
6
- metadata.gz: ba14e29595a5d17c3c3d102f49d1234a82e7ea23a7062a4a61c6dc56770d9d4922f1be7ae01663c41382efb6c6c51ed3fb5dfd4c188315ec1a5bf3f43eb699fc
7
- data.tar.gz: c2d8b322fac4048067b809bf7944c8ca27bb787d4b590a68eec62169a768a65ddbb089359da73f550a32b59a0405b95629ae4932bf7a3dfb9d5426e2e26d3e7d
6
+ metadata.gz: 7a8dc1fdac410f59695f486860f078359a8344492335759b2df69f2fa382e3366000d70d994c4c05bdfa613c6c9dc207b66fefd2e4731f97dca97f94eda94b85
7
+ data.tar.gz: e7c6581d6fc6558687b023505564c669f59ce8e5dc0a298392f83fab92e1f47b27704a0b7644143161c6a264d601c2ac2a382c8cc0bdfb54f97ea52789846891
data/.version CHANGED
@@ -1 +1 @@
1
- 0.8.0
1
+ 0.9.4
@@ -3,60 +3,43 @@
3
3
  module Sequel::Plugins::TyperoAttributes
4
4
  module ClassMethods
5
5
  def typero
6
- Typero.new self
6
+ Typero.schema self
7
7
  end
8
8
  end
9
9
 
10
10
  module InstanceMethods
11
11
  # calling typero! on any object will validate all fields
12
- def typero! field_name=nil
13
- return unless Typero.defined?(self.class)
12
+ def validate
13
+ super
14
14
 
15
- typero = self.class.typero
15
+ schema = Typero.schema(self.class) || return
16
16
 
17
- typero.validate(self) do |name, err|
17
+ schema.validate(self) do |name, err|
18
18
  errors.add(name, err) unless (errors.on(name) || []).include?(err)
19
19
  end
20
20
 
21
21
  # this are rules unique to database, so we check them here
22
- typero.rules.each do |field, rule|
23
- self[field] ||= {} if rule[:type] == 'hash'
24
-
22
+ schema.rules.each do |field, rule|
25
23
  # check uniqe fields
26
- if rule[:unique]
24
+ if unique = rule.dig(:meta, :unique)
27
25
  id = self[:id] || 0
28
26
  value = self[field]
29
27
 
30
28
  # we only check if field is changed
31
29
  if value.present? && column_changed?(field) && self.class.xwhere('LOWER(%s)=LOWER(?) and id<>?' % field, value, id).first
32
- error = rule[:unique].class == TrueClass ? %[Value '"#{value}"' for #{field} allready exists] : rule[:unique]
30
+ error = unique.class == TrueClass ? %[Value "#{value}" for field "#{field}" has been already used, please chose another value.] : unique
33
31
  errors.add(field, error) unless (errors.on(field) || []).include?(error)
34
32
  end
35
33
  end
36
34
 
37
35
  # check protected fields
38
- if rule[:protected] && self[:id]
36
+ if prot = rule.dig(:meta, :protected) && self[:id]
39
37
  if column_changed?(field)
40
- 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
41
39
  errors.add(field, error) unless (errors.on(field) || []).include?(error)
42
40
  end
43
41
  end
44
42
  end
45
-
46
- # check single field if single field given
47
- if field_name
48
- raise ArgumentError.new 'Field :%s not found in %s' % [field_name, self] unless self[field_name]
49
- return unless errors.on(field_name)
50
-
51
- errors.on(field_name).join(', ')
52
- end
53
-
54
- true
55
- end
56
-
57
- def validate
58
- typero!
59
- super
60
43
  end
61
44
  end
62
45
 
data/lib/typero.rb CHANGED
@@ -1,5 +1,8 @@
1
+ # require 'hash_wia'
2
+
1
3
  # base libs
2
4
  require_relative 'typero/typero'
5
+ require_relative 'typero/schema'
3
6
  require_relative 'typero/params'
4
7
  require_relative 'typero/type/type'
5
8
 
data/lib/typero/params.rb CHANGED
@@ -1,10 +1,8 @@
1
1
  # Base class for schema validation
2
2
 
3
- class Typero
3
+ module Typero
4
4
  class Params
5
- ALLOWED = %i(name min max default allowed delimiter max_count req required type array meta desc description duplicates unique)
6
-
7
- attr_reader :rules, :db_rules
5
+ attr_reader :db_rules
8
6
 
9
7
  def initialize &block
10
8
  @db_rules = []
@@ -12,10 +10,14 @@ class Typero
12
10
  instance_exec &block
13
11
  end
14
12
 
13
+ def rules
14
+ @rules.dup
15
+ end
16
+
15
17
  private
16
18
 
17
19
  # used in dsl to define schema field options
18
- def set field, *args
20
+ def set field, *args, &block
19
21
  raise "Field name not given (Typero)" unless field
20
22
 
21
23
  if args.first.is_a?(Hash)
@@ -25,17 +27,31 @@ class Typero
25
27
  opts[:type] ||= args[0]
26
28
  end
27
29
 
28
- opts[:type] ||= :string
29
- opts[:required] = true unless opts[:required].is_a?(FalseClass) || opts[:req].is_a?(FalseClass)
30
+ opts[:type] = :string if opts[:type].nil?
30
31
 
31
32
  field = field.to_s
32
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
+
33
46
  # name? - opional name
34
47
  if field.include?('?')
35
48
  field = field.sub('?', '')
36
49
  opts[:required] = false
37
50
  end
38
51
 
52
+ opts[:required] = opts.delete(:req) unless opts[:req].nil?
53
+ opts[:required] = true if opts[:required].nil?
54
+
39
55
  # array that allows duplicates
40
56
  if opts[:type].is_a?(Array)
41
57
  opts[:type] = opts[:type].first
@@ -48,20 +64,49 @@ class Typero
48
64
  opts[:array] = true
49
65
  end
50
66
 
67
+ opts[:type] = @block_type if @block_type
68
+
69
+ # Boolean
70
+ if opts[:type].is_a?(TrueClass) || opts[:type] == :true
71
+ opts[:required] = false
72
+ opts[:default] = true
73
+ opts[:type] = :boolean
74
+ elsif opts[:type].is_a?(FalseClass) || opts[:type] == :false || opts[:type] == :boolean
75
+ opts[:required] = false if opts[:required].nil?
76
+ opts[:default] = false if opts[:default].nil?
77
+ opts[:type] = :boolean
78
+ end
79
+
80
+ # model / schema
81
+ if opts[:type].class.ancestors.include?(Typero::Schema)
82
+ opts[:model] = opts.delete(:type)
83
+ end
84
+ opts[:model] = opts.delete(:schema) if opts[:schema]
85
+ opts[:type] = :model if opts[:model]
86
+
87
+ if block_given?
88
+ opts[:type] = :model
89
+ opts[:model] = Typero.schema &block
90
+ end
91
+
51
92
  opts[:type] ||= 'string'
52
- opts[:type] = opts[:type].to_s.downcase
93
+ opts[:type] = opts[:type].to_s.downcase.to_sym
53
94
 
54
95
  opts[:description] = opts.delete(:desc) unless opts[:desc].nil?
55
96
 
56
97
  # chek alloed params, all optional should go in meta
57
- result = opts.keys - ALLOWED
58
- raise ArgumentError.new('Unallowed Type params found: %s, allowed: %s' % [result.join(', '), ALLOWED]) if result.length > 0
98
+ opts.keys.each do |key|
99
+ type = Typero::Type.load opts[:type]
100
+ type.allowed_opt?(key) {|err| raise ArgumentError, err }
101
+ end
59
102
 
60
103
  field = field.to_sym
61
104
 
62
105
  db :add_index, field if opts.delete(:index)
63
106
 
64
- klass = Typero::Type.load opts[:type]
107
+ # trigger error if type not found
108
+ Typero::Type.load opts[:type]
109
+
65
110
  @rules[field] = opts
66
111
  end
67
112
 
@@ -78,7 +123,7 @@ class Typero
78
123
  # set :emails, Array[:email]
79
124
  # email Array[:emails]
80
125
  def method_missing field, *args, &block
81
- set field, *args
126
+ set field, *args, &block
82
127
  end
83
128
  end
84
129
  end
@@ -0,0 +1,165 @@
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
+ # force filed as a symbol
27
+ field = field.to_sym
28
+
29
+ for k in opts.keys
30
+ opts[k] = @object.instance_exec(&opts[k]) if opts[k].is_a?(Proc)
31
+ end
32
+
33
+ # set value to default if value is blank and default given
34
+ @object[field] = opts[:default] if opts[:default] && @object[field].blank?
35
+
36
+ if @object.respond_to?(:key?)
37
+ if @object.key?(field)
38
+ value = @object[field]
39
+ elsif @object.key?(field.to_s)
40
+ # invalid string key, needs fix
41
+ value = @object[field] = @object.delete(field.to_s)
42
+ end
43
+ else
44
+ value = @object[field]
45
+ end
46
+
47
+ if opts[:array]
48
+ unless value.respond_to?(:each)
49
+ opts[:delimiter] ||= /\s*[,\n]\s*/
50
+ value = value.to_s.split(opts[:delimiter])
51
+ end
52
+
53
+ value = value
54
+ .flatten
55
+ .map { |el| el.to_s == '' ? nil : check_filed_value(field, el, opts) }
56
+ .compact
57
+
58
+ value = Set.new(value).to_a unless opts[:duplicates]
59
+
60
+ opts[:max_count] ||= 100
61
+ 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]
62
+ 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]
63
+
64
+ add_required_error field, value.first, opts
65
+ else
66
+ value = nil if value.to_s == ''
67
+
68
+ # if value is not list of allowed values, raise error
69
+ allowed = opts[:allow] || opts[:allowed] || opts[:values]
70
+ if value && allowed && !allowed.include?(value)
71
+ add_error field, 'Value "%s" is not allowed' % value, opts
72
+ end
73
+
74
+ value = check_filed_value field, value, opts
75
+ add_required_error field, value, opts
76
+ end
77
+
78
+ # present empty string values as nil
79
+ @object[field] = value.to_s.sub(/\s+/, '') == '' ? nil : value
80
+ end
81
+
82
+ if @errors.keys.length > 0 && block_given?
83
+ @errors.each { |k, v| yield(k, v) }
84
+ end
85
+
86
+ @errors
87
+ end
88
+
89
+ def valid? object
90
+ errors = validate object
91
+ errors.keys.length == 0
92
+ end
93
+
94
+ # returns field, db_type, db_opts
95
+ def db_schema
96
+ out = @schema.rules.inject([]) do |total, (field, opts)|
97
+ # get db filed schema
98
+ type, opts = Typero::Type.load(opts[:type]).new(nil, opts).db_field
99
+
100
+ # add array true to field it ont defined in schema
101
+ schema_opts = @schema.rules[field]
102
+ opts[:array] = true if schema_opts[:array]
103
+
104
+ total << [type, field, opts]
105
+ end
106
+
107
+ out += @schema.db_rules
108
+
109
+ out
110
+ end
111
+
112
+ # iterate trough all the ruels via block interface
113
+ # schema.rules do |field, opts|
114
+ # schema.rules(:url) do |field, opts|
115
+ def rules filter = nil, &block
116
+ return @schema.rules unless filter
117
+ out = @schema.rules
118
+ out = out.select { |k, v| v[:type].to_s == filter.to_s || v[:array_type].to_s == filter.to_s } if filter
119
+ return out unless block_given?
120
+
121
+ out.each { |k, v| yield k, v }
122
+ end
123
+ alias :to_h :rules
124
+
125
+ private
126
+
127
+ # adds error to array or prefixes with field name
128
+ def add_error field, msg, opts
129
+ if @errors[field]
130
+ @errors[field] += ", %s" % msg
131
+ else
132
+ if msg && msg[0, 1].downcase == msg[0, 1]
133
+ field_name = opts[:name] || field.to_s.sub(/_id$/, "").capitalize
134
+ msg = "%s %s" % [field_name, msg]
135
+ end
136
+
137
+ @errors[field] = msg
138
+ end
139
+ end
140
+
141
+ def safe_type type
142
+ type.to_s.gsub(/[^\w]/, "").classify
143
+ end
144
+
145
+ def add_required_error field, value, opts
146
+ return unless opts[:required] && value.nil?
147
+ msg = opts[:required].class == TrueClass ? "is required" : opts[:required]
148
+ add_error field, msg, opts
149
+ end
150
+
151
+ def check_filed_value field, value, opts
152
+ klass = "Typero::%sType" % safe_type(opts[:type])
153
+ check = klass.constantize.new value, opts
154
+ check.get
155
+ rescue TypeError => e
156
+ if e.message[0] == '{'
157
+ for key, msg in JSON.parse(e.message)
158
+ add_error [field, key].join('.'), msg, opts
159
+ end
160
+ else
161
+ add_error field, e.message, opts
162
+ end
163
+ end
164
+ end
165
+ end
@@ -1,6 +1,6 @@
1
1
  # Master class
2
2
 
3
- class Typero
3
+ module Typero
4
4
  class Type
5
5
  ERRORS = {
6
6
  en: {
@@ -9,14 +9,32 @@ class Typero
9
9
  max_length_error: 'max lenght is %s, you have %s',
10
10
  min_value_error: 'min is %s, got %s',
11
11
  max_value_error: 'max is %s, got %s',
12
- unallowed_characters_error: 'is having unallowed characters'
12
+ unallowed_characters_error: 'is having unallowed characters',
13
+ not_in_range: 'Value in not in allowed range (%s)'
13
14
  }
14
15
  }
15
16
 
16
- OPTS = {}
17
-
18
- attr_accessor :opts
19
- attr_accessor :value
17
+ # default shared allowed opts keys
18
+ OPTS = {}
19
+ OPTS_KEYS = [
20
+ :allow,
21
+ :allowed,
22
+ :array,
23
+ :default,
24
+ :description,
25
+ :delimiter,
26
+ :max_count,
27
+ :meta,
28
+ :min_count,
29
+ :model,
30
+ :name,
31
+ :req,
32
+ :required,
33
+ :type,
34
+ :values
35
+ ]
36
+
37
+ attr_reader :opts
20
38
 
21
39
  class << self
22
40
  def load name
@@ -39,24 +57,64 @@ class Typero
39
57
  OPTS[self] ||= {}
40
58
  OPTS[self][key] = desc
41
59
  end
60
+
61
+ def allowed_opt? name
62
+ return true if OPTS_KEYS.include?(name)
63
+
64
+ OPTS[self] ||= {}
65
+ return true if OPTS[self][name]
66
+
67
+ msg = %[Unallowed param "#{name}" for type "#{to_s}" found. Allowed are "#{OPTS_KEYS.join(', ')}"]
68
+ msg += %[ + "#{OPTS[self].keys.join(', ')}"] if OPTS[self].keys.first
69
+
70
+ block_given? ? yield(msg) : raise(ArgumentError, msg)
71
+
72
+ false
73
+ end
42
74
  end
43
75
 
44
76
  ###
45
77
 
46
- def initialize value, opts={}
78
+ def initialize value, opts={}, &block
79
+ value = value.strip.rstrip if value.is_a?(String)
80
+
81
+ opts.keys.each {|key| self.class.allowed_opt?(key) }
82
+
47
83
  @value = value
48
84
  @opts = opts
85
+ @block = block
86
+ end
87
+
88
+ def value &block
89
+ if block_given?
90
+ @value = block.call @value
91
+ else
92
+ @value
93
+ end
49
94
  end
50
95
 
51
- # default validation for any type
52
- def validate
53
- true
96
+ def get
97
+ if value.nil?
98
+ opts[:default].nil? ? default : opts[:default]
99
+ else
100
+ set
101
+ error_for(:not_in_range, opts[:values].join(', ')) if opts[:values] && !opts[:values].include?(@value)
102
+ value
103
+ end
54
104
  end
55
105
 
56
106
  def default
57
107
  nil
58
108
  end
59
109
 
110
+ def db_field
111
+ out = db_schema
112
+ out[1] ||= {}
113
+ out[1][:default] ||= opts[:default] unless opts[:default].nil?
114
+ out[1][:null] = false if !opts[:array] && opts[:required]
115
+ out
116
+ end
117
+
60
118
  private
61
119
 
62
120
  # get error from option or the default one