typero 0.8.0 → 0.9.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.version +1 -1
- data/lib/adapters/sequel.rb +10 -27
- data/lib/typero.rb +3 -0
- data/lib/typero/params.rb +57 -12
- data/lib/typero/schema.rb +165 -0
- data/lib/typero/type/type.rb +68 -10
- data/lib/typero/type/types/boolean_type.rb +26 -0
- data/lib/typero/type/types/currency_type.rb +21 -0
- data/lib/typero/type/types/date_type.rb +38 -0
- data/lib/typero/type/types/datetime_type.rb +19 -0
- data/lib/typero/type/types/email_type.rb +20 -0
- data/lib/typero/type/types/float_type.rb +24 -0
- data/lib/typero/type/types/hash_type.rb +31 -0
- data/lib/typero/type/types/{image.rb → image_type.rb} +4 -10
- data/lib/typero/type/types/integer_type.rb +16 -0
- data/lib/typero/type/types/label_type.rb +20 -0
- data/lib/typero/type/types/locale_type.rb +13 -0
- data/lib/typero/type/types/model_type.rb +23 -0
- data/lib/typero/type/types/{oib.rb → oib_type.rb} +7 -10
- data/lib/typero/type/types/point_type.rb +25 -0
- data/lib/typero/type/types/string_type.rb +23 -0
- data/lib/typero/type/types/{text.rb → text_type.rb} +3 -5
- data/lib/typero/type/types/time_type.rb +5 -0
- data/lib/typero/type/types/timezone_type.rb +15 -0
- data/lib/typero/type/types/url_type.rb +13 -0
- data/lib/typero/typero.rb +72 -141
- metadata +26 -21
- data/lib/typero/type/types/boolean.rb +0 -16
- data/lib/typero/type/types/currency.rb +0 -19
- data/lib/typero/type/types/date.rb +0 -10
- data/lib/typero/type/types/datetime.rb +0 -10
- data/lib/typero/type/types/email.rb +0 -21
- data/lib/typero/type/types/float.rb +0 -21
- data/lib/typero/type/types/hash.rb +0 -22
- data/lib/typero/type/types/integer.rb +0 -21
- data/lib/typero/type/types/label.rb +0 -15
- data/lib/typero/type/types/point.rb +0 -29
- data/lib/typero/type/types/string.rb +0 -23
- data/lib/typero/type/types/url.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f8d03e15d714e38774a8f817aaf02c710e3ddca8b65f9d413aefb01678bd31b
|
4
|
+
data.tar.gz: ebd4cb0d01d43f7a93c3e2575daa832ba960dd8ece2a284676c763f4c43ef30c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a8dc1fdac410f59695f486860f078359a8344492335759b2df69f2fa382e3366000d70d994c4c05bdfa613c6c9dc207b66fefd2e4731f97dca97f94eda94b85
|
7
|
+
data.tar.gz: e7c6581d6fc6558687b023505564c669f59ce8e5dc0a298392f83fab92e1f47b27704a0b7644143161c6a264d601c2ac2a382c8cc0bdfb54f97ea52789846891
|
data/.version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.9.4
|
data/lib/adapters/sequel.rb
CHANGED
@@ -3,60 +3,43 @@
|
|
3
3
|
module Sequel::Plugins::TyperoAttributes
|
4
4
|
module ClassMethods
|
5
5
|
def typero
|
6
|
-
Typero.
|
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
|
13
|
-
|
12
|
+
def validate
|
13
|
+
super
|
14
14
|
|
15
|
-
|
15
|
+
schema = Typero.schema(self.class) || return
|
16
16
|
|
17
|
-
|
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
|
-
|
23
|
-
self[field] ||= {} if rule[:type] == 'hash'
|
24
|
-
|
22
|
+
schema.rules.each do |field, rule|
|
25
23
|
# check uniqe fields
|
26
|
-
if rule
|
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 =
|
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
|
36
|
+
if prot = rule.dig(:meta, :protected) && self[:id]
|
39
37
|
if column_changed?(field)
|
40
|
-
error =
|
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
data/lib/typero/params.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
# Base class for schema validation
|
2
2
|
|
3
|
-
|
3
|
+
module Typero
|
4
4
|
class Params
|
5
|
-
|
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]
|
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
|
-
|
58
|
-
|
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
|
-
|
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
|
data/lib/typero/type/type.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Master class
|
2
2
|
|
3
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
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
|