typero 0.4.0 → 0.9.0
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 +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
|