typero 0.4.0 → 0.9.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/adapters/sequel.rb +69 -0
- data/lib/typero.rb +12 -170
- data/lib/typero/exporter.rb +93 -0
- data/lib/typero/params.rb +99 -0
- data/lib/typero/schema.rb +132 -0
- data/lib/typero/type/type.rb +110 -0
- 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 +32 -0
- data/lib/typero/type/types/datetime_type.rb +14 -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 +25 -0
- data/lib/typero/type/types/image_type.rb +22 -0
- 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/model_type.rb +21 -0
- data/lib/typero/type/{oib.rb → types/oib_type.rb} +16 -11
- data/lib/typero/type/types/point_type.rb +25 -0
- data/lib/typero/type/types/string_type.rb +20 -0
- data/lib/typero/type/types/text_type.rb +10 -0
- data/lib/typero/type/types/url_type.rb +13 -0
- data/lib/typero/typero.rb +90 -0
- metadata +29 -19
- data/lib/typero/type.rb +0 -39
- data/lib/typero/type/array.rb +0 -44
- data/lib/typero/type/boolean.rb +0 -10
- data/lib/typero/type/email.rb +0 -21
- data/lib/typero/type/float.rb +0 -21
- data/lib/typero/type/hash.rb +0 -14
- data/lib/typero/type/integer.rb +0 -19
- data/lib/typero/type/label.rb +0 -13
- data/lib/typero/type/string.rb +0 -22
- data/lib/typero/type/url.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e78c338c182793ab2bffd1b4c5469d5285f57f55697402b6e80fb1881a3f7a8
|
4
|
+
data.tar.gz: 18a803b38ef8a24cb3756a43e5b5704082a08f4d805be57239cb054c28e6f132
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c02d8cec42f465c5cf34b0b69c856cf193f1c5e289898af0aefdebf1d61ed91c95148a1fa9ca1c047a07810c99bd518946dbfe323e0e53c4502b66b08910c85
|
7
|
+
data.tar.gz: de185fca3ac80a4e7e9d6b87778872b3b352f89678f334e24b0047480d401cbd0abf95bb8940ba0e29d9d3f485ef62521b38335f1f6cc3a7d1f59194383f4a67
|
data/.version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.9.0
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sequel::Plugins::TyperoAttributes
|
4
|
+
module ClassMethods
|
5
|
+
def typero
|
6
|
+
Typero.new self
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
# calling typero! on any object will validate all fields
|
12
|
+
def typero! field_name=nil
|
13
|
+
return unless Typero.defined?(self.class)
|
14
|
+
|
15
|
+
typero = self.class.typero
|
16
|
+
|
17
|
+
typero.validate(self) do |name, err|
|
18
|
+
errors.add(name, err) unless (errors.on(name) || []).include?(err)
|
19
|
+
end
|
20
|
+
|
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
|
+
|
25
|
+
# check uniqe fields
|
26
|
+
if rule[:unique]
|
27
|
+
id = self[:id] || 0
|
28
|
+
value = self[field]
|
29
|
+
|
30
|
+
# we only check if field is changed
|
31
|
+
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]
|
33
|
+
errors.add(field, error) unless (errors.on(field) || []).include?(error)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# check protected fields
|
38
|
+
if rule[:protected] && self[:id]
|
39
|
+
if column_changed?(field)
|
40
|
+
error = rule[:protected].class == TrueClass ? "value once defined can't be overwritten." : rule[:protected]
|
41
|
+
errors.add(field, error) unless (errors.on(field) || []).include?(error)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
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
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module DatasetMethods
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
Sequel::Model.plugin :typero_attributes
|
69
|
+
|
data/lib/typero.rb
CHANGED
@@ -1,172 +1,14 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
#
|
9
|
-
|
10
|
-
|
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| ... }
|
18
|
-
|
19
|
-
class Typero
|
20
|
-
attr_reader :rules
|
21
|
-
|
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.set
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
###
|
51
|
-
|
52
|
-
# accepts dsl block to
|
53
|
-
def initialize hash={}, &block
|
54
|
-
@rules = {}
|
55
|
-
hash.each { |k, v| set(k, v) }
|
56
|
-
instance_exec &block if block
|
57
|
-
end
|
58
|
-
|
59
|
-
# set :age, type: :integer -> integer :age
|
60
|
-
# email :email
|
61
|
-
# set :email, [:emails]
|
62
|
-
# email [:emails]
|
63
|
-
def method_missing name, *args, &block
|
64
|
-
field = args.shift
|
65
|
-
|
66
|
-
if field.class == Array
|
67
|
-
field = field.first
|
68
|
-
name = [name]
|
69
|
-
end
|
70
|
-
|
71
|
-
set field, type=name, *args
|
72
|
-
end
|
73
|
-
|
74
|
-
# coerce opts values
|
75
|
-
def parse_option opts
|
76
|
-
opts[:type] ||= 'string'
|
77
|
-
opts[:req] = opts.delete(:required) unless opts[:required].nil?
|
78
|
-
|
79
|
-
if opts[:type].is_a?(Array)
|
80
|
-
opts[:array_type] = opts[:type][0] if opts[:type][0]
|
81
|
-
opts[:type] = 'array'
|
82
|
-
end
|
83
|
-
|
84
|
-
opts[:type] = opts[:type].to_s.downcase
|
85
|
-
|
86
|
-
opts[:required] = opts[:req] unless opts[:req].nil?
|
87
|
-
opts[:unique] = opts[:uniq] unless opts[:uniq].nil?
|
88
|
-
opts[:description] = opts[:desc] unless opts[:desc].nil?
|
89
|
-
|
90
|
-
# allowed_names = [:req, :uniq, :protected, :type, :min, :max, :array_type, :default, :downcase, :desc, :label]
|
91
|
-
# opts.keys.each do |key|
|
92
|
-
# raise ArgumentError.new('%s is not allowed as typero option' % key) unless allowed_names.index(key)
|
93
|
-
# end
|
94
|
-
|
95
|
-
opts
|
96
|
-
end
|
97
|
-
|
98
|
-
# used in dsl to define value
|
99
|
-
def set field, type=String, opts={}
|
100
|
-
opts = type.is_a?(Hash) ? type : opts.merge(type: type)
|
101
|
-
|
102
|
-
opts[:type] ||= :string
|
103
|
-
klass = Typero::Type.load opts[:type]
|
104
|
-
@rules[field] = parse_option opts
|
105
|
-
end
|
106
|
-
|
107
|
-
def safe_type type
|
108
|
-
type.to_s.gsub(/[^\w]/,'').classify
|
109
|
-
end
|
110
|
-
|
111
|
-
# adds error to array or prefixes with field name
|
112
|
-
def add_error field, msg
|
113
|
-
if @errors[field]
|
114
|
-
@errors[field] += ', %s' % msg
|
115
|
-
else
|
116
|
-
if msg[0,1].downcase == msg[0,1]
|
117
|
-
field_name = field.to_s.sub(/_id$/,'').humanize
|
118
|
-
msg = '%s %s' % [field_name, msg]
|
119
|
-
end
|
120
|
-
|
121
|
-
@errors[field] = msg
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
# validates any instance object or object with hash variable interface
|
126
|
-
# it also coarces values
|
127
|
-
def validate instance
|
128
|
-
@errors = {}
|
129
|
-
|
130
|
-
@rules.each do |field, opts|
|
131
|
-
# set value to default if value is blank and default given
|
132
|
-
instance[field] = opts[:default] if opts[:default] && instance[field].blank?
|
133
|
-
|
134
|
-
# get field value
|
135
|
-
value = instance[field]
|
136
|
-
|
137
|
-
if value.present?
|
138
|
-
klass = 'Typero::%sType' % safe_type(opts[:type])
|
139
|
-
check = klass.constantize.new value, opts
|
140
|
-
check.value = check.default if check.value.nil?
|
141
|
-
|
142
|
-
unless check.value.nil?
|
143
|
-
begin
|
144
|
-
check.set
|
145
|
-
check.validate
|
146
|
-
instance[field] = check.value
|
147
|
-
rescue TypeError => e
|
148
|
-
add_error field, e.message
|
149
|
-
end
|
150
|
-
end
|
151
|
-
elsif opts[:required]
|
152
|
-
msg = opts[:required].class == TrueClass ? 'is required' : opts[:req]
|
153
|
-
add_error field, msg
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
if @errors.keys.length > 0 && block_given?
|
158
|
-
@errors.each { |k,v| yield(k, v) }
|
159
|
-
end
|
160
|
-
|
161
|
-
@errors
|
162
|
-
end
|
163
|
-
|
164
|
-
def valid? instance
|
165
|
-
errors = validate instance
|
166
|
-
errors.keys.length == 0
|
167
|
-
end
|
1
|
+
# base libs
|
2
|
+
require_relative 'typero/typero'
|
3
|
+
require_relative 'typero/schema'
|
4
|
+
require_relative 'typero/params'
|
5
|
+
require_relative 'typero/exporter'
|
6
|
+
require_relative 'typero/type/type'
|
7
|
+
|
8
|
+
# checker types
|
9
|
+
Dir['%s/typero/type/types/*.rb' % __dir__].each do |file|
|
10
|
+
require file
|
168
11
|
end
|
169
12
|
|
170
|
-
|
171
|
-
|
172
|
-
Dir['%s/typero/type/*.rb' % File.dirname(__FILE__)].each { |file| require file }
|
13
|
+
# load Sequel adapter is Sequel is available
|
14
|
+
require_relative './adapters/sequel' if defined?(Sequel)
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Typero
|
2
|
+
class Exporter
|
3
|
+
EXPORTERS ||= {}
|
4
|
+
|
5
|
+
attr_accessor :response
|
6
|
+
|
7
|
+
def initialize model, opts={}
|
8
|
+
opts = { user: opts } unless opts.is_a?(Hash)
|
9
|
+
|
10
|
+
opts[:exporter] ||= model.class
|
11
|
+
opts[:depth] ||= 1
|
12
|
+
opts[:current_depth] ||= 0
|
13
|
+
|
14
|
+
exporter = opts.delete(:exporter).to_s.classify
|
15
|
+
|
16
|
+
@model = model
|
17
|
+
@opts = opts
|
18
|
+
@block = EXPORTERS[exporter] || raise('Exporter "%s" (:%s) not found' % [block, block.underscore])
|
19
|
+
@response = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def render
|
23
|
+
instance_exec &@block
|
24
|
+
@response
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def export object, opts={}
|
30
|
+
if object.is_a?(Symbol)
|
31
|
+
return property object, export(model.send(object))
|
32
|
+
end
|
33
|
+
|
34
|
+
return if @opts[:current_depth] >= @opts[:depth]
|
35
|
+
|
36
|
+
@opts[:current_depth] += 1
|
37
|
+
out = self.class.new(object, @opts.merge(opts)).render
|
38
|
+
@opts[:current_depth] -= 1
|
39
|
+
out
|
40
|
+
end
|
41
|
+
|
42
|
+
def property name, data=:_undefined
|
43
|
+
if block_given?
|
44
|
+
data = yield if data == :_undefined
|
45
|
+
@response[name] = data
|
46
|
+
else
|
47
|
+
data = data == :_undefined ? model.send(name) : data
|
48
|
+
|
49
|
+
if data.respond_to?(:export_json)
|
50
|
+
data = data.export_json
|
51
|
+
elsif data.respond_to?(:to_h)
|
52
|
+
data = data.to_h
|
53
|
+
end
|
54
|
+
|
55
|
+
@response[name] = data
|
56
|
+
end
|
57
|
+
end
|
58
|
+
alias :prop :property
|
59
|
+
|
60
|
+
def hproperty name
|
61
|
+
@response[name] = model[name]
|
62
|
+
end
|
63
|
+
alias :hprop :hproperty
|
64
|
+
|
65
|
+
def namespace name, &block
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
def meta &block
|
70
|
+
namespace :meta, &block
|
71
|
+
end
|
72
|
+
|
73
|
+
def model
|
74
|
+
@model
|
75
|
+
end
|
76
|
+
|
77
|
+
# get current user from globals if globals defined
|
78
|
+
def user
|
79
|
+
if @opts[:user]
|
80
|
+
@opts[:user]
|
81
|
+
elsif defined?(User) && User.respond_to?(:current)
|
82
|
+
User.current
|
83
|
+
elsif defined?(Current) && Current.respond_to?(:user)
|
84
|
+
Current.user
|
85
|
+
elsif current_user = Thread.current[:current_user]
|
86
|
+
current_user
|
87
|
+
else
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# Base class for schema validation
|
2
|
+
|
3
|
+
module Typero
|
4
|
+
class Params
|
5
|
+
attr_reader :rules, :db_rules
|
6
|
+
|
7
|
+
def initialize &block
|
8
|
+
@db_rules = []
|
9
|
+
@rules = {}
|
10
|
+
instance_exec &block
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# used in dsl to define schema field options
|
16
|
+
def set field, *args
|
17
|
+
raise "Field name not given (Typero)" unless field
|
18
|
+
|
19
|
+
if args.first.is_a?(Hash)
|
20
|
+
opts = args.first || {}
|
21
|
+
else
|
22
|
+
opts = args[1] || {}
|
23
|
+
opts[:type] ||= args[0]
|
24
|
+
end
|
25
|
+
|
26
|
+
opts[:type] = :string if opts[:type].nil?
|
27
|
+
|
28
|
+
field = field.to_s
|
29
|
+
|
30
|
+
# name? - opional name
|
31
|
+
if field.include?('?')
|
32
|
+
field = field.sub('?', '')
|
33
|
+
opts[:required] = false
|
34
|
+
end
|
35
|
+
|
36
|
+
opts[:required] = true if opts[:required].nil?
|
37
|
+
|
38
|
+
# array that allows duplicates
|
39
|
+
if opts[:type].is_a?(Array)
|
40
|
+
opts[:type] = opts[:type].first
|
41
|
+
opts[:array] = true
|
42
|
+
end
|
43
|
+
|
44
|
+
# no duplicates array
|
45
|
+
if opts[:type].is_a?(Set)
|
46
|
+
opts[:type] = opts[:type].to_a.first
|
47
|
+
opts[:array] = true
|
48
|
+
end
|
49
|
+
|
50
|
+
# Boolean
|
51
|
+
if opts[:type].is_a?(TrueClass)
|
52
|
+
opts[:required] = false
|
53
|
+
opts[:default] = true
|
54
|
+
opts[:type] = :boolean
|
55
|
+
elsif opts[:type].is_a?(FalseClass)
|
56
|
+
opts[:required] = false
|
57
|
+
opts[:default] = false
|
58
|
+
opts[:type] = :boolean
|
59
|
+
end
|
60
|
+
|
61
|
+
opts[:model] = opts.delete(:schema) if opts[:schema]
|
62
|
+
opts[:type] = :model if opts[:model]
|
63
|
+
|
64
|
+
opts[:type] ||= 'string'
|
65
|
+
opts[:type] = opts[:type].to_s.downcase.to_sym
|
66
|
+
|
67
|
+
opts[:description] = opts.delete(:desc) unless opts[:desc].nil?
|
68
|
+
|
69
|
+
# chek alloed params, all optional should go in meta
|
70
|
+
result = opts.keys - Typero::Type::OPTS_KEYS
|
71
|
+
raise ArgumentError.new('Unallowed Type params found: "%s", allowed: %s' % [result.join(' and '), Typero::Type::OPTS_KEYS.sort]) if result.length > 0
|
72
|
+
|
73
|
+
field = field.to_sym
|
74
|
+
|
75
|
+
db :add_index, field if opts.delete(:index)
|
76
|
+
|
77
|
+
# trigger error if type not found
|
78
|
+
Typero::Type.load opts[:type]
|
79
|
+
|
80
|
+
@rules[field] = opts
|
81
|
+
end
|
82
|
+
|
83
|
+
# pass values for db_schema only
|
84
|
+
# db :timestamps
|
85
|
+
# db :add_index, :code -> t.add_index :code
|
86
|
+
def db *args
|
87
|
+
@db_rules.push args
|
88
|
+
end
|
89
|
+
|
90
|
+
# set :age, type: :integer -> integer :age
|
91
|
+
# email :email
|
92
|
+
#
|
93
|
+
# set :emails, Array[:email]
|
94
|
+
# email Array[:emails]
|
95
|
+
def method_missing field, *args, &block
|
96
|
+
set field, *args
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|