typero 0.8.0 → 0.9.4

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 (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