typero 0.9.9 → 0.10.0
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.
- checksums.yaml +4 -4
- data/.version +1 -1
- data/lib/typero/define.rb +79 -69
- data/lib/typero/schema.rb +115 -120
- data/lib/typero/type/geo_extract.rb +75 -0
- data/lib/typero/type/type.rb +1 -1
- data/lib/typero/type/types/boolean_type.rb +1 -1
- data/lib/typero/type/types/currency_type.rb +1 -1
- data/lib/typero/type/types/date_type.rb +1 -1
- data/lib/typero/type/types/datetime_type.rb +1 -1
- data/lib/typero/type/types/email_type.rb +1 -1
- data/lib/typero/type/types/float_type.rb +3 -5
- data/lib/typero/type/types/hash_type.rb +1 -1
- data/lib/typero/type/types/image_type.rb +1 -1
- data/lib/typero/type/types/integer_type.rb +1 -1
- data/lib/typero/type/types/label_type.rb +1 -1
- data/lib/typero/type/types/locale_type.rb +1 -1
- data/lib/typero/type/types/model_type.rb +1 -1
- data/lib/typero/type/types/oib_type.rb +3 -3
- data/lib/typero/type/types/phone_type.rb +15 -0
- data/lib/typero/type/types/point_type.rb +9 -13
- data/lib/typero/type/types/simple_point_type.rb +9 -12
- data/lib/typero/type/types/slug_type.rb +25 -0
- data/lib/typero/type/types/string_type.rb +1 -1
- data/lib/typero/type/types/text_type.rb +1 -1
- data/lib/typero/type/types/timezone_type.rb +1 -1
- data/lib/typero/type/types/url_type.rb +1 -1
- data/lib/typero/type/types/uuid_type.rb +13 -0
- data/lib/typero.rb +1 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 988ca72b1b08538ccf6bf49e2fcd449a97b4b403bc8306e6d9c1242df6ae0a54
|
|
4
|
+
data.tar.gz: dd90e591bc0b9790e60656e8f6a342b033ab2adca4d80b80a6a1e702878f895e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8940ebb5034757bb9c4a285b0e6e74bc65131ab5bdebdd9154b0643843836c5cab7c2a98b988361181bea0cf38d461f40662bf9dda926d074bf7b5756f1d6312
|
|
7
|
+
data.tar.gz: e77d16947abb2762bc33ed5c5ba855da56924f2187bb8d31c067d614561cd3f44a07730f8fb858882baa0876ef55da9529a495695454a58bf5c2fe205b81e1f7
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.10.0
|
data/lib/typero/define.rb
CHANGED
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
# Base class for schema
|
|
2
|
-
# Accepts
|
|
3
|
-
|
|
4
|
-
# module Typero
|
|
5
|
-
# class Loader
|
|
6
|
-
# def timestamps
|
|
7
|
-
# created_at Time
|
|
8
|
-
# created_by_ref
|
|
9
|
-
# end
|
|
10
|
-
# end
|
|
11
|
-
# end
|
|
12
|
-
|
|
13
|
-
# class Task < ApplicationModel
|
|
14
|
-
# schema do
|
|
15
|
-
# name # string type
|
|
16
|
-
# timestamps # metod defined -> call
|
|
17
|
-
# end
|
|
18
|
-
# end
|
|
1
|
+
# Base class for schema definition DSL.
|
|
2
|
+
# Accepts a block of field declarations and returns hash of parsed rules.
|
|
19
3
|
|
|
20
4
|
require 'set'
|
|
21
5
|
require 'json'
|
|
@@ -40,6 +24,52 @@ module Typero
|
|
|
40
24
|
def set field, *args, &block
|
|
41
25
|
raise "Field name not given (Typero)" unless field
|
|
42
26
|
|
|
27
|
+
opts = parse_args args
|
|
28
|
+
field = field.to_s
|
|
29
|
+
|
|
30
|
+
# bang suffix defines block type for all fields in the block
|
|
31
|
+
if field.include?('!')
|
|
32
|
+
return define_block_type(field, &block)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# question mark suffix makes field optional
|
|
36
|
+
field = parse_field_name field, opts
|
|
37
|
+
|
|
38
|
+
resolve_type opts
|
|
39
|
+
|
|
40
|
+
# inline block defines a nested model schema
|
|
41
|
+
if block_given?
|
|
42
|
+
opts[:type] = :model
|
|
43
|
+
opts[:model] = Typero.schema &block
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
opts[:type] = opts[:type].to_s.downcase.to_sym
|
|
47
|
+
opts[:description] = opts.delete(:desc) unless opts[:desc].nil?
|
|
48
|
+
|
|
49
|
+
validate_opts opts
|
|
50
|
+
|
|
51
|
+
field = field.to_sym
|
|
52
|
+
db(:add_index, field) if opts.delete(:index)
|
|
53
|
+
|
|
54
|
+
@rules[field] = opts
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# pass values for db_schema only
|
|
58
|
+
# db :timestamps
|
|
59
|
+
# db :add_index, :code -> t.add_index :code
|
|
60
|
+
def db *args
|
|
61
|
+
@db_rules.push args
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# if method undefined, call set method
|
|
65
|
+
# age Integer -> set :age, type: :integer
|
|
66
|
+
def method_missing field, *args, &block
|
|
67
|
+
set field, *args, &block
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# --- argument parsing ---
|
|
71
|
+
|
|
72
|
+
def parse_args args
|
|
43
73
|
if args.first.is_a?(Hash)
|
|
44
74
|
opts = args.first || {}
|
|
45
75
|
else
|
|
@@ -48,22 +78,10 @@ module Typero
|
|
|
48
78
|
end
|
|
49
79
|
|
|
50
80
|
opts[:type] = :string if opts[:type].nil?
|
|
81
|
+
opts
|
|
82
|
+
end
|
|
51
83
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if field.include?('!')
|
|
55
|
-
if block
|
|
56
|
-
field = field.sub('!', '')
|
|
57
|
-
@block_type = field.to_sym
|
|
58
|
-
instance_exec &block
|
|
59
|
-
@block_type = nil
|
|
60
|
-
return
|
|
61
|
-
else
|
|
62
|
-
raise ArgumentError.new 'If you use ! you have to provide a block'
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# name? - opional name
|
|
84
|
+
def parse_field_name field, opts
|
|
67
85
|
if field.include?('?')
|
|
68
86
|
field = field.sub('?', '')
|
|
69
87
|
opts[:required] = false
|
|
@@ -72,21 +90,43 @@ module Typero
|
|
|
72
90
|
opts[:required] = opts.delete(:req) unless opts[:req].nil?
|
|
73
91
|
opts[:required] = true if opts[:required].nil?
|
|
74
92
|
|
|
75
|
-
|
|
93
|
+
field
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# --- type resolution ---
|
|
97
|
+
|
|
98
|
+
def define_block_type field, &block
|
|
99
|
+
raise ArgumentError, 'If you use ! you have to provide a block' unless block
|
|
100
|
+
|
|
101
|
+
field = field.sub('!', '')
|
|
102
|
+
@block_type = field.to_sym
|
|
103
|
+
instance_exec &block
|
|
104
|
+
@block_type = nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def resolve_type opts
|
|
108
|
+
# bare Array or Set class -> array of strings
|
|
109
|
+
if opts[:type] == Array || opts[:type] == Set
|
|
110
|
+
opts[:type] = :string
|
|
111
|
+
opts[:array] = true
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Array[:type] -> typed array
|
|
76
115
|
if opts[:type].is_a?(Array)
|
|
77
116
|
opts[:type] = opts[:type].first
|
|
78
117
|
opts[:array] = true
|
|
79
118
|
end
|
|
80
119
|
|
|
81
|
-
# no duplicates
|
|
120
|
+
# Set[:type] -> typed array (no duplicates)
|
|
82
121
|
if opts[:type].is_a?(Set)
|
|
83
122
|
opts[:type] = opts[:type].to_a.first
|
|
84
123
|
opts[:array] = true
|
|
85
124
|
end
|
|
86
125
|
|
|
126
|
+
# block type override (integer! do ... end)
|
|
87
127
|
opts[:type] = @block_type if @block_type
|
|
88
128
|
|
|
89
|
-
#
|
|
129
|
+
# boolean variants
|
|
90
130
|
if opts[:type].is_a?(TrueClass) || opts[:type] == :true
|
|
91
131
|
opts[:required] = false
|
|
92
132
|
opts[:default] = true
|
|
@@ -97,49 +137,19 @@ module Typero
|
|
|
97
137
|
opts[:type] = :boolean
|
|
98
138
|
end
|
|
99
139
|
|
|
100
|
-
# model / schema
|
|
140
|
+
# model / schema reference
|
|
101
141
|
if opts[:type].is_a?(Typero::Schema)
|
|
102
142
|
opts[:model] = opts.delete(:type)
|
|
103
143
|
end
|
|
104
144
|
opts[:model] = opts.delete(:schema) if opts[:schema]
|
|
105
145
|
opts[:type] = :model if opts[:model]
|
|
146
|
+
end
|
|
106
147
|
|
|
107
|
-
|
|
108
|
-
opts[:type] = :model
|
|
109
|
-
opts[:model] = Typero.schema &block
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
opts[:type] = opts[:type].to_s.downcase.to_sym
|
|
113
|
-
|
|
114
|
-
opts[:description] = opts.delete(:desc) unless opts[:desc].nil?
|
|
115
|
-
|
|
116
|
-
# check allowed params, all optional should go in meta
|
|
148
|
+
def validate_opts opts
|
|
117
149
|
type = Typero::Type.load opts[:type]
|
|
118
150
|
opts.keys.each do |key|
|
|
119
151
|
type.allowed_opt?(key) {|err| raise ArgumentError, err }
|
|
120
152
|
end
|
|
121
|
-
|
|
122
|
-
field = field.to_sym
|
|
123
|
-
|
|
124
|
-
if opts.delete(:index)
|
|
125
|
-
db :add_index, field
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
@rules[field] = opts
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# pass values for db_schema only
|
|
132
|
-
# db :timestamps
|
|
133
|
-
# db :add_index, :code -> t.add_index :code
|
|
134
|
-
def db *args
|
|
135
|
-
@db_rules.push args.unshift(:db_rule!)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# if method undefine, call set method
|
|
139
|
-
# age Integer -> set :age, type: :integer
|
|
140
|
-
# email Array[:emails] -> set :emails, Array[:email]
|
|
141
|
-
def method_missing field, *args, &block
|
|
142
|
-
set field, *args, &block
|
|
143
153
|
end
|
|
144
154
|
end
|
|
145
155
|
end
|
data/lib/typero/schema.rb
CHANGED
|
@@ -1,31 +1,8 @@
|
|
|
1
|
-
# Typero.schema :user, type: :model do
|
|
2
|
-
|
|
3
|
-
# Typero.schema :some_name, type: :model, db: DB_LOG do
|
|
4
|
-
# set :name, String, req: true
|
|
5
|
-
# set :email, :email, req: true
|
|
6
|
-
# set :emails, [:email], min: 2
|
|
7
|
-
# end
|
|
8
|
-
#
|
|
9
|
-
# rules = Typero.schema :some_name
|
|
10
|
-
#
|
|
11
|
-
# or
|
|
12
|
-
#
|
|
13
|
-
# rules = Typero.schema do
|
|
14
|
-
# string :name, req: true # generic email string
|
|
15
|
-
# email :email, req: true # string of type email
|
|
16
|
-
# emails [:skills], min: 2 # list of emails in filed named "emails"
|
|
17
|
-
# end
|
|
18
|
-
#
|
|
19
|
-
# errors = rules.validate (@object || @hash) {|errors| ... }
|
|
20
|
-
# rules.valid? (@object)
|
|
21
|
-
|
|
22
1
|
module Typero
|
|
23
2
|
class Schema
|
|
24
3
|
SCHEMA_STORE ||= {}
|
|
25
4
|
|
|
26
|
-
attr_reader :klass
|
|
27
|
-
attr_reader :schema
|
|
28
|
-
attr_reader :opts
|
|
5
|
+
attr_reader :klass, :opts
|
|
29
6
|
|
|
30
7
|
# accepts dsl block to define schema
|
|
31
8
|
# or define: keyword for internal use (only/except)
|
|
@@ -63,77 +40,31 @@ module Typero
|
|
|
63
40
|
# validates any instance object with hash variable interface
|
|
64
41
|
# it also coerces values
|
|
65
42
|
def validate object, options = nil
|
|
66
|
-
@object
|
|
67
|
-
@errors
|
|
68
|
-
options
|
|
69
|
-
|
|
70
|
-
# remove undefined keys if Hash provided
|
|
71
|
-
if options[:strict] && object.is_a?(Hash)
|
|
72
|
-
undefined = object.keys.map(&:to_s) - @schema.rules.keys.map(&:to_s)
|
|
73
|
-
object.delete_if { |k, _| undefined.include?(k.to_s) }
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
@schema.rules.each do |field, opts|
|
|
77
|
-
# force field as a symbol
|
|
78
|
-
field = field.to_sym
|
|
79
|
-
|
|
80
|
-
opts.keys.each do |k|
|
|
81
|
-
opts[k] = @object.instance_exec(&opts[k]) if opts[k].is_a?(Proc)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# set value to default if value is blank and default given
|
|
85
|
-
if !opts[:default].nil? && @object[field].to_s.blank?
|
|
86
|
-
@object[field] = opts[:default]
|
|
87
|
-
end
|
|
43
|
+
@object = object
|
|
44
|
+
@errors = {}
|
|
45
|
+
options ||= {}
|
|
88
46
|
|
|
89
|
-
|
|
90
|
-
if @object.key?(field)
|
|
91
|
-
value = @object[field]
|
|
92
|
-
elsif @object.key?(field.to_s)
|
|
93
|
-
# invalid string key, needs fix
|
|
94
|
-
value = @object[field] = @object.delete(field.to_s)
|
|
95
|
-
end
|
|
96
|
-
else
|
|
97
|
-
value = @object[field]
|
|
98
|
-
end
|
|
47
|
+
strip_undefined_keys! if options[:strict] && object.is_a?(Hash)
|
|
99
48
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
value = value.to_s.split(opts[:delimiter])
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
value = value
|
|
107
|
-
.flatten
|
|
108
|
-
.map { |el| el.to_s == '' ? nil : check_field_value(field, el, opts) }
|
|
109
|
-
.compact
|
|
110
|
-
|
|
111
|
-
value = Set.new(value).to_a unless opts[:duplicates]
|
|
112
|
-
|
|
113
|
-
opts[:max_count] ||= 100
|
|
114
|
-
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]
|
|
115
|
-
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]
|
|
49
|
+
@schema.rules.each do |field, raw_opts|
|
|
50
|
+
field = field.to_sym
|
|
51
|
+
opts = resolve_opts(raw_opts)
|
|
116
52
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
value = nil if value.to_s == ''
|
|
53
|
+
apply_default field, opts
|
|
54
|
+
value = read_value field
|
|
120
55
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
56
|
+
value =
|
|
57
|
+
if opts[:array]
|
|
58
|
+
validate_array field, value, opts
|
|
59
|
+
else
|
|
60
|
+
validate_scalar field, value, opts
|
|
125
61
|
end
|
|
126
62
|
|
|
127
|
-
value = check_field_value field, value, opts
|
|
128
|
-
|
|
129
|
-
add_required_error field, value, opts
|
|
130
|
-
end
|
|
131
|
-
|
|
132
63
|
# present empty string values as nil
|
|
133
|
-
@object[field] = value
|
|
64
|
+
@object[field] = blank?(value) ? nil : value
|
|
134
65
|
end
|
|
135
66
|
|
|
136
|
-
if @errors.
|
|
67
|
+
if @errors.any? && block_given?
|
|
137
68
|
@errors.each { |k, v| yield(k, v) }
|
|
138
69
|
end
|
|
139
70
|
|
|
@@ -141,26 +72,21 @@ module Typero
|
|
|
141
72
|
end
|
|
142
73
|
|
|
143
74
|
def valid? object
|
|
144
|
-
|
|
145
|
-
|
|
75
|
+
validate(object).empty?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# returns raw db rules like [:timestamps] or [:add_index, :code]
|
|
79
|
+
def db_rules
|
|
80
|
+
@schema.db_rules
|
|
146
81
|
end
|
|
147
82
|
|
|
148
83
|
# returns field, db_type, db_opts
|
|
149
84
|
def db_schema
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
# add array true to field if not defined in schema
|
|
155
|
-
schema_opts = @schema.rules[field]
|
|
156
|
-
opts[:array] = true if schema_opts[:array]
|
|
157
|
-
|
|
158
|
-
total << [field, type, opts]
|
|
85
|
+
@schema.rules.map do |field, opts|
|
|
86
|
+
type, db_opts = Typero::Type.load(opts[:type]).new(nil, opts).db_field
|
|
87
|
+
db_opts[:array] = true if opts[:array]
|
|
88
|
+
[field, type, db_opts]
|
|
159
89
|
end
|
|
160
|
-
|
|
161
|
-
out += @schema.db_rules
|
|
162
|
-
|
|
163
|
-
out
|
|
164
90
|
end
|
|
165
91
|
|
|
166
92
|
# iterate through all the rules via block interface
|
|
@@ -178,33 +104,78 @@ module Typero
|
|
|
178
104
|
|
|
179
105
|
private
|
|
180
106
|
|
|
181
|
-
#
|
|
182
|
-
def
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if msg && msg[0, 1].downcase == msg[0, 1]
|
|
187
|
-
field_name = opts[:name] || field.to_s.sub(/_id$/, "").capitalize
|
|
188
|
-
msg = "%s %s" % [field_name, msg]
|
|
189
|
-
end
|
|
107
|
+
# remove keys not defined in schema
|
|
108
|
+
def strip_undefined_keys!
|
|
109
|
+
defined_keys = @schema.rules.keys.map(&:to_s)
|
|
110
|
+
@object.delete_if { |k, _| !defined_keys.include?(k.to_s) }
|
|
111
|
+
end
|
|
190
112
|
|
|
191
|
-
|
|
113
|
+
# dup opts so Proc resolution does not mutate stored schema
|
|
114
|
+
def resolve_opts raw_opts
|
|
115
|
+
opts = raw_opts.dup
|
|
116
|
+
opts.each do |k, v|
|
|
117
|
+
opts[k] = @object.instance_exec(&v) if v.is_a?(Proc)
|
|
192
118
|
end
|
|
119
|
+
opts
|
|
193
120
|
end
|
|
194
121
|
|
|
195
|
-
def
|
|
196
|
-
|
|
122
|
+
def apply_default field, opts
|
|
123
|
+
if !opts[:default].nil? && @object[field].to_s.blank?
|
|
124
|
+
@object[field] = opts[:default]
|
|
125
|
+
end
|
|
197
126
|
end
|
|
198
127
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
128
|
+
# read value from object, normalizing string keys to symbols for Hash
|
|
129
|
+
def read_value field
|
|
130
|
+
if @object.respond_to?(:key?)
|
|
131
|
+
if @object.key?(field)
|
|
132
|
+
@object[field]
|
|
133
|
+
elsif @object.key?(field.to_s)
|
|
134
|
+
@object[field] = @object.delete(field.to_s)
|
|
135
|
+
end
|
|
136
|
+
else
|
|
137
|
+
@object[field]
|
|
138
|
+
end
|
|
203
139
|
end
|
|
204
140
|
|
|
205
|
-
def
|
|
206
|
-
|
|
207
|
-
|
|
141
|
+
def validate_array field, value, opts
|
|
142
|
+
unless value.respond_to?(:each)
|
|
143
|
+
delimiter = opts[:delimiter] || /\s*[,\n]\s*/
|
|
144
|
+
value = value.to_s.split(delimiter)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
value = value
|
|
148
|
+
.flatten
|
|
149
|
+
.map { |el| el.to_s == '' ? nil : coerce_value(field, el, opts) }
|
|
150
|
+
.compact
|
|
151
|
+
|
|
152
|
+
value = Set.new(value).to_a unless opts[:duplicates]
|
|
153
|
+
|
|
154
|
+
max_count = opts[:max_count] || 100
|
|
155
|
+
add_error(field, 'Max number of array elements is %d, you have %d' % [max_count, value.length], opts) if value.length > max_count
|
|
156
|
+
add_error(field, 'Min number of array elements is %d, you have %d' % [opts[:min_count], value.length], opts) if opts[:min_count] && value.length < opts[:min_count]
|
|
157
|
+
|
|
158
|
+
add_required_error field, value.first, opts
|
|
159
|
+
value
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def validate_scalar field, value, opts
|
|
163
|
+
value = nil if value.to_s == ''
|
|
164
|
+
|
|
165
|
+
allowed = opts[:allow] || opts[:allowed] || opts[:values]
|
|
166
|
+
if value && allowed && !allowed.map(&:to_s).include?(value.to_s)
|
|
167
|
+
add_error field, 'Value "%s" is not allowed' % value, opts
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
value = coerce_value field, value, opts
|
|
171
|
+
add_required_error field, value, opts
|
|
172
|
+
value
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# coerce a single value through its type class
|
|
176
|
+
def coerce_value field, value, opts
|
|
177
|
+
klass = Typero::Type.load(opts[:type])
|
|
178
|
+
check = klass.new value, opts
|
|
208
179
|
check.get
|
|
209
180
|
rescue TypeError => e
|
|
210
181
|
if e.message[0] == '{'
|
|
@@ -217,5 +188,29 @@ module Typero
|
|
|
217
188
|
rescue JSON::ParserError
|
|
218
189
|
add_error field, e.message, opts
|
|
219
190
|
end
|
|
191
|
+
|
|
192
|
+
# adds error to hash, prefixing with field name if message starts lowercase
|
|
193
|
+
def add_error field, msg, opts
|
|
194
|
+
if @errors[field]
|
|
195
|
+
@errors[field] += ", %s" % msg
|
|
196
|
+
else
|
|
197
|
+
if msg && msg[0, 1].downcase == msg[0, 1]
|
|
198
|
+
field_name = opts[:name] || field.to_s.sub(/_id$/, "").capitalize
|
|
199
|
+
msg = "%s %s" % [field_name, msg]
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
@errors[field] = msg
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def add_required_error field, value, opts
|
|
207
|
+
return unless opts[:required] && value.nil?
|
|
208
|
+
msg = opts[:required].class == TrueClass ? "is required" : opts[:required]
|
|
209
|
+
add_error field, msg, opts
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def blank? value
|
|
213
|
+
value.to_s.sub(/\s+/, '') == ''
|
|
214
|
+
end
|
|
220
215
|
end
|
|
221
216
|
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Shared coordinate extraction from map URLs and plain lat,lon strings.
|
|
2
|
+
# Returns [lat, lon] as strings, or nil if no match.
|
|
3
|
+
|
|
4
|
+
module Typero
|
|
5
|
+
module GeoExtract
|
|
6
|
+
# extract lat, lon from various map URLs or plain "lat,lon" string
|
|
7
|
+
def extract_coords data
|
|
8
|
+
data = data.to_s.strip
|
|
9
|
+
|
|
10
|
+
coords = extract_from_url(data) || extract_from_string(data)
|
|
11
|
+
return nil unless coords
|
|
12
|
+
|
|
13
|
+
coords.map { |c| c.to_s.strip }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def extract_from_url data
|
|
19
|
+
return nil unless data.include?('://')
|
|
20
|
+
|
|
21
|
+
# Google Maps: /@45.815,15.982,... or ?q=45.815,15.982
|
|
22
|
+
if data.include?('/@')
|
|
23
|
+
parts = data.split('/@', 2).last.split(',')
|
|
24
|
+
return [parts[0], parts[1]] if parts.length >= 2
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if data =~ /google.*[?&]q=([-\d.]+),([-\d.]+)/
|
|
28
|
+
return [$1, $2]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# OpenStreetMap: /#map=15/45.815/15.982
|
|
32
|
+
if data =~ /openstreetmap.*#map=\d+\/([-\d.]+)\/([-\d.]+)/
|
|
33
|
+
return [$1, $2]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# OpenStreetMap: ?mlat=45.815&mlon=15.982
|
|
37
|
+
if data =~ /openstreetmap.*[?&]mlat=([-\d.]+).*[?&]mlon=([-\d.]+)/
|
|
38
|
+
return [$1, $2]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Apple Maps: ?ll=45.815,15.982
|
|
42
|
+
if data =~ /maps\.apple\.com.*[?&]ll=([-\d.]+),([-\d.]+)/
|
|
43
|
+
return [$1, $2]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Waze: ?ll=45.815,15.982
|
|
47
|
+
if data =~ /waze\.com.*[?&]ll=([-\d.]+),([-\d.]+)/
|
|
48
|
+
return [$1, $2]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Bing Maps: ?cp=45.815~15.982
|
|
52
|
+
if data =~ /bing\.com.*[?&]cp=([-\d.]+)~([-\d.]+)/
|
|
53
|
+
return [$1, $2]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Bing Maps: /point.45.815_15.982
|
|
57
|
+
if data =~ /bing\.com.*point\.([-\d.]+)_([-\d.]+)/
|
|
58
|
+
return [$1, $2]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def extract_from_string data
|
|
65
|
+
return nil if data.include?('://') || data.include?('POINT')
|
|
66
|
+
|
|
67
|
+
if data.include?(',')
|
|
68
|
+
parts = data.split(/\s*,\s*/)
|
|
69
|
+
return [parts[0], parts[1]] if parts.length >= 2
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/typero/type/type.rb
CHANGED
|
@@ -3,12 +3,11 @@ class Typero::FloatType < Typero::Type
|
|
|
3
3
|
opts :max, 'Maximum value'
|
|
4
4
|
opts :round, 'Round to (decimal spaces)'
|
|
5
5
|
|
|
6
|
-
def
|
|
7
|
-
@value =
|
|
6
|
+
def coerce
|
|
8
7
|
if opts[:round]
|
|
9
|
-
value.to_f.round(opts[:round])
|
|
8
|
+
value { |data| data.to_f.round(opts[:round]) }
|
|
10
9
|
else
|
|
11
|
-
value.to_f
|
|
10
|
+
value { |data| data.to_f }
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
error_for(:min_value_error, opts[:min], value) if opts[:min] && value < opts[:min]
|
|
@@ -19,4 +18,3 @@ class Typero::FloatType < Typero::Type
|
|
|
19
18
|
[:float, {}]
|
|
20
19
|
end
|
|
21
20
|
end
|
|
22
|
-
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
class Typero::OibType < Typero::Type
|
|
2
2
|
error :en, :not_an_oib_error, 'not in an OIB format'
|
|
3
3
|
|
|
4
|
-
def
|
|
4
|
+
def coerce
|
|
5
5
|
value do |data|
|
|
6
6
|
check?(data) ? data.to_s : error_for(:not_an_oib_error)
|
|
7
7
|
end
|
|
@@ -22,7 +22,7 @@ class Typero::OibType < Typero::Type
|
|
|
22
22
|
return false unless oib.match(/^[0-9]{11}$/)
|
|
23
23
|
|
|
24
24
|
control_sum = (0..9).inject(10) do |middle, position|
|
|
25
|
-
middle += oib
|
|
25
|
+
middle += oib[position].to_i
|
|
26
26
|
middle %= 10
|
|
27
27
|
middle = 10 if middle == 0
|
|
28
28
|
middle *= 2
|
|
@@ -32,7 +32,7 @@ class Typero::OibType < Typero::Type
|
|
|
32
32
|
control_sum = 11 - control_sum
|
|
33
33
|
control_sum = 0 if control_sum == 10
|
|
34
34
|
|
|
35
|
-
return control_sum == oib
|
|
35
|
+
return control_sum == oib[10].to_i
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class Typero::PhoneType < Typero::Type
|
|
2
|
+
error :en, :invalid_phone, 'is not a valid phone number'
|
|
3
|
+
|
|
4
|
+
def coerce
|
|
5
|
+
value do |data|
|
|
6
|
+
data.to_s.gsub(/[\(\)\-]/, ' ').gsub(/\s+/, ' ').strip
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
error_for(:invalid_phone) unless value =~ /^[\d\s\+]+$/ && value.scan(/\d/).length >= 5
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def db_schema
|
|
13
|
+
[:string, { limit: 50 }]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -2,20 +2,17 @@
|
|
|
2
2
|
# point = @object.class.xselect("ST_AsText(#{field}) as #{field}").where(id: @object.id).first[field.to_sym]
|
|
3
3
|
|
|
4
4
|
class Typero::PointType < Typero::Type
|
|
5
|
-
|
|
6
|
-
if value.include?('/@')
|
|
7
|
-
# extract value from google maps link
|
|
8
|
-
point = value.split('/@', 2).last.split(',')
|
|
9
|
-
value { [point[0], point[1]].join(',') }
|
|
10
|
-
end
|
|
5
|
+
include Typero::GeoExtract
|
|
11
6
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
end
|
|
7
|
+
def coerce
|
|
8
|
+
if value.is_a?(String) && !value.include?('POINT')
|
|
9
|
+
coords = extract_coords(value)
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
if coords
|
|
12
|
+
value { 'SRID=4326;POINT(%s %s)' % [coords[0], coords[1]] }
|
|
13
|
+
else
|
|
14
|
+
error_for(:unallowed_characters_error)
|
|
15
|
+
end
|
|
19
16
|
end
|
|
20
17
|
end
|
|
21
18
|
|
|
@@ -23,4 +20,3 @@ class Typero::PointType < Typero::Type
|
|
|
23
20
|
[:geography, {}]
|
|
24
21
|
end
|
|
25
22
|
end
|
|
26
|
-
|
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
# Same as point, but we keep data as a float
|
|
1
|
+
# Same as point, but we keep data as a float array [lat, lon]
|
|
2
2
|
|
|
3
3
|
class Typero::SimplePointType < Typero::Type
|
|
4
|
-
|
|
5
|
-
if value.include?('/@')
|
|
6
|
-
# extract value from google maps link
|
|
7
|
-
point = value.split('/@', 2).last.split(',')[0,2]
|
|
8
|
-
value { point }
|
|
9
|
-
end
|
|
4
|
+
include Typero::GeoExtract
|
|
10
5
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
end
|
|
6
|
+
def coerce
|
|
7
|
+
coords = extract_coords(value)
|
|
14
8
|
|
|
15
|
-
|
|
9
|
+
if coords
|
|
10
|
+
value { coords }
|
|
11
|
+
else
|
|
12
|
+
error_for(:unallowed_characters_error)
|
|
13
|
+
end
|
|
16
14
|
end
|
|
17
15
|
|
|
18
16
|
def db_schema
|
|
19
17
|
[:float, { array: true }]
|
|
20
18
|
end
|
|
21
19
|
end
|
|
22
|
-
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class Typero::SlugType < Typero::Type
|
|
2
|
+
opts :max, 'Maximum slug length'
|
|
3
|
+
|
|
4
|
+
error :en, :invalid_slug, 'contains invalid characters'
|
|
5
|
+
|
|
6
|
+
def coerce
|
|
7
|
+
max = opts[:max] || 255
|
|
8
|
+
|
|
9
|
+
value do |data|
|
|
10
|
+
data
|
|
11
|
+
.to_s
|
|
12
|
+
.downcase
|
|
13
|
+
.gsub(/[^\w\-]/, '-')
|
|
14
|
+
.gsub(/\-+/, '-')
|
|
15
|
+
.sub(/^\-/, '')
|
|
16
|
+
.sub(/\-$/, '')[0, max]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
error_for(:invalid_slug) unless value =~ /^[\w][\w\-]*[\w]$|^[\w]$/
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def db_schema
|
|
23
|
+
[:string, { limit: opts[:max] || 255 }]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class Typero::UuidType < Typero::Type
|
|
2
|
+
error :en, :invalid_uuid, 'is not a valid UUID'
|
|
3
|
+
|
|
4
|
+
def coerce
|
|
5
|
+
value { |data| data.to_s.strip.downcase }
|
|
6
|
+
|
|
7
|
+
error_for(:invalid_uuid) unless value =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def db_schema
|
|
11
|
+
[:string, { limit: 36 }]
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/typero.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: typero
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dino Reic
|
|
@@ -21,6 +21,7 @@ files:
|
|
|
21
21
|
- "./lib/typero.rb"
|
|
22
22
|
- "./lib/typero/define.rb"
|
|
23
23
|
- "./lib/typero/schema.rb"
|
|
24
|
+
- "./lib/typero/type/geo_extract.rb"
|
|
24
25
|
- "./lib/typero/type/type.rb"
|
|
25
26
|
- "./lib/typero/type/types/boolean_type.rb"
|
|
26
27
|
- "./lib/typero/type/types/currency_type.rb"
|
|
@@ -35,13 +36,16 @@ files:
|
|
|
35
36
|
- "./lib/typero/type/types/locale_type.rb"
|
|
36
37
|
- "./lib/typero/type/types/model_type.rb"
|
|
37
38
|
- "./lib/typero/type/types/oib_type.rb"
|
|
39
|
+
- "./lib/typero/type/types/phone_type.rb"
|
|
38
40
|
- "./lib/typero/type/types/point_type.rb"
|
|
39
41
|
- "./lib/typero/type/types/simple_point_type.rb"
|
|
42
|
+
- "./lib/typero/type/types/slug_type.rb"
|
|
40
43
|
- "./lib/typero/type/types/string_type.rb"
|
|
41
44
|
- "./lib/typero/type/types/text_type.rb"
|
|
42
45
|
- "./lib/typero/type/types/time_type.rb"
|
|
43
46
|
- "./lib/typero/type/types/timezone_type.rb"
|
|
44
47
|
- "./lib/typero/type/types/url_type.rb"
|
|
48
|
+
- "./lib/typero/type/types/uuid_type.rb"
|
|
45
49
|
- "./lib/typero/typero.rb"
|
|
46
50
|
homepage: https://github.com/dux/typero
|
|
47
51
|
licenses:
|
|
@@ -61,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
61
65
|
- !ruby/object:Gem::Version
|
|
62
66
|
version: '0'
|
|
63
67
|
requirements: []
|
|
64
|
-
rubygems_version:
|
|
68
|
+
rubygems_version: 4.0.8
|
|
65
69
|
specification_version: 4
|
|
66
70
|
summary: Ruby type system
|
|
67
71
|
test_files: []
|