datoki 1.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE +23 -0
- data/README.md +13 -0
- data/VERSION +1 -0
- data/datoki.gemspec +31 -0
- data/lib/datoki.rb +748 -0
- data/specs/datoki.rb +556 -0
- data/specs/lib/helpers.rb +25 -0
- metadata +137 -0
data/lib/datoki.rb
ADDED
@@ -0,0 +1,748 @@
|
|
1
|
+
|
2
|
+
require 'sequel'
|
3
|
+
|
4
|
+
module Datoki
|
5
|
+
|
6
|
+
UTC_NOW_DATE = ::Sequel.lit("CURRENT_DATE")
|
7
|
+
UTC_NOW_RAW = "timezone('UTC'::text, now())"
|
8
|
+
UTC_NOW = ::Sequel.lit("timezone('UTC'::text, now())")
|
9
|
+
|
10
|
+
Invalid = Class.new RuntimeError
|
11
|
+
Schema_Conflict = Class.new RuntimeError
|
12
|
+
|
13
|
+
Actions = [:all, :create, :read, :update, :update_or_create, :trash, :delete]
|
14
|
+
Char_Types = [:varchar, :text]
|
15
|
+
Numeric_Types = [:smallint, :integer, :bigint, :decimal, :numeric]
|
16
|
+
Types = Char_Types + Numeric_Types + [:datetime]
|
17
|
+
|
18
|
+
class << self
|
19
|
+
|
20
|
+
def included klass
|
21
|
+
klass.extend Def_Field
|
22
|
+
klass.initialize_def_field
|
23
|
+
end
|
24
|
+
|
25
|
+
def db db = :return
|
26
|
+
return @db if db == :return
|
27
|
+
@db = db
|
28
|
+
@tables = @db.tables
|
29
|
+
end
|
30
|
+
|
31
|
+
def db_type_to_ruby type, alt = nil
|
32
|
+
if Datoki::Types.include?( type.to_sym )
|
33
|
+
type.to_sym
|
34
|
+
elsif type['character varying']
|
35
|
+
:varchar
|
36
|
+
elsif Datoki::Types.include?(alt)
|
37
|
+
alt
|
38
|
+
else
|
39
|
+
fail("Unknown db type: #{type.inspect}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end # === class self ===
|
44
|
+
|
45
|
+
module Def_Field
|
46
|
+
|
47
|
+
attr_reader :ons, :fields
|
48
|
+
|
49
|
+
def initialize_def_field
|
50
|
+
@record_errors = false
|
51
|
+
@ons = {}
|
52
|
+
@fields = {}
|
53
|
+
@current_field = nil
|
54
|
+
@schema = {}
|
55
|
+
@schema_match = false
|
56
|
+
@table_name = nil
|
57
|
+
name = self.to_s.downcase.to_sym
|
58
|
+
table(name) if Datoki.db.tables.include?(name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def schema_match?
|
62
|
+
@schema_match
|
63
|
+
end
|
64
|
+
|
65
|
+
def record_errors?
|
66
|
+
@record_errors
|
67
|
+
end
|
68
|
+
|
69
|
+
def record_errors
|
70
|
+
@record_errors = true
|
71
|
+
end
|
72
|
+
|
73
|
+
def table name
|
74
|
+
if !@schema.empty? || @table_name
|
75
|
+
fail "Schema/table already defined: #{@table_name.inspect}"
|
76
|
+
end
|
77
|
+
|
78
|
+
db_schema = Datoki.db.schema(name)
|
79
|
+
|
80
|
+
if !db_schema
|
81
|
+
fail "Schema not found for: #{name.inspect}"
|
82
|
+
end
|
83
|
+
|
84
|
+
@table_name = name
|
85
|
+
|
86
|
+
db_schema.each { |pair|
|
87
|
+
@schema[pair.first] = pair.last
|
88
|
+
}
|
89
|
+
|
90
|
+
if @schema.empty?
|
91
|
+
@schema_match = true
|
92
|
+
end
|
93
|
+
|
94
|
+
schema
|
95
|
+
end
|
96
|
+
|
97
|
+
def html_escape
|
98
|
+
@html_escape ||= begin
|
99
|
+
fields.inject({}) { |memo, (name, meta)|
|
100
|
+
memo[name] = meta[:html_escape]
|
101
|
+
memo
|
102
|
+
}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def schema *args
|
107
|
+
case args.size
|
108
|
+
|
109
|
+
when 0
|
110
|
+
@schema
|
111
|
+
|
112
|
+
when 1
|
113
|
+
result = @schema[args.first]
|
114
|
+
fail "Unknown field: #{args.first.inspect}" unless result
|
115
|
+
result
|
116
|
+
|
117
|
+
else
|
118
|
+
fail "Unknown args: #{args.inspect}"
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def inspect_field? target, name, *args
|
124
|
+
case target
|
125
|
+
when :type
|
126
|
+
meta = fields[name]
|
127
|
+
fail "Unknown field: #{name.inspect}" unless meta
|
128
|
+
return true if args.include?(meta[:type])
|
129
|
+
return true if args.include?(:chars) && Char_Types.include?(meta[:type])
|
130
|
+
args.include?(:numeric) && Numeric_Types.include?(meta[:type])
|
131
|
+
else
|
132
|
+
fail "Unknown arg: #{target.inspect}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def field? *args
|
137
|
+
inspect_field?(:type, field[:name], *args)
|
138
|
+
end
|
139
|
+
|
140
|
+
def field *args
|
141
|
+
return fields[@current_field] if args.empty?
|
142
|
+
return fields[args.first] unless block_given?
|
143
|
+
|
144
|
+
name = args.first
|
145
|
+
|
146
|
+
fail "#{name.inspect} already defined." if fields[name]
|
147
|
+
|
148
|
+
fields[name] = {
|
149
|
+
:name => name,
|
150
|
+
:type => :unknown,
|
151
|
+
:english_name => name.to_s.freeze,
|
152
|
+
:allow => {:null => false},
|
153
|
+
:disable => {},
|
154
|
+
:cleaners => {},
|
155
|
+
:on => {}
|
156
|
+
}
|
157
|
+
|
158
|
+
@current_field = name
|
159
|
+
|
160
|
+
if field? :chars
|
161
|
+
field[:allow][:strip] = true
|
162
|
+
end
|
163
|
+
|
164
|
+
if schema[name]
|
165
|
+
if schema[name].has_key? :max_length
|
166
|
+
fields[name][:max] = schema[name][:max_length]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
yield
|
171
|
+
|
172
|
+
fail("Type not specified for #{name.inspect}") if field[:type] == :unknown
|
173
|
+
|
174
|
+
# === check :allow_null and :min are not both set.
|
175
|
+
if field?(:chars) && field[:allow][:null] && field.has_key?(:min) && field[:min] < 1
|
176
|
+
fail "#{field[:type].inspect} can't be both: allow :null && :min = #{field[:min]}"
|
177
|
+
end
|
178
|
+
|
179
|
+
# === Ensure schema matches with field definition:
|
180
|
+
schema_match
|
181
|
+
|
182
|
+
field[:html_escape] = case
|
183
|
+
when field[:html_escape]
|
184
|
+
field[:html_escape]
|
185
|
+
when field?(:numeric)
|
186
|
+
:number
|
187
|
+
when field?(:chars)
|
188
|
+
:string
|
189
|
+
else
|
190
|
+
fail "Unknown html_escape for: #{field[:name].inspect}"
|
191
|
+
end
|
192
|
+
|
193
|
+
@current_field = nil
|
194
|
+
end # === def field
|
195
|
+
|
196
|
+
def schema_match target = :current
|
197
|
+
return true if !@table_name
|
198
|
+
return true if schema_match?
|
199
|
+
|
200
|
+
if target == :all # === do a schema match on entire table
|
201
|
+
schema.each { |name, db_schema|
|
202
|
+
orig_field = @current_field
|
203
|
+
@current_field = name
|
204
|
+
schema_match
|
205
|
+
@current_field = orig_field
|
206
|
+
}
|
207
|
+
|
208
|
+
@schema_match = true
|
209
|
+
return true
|
210
|
+
end # === if target
|
211
|
+
|
212
|
+
name = @current_field
|
213
|
+
db_schema = schema[@current_field]
|
214
|
+
|
215
|
+
if db_schema && !field && db_schema[:type] != :datetime
|
216
|
+
fail Schema_Conflict, "#{name}: #{name.inspect} has not been defined."
|
217
|
+
end
|
218
|
+
|
219
|
+
return true if field[:schema_match]
|
220
|
+
|
221
|
+
if db_schema[:allow_null] != field[:allow][:null]
|
222
|
+
fail Schema_Conflict, "#{name}: :allow_null: #{db_schema[:allow_null].inspect} != #{field[:allow][:null].inspect}"
|
223
|
+
end
|
224
|
+
|
225
|
+
if field?(:chars)
|
226
|
+
if !field[:min].is_a?(Numeric) || field[:min] < 0
|
227
|
+
fail ":min not properly defined for #{name.inspect}: #{field[:min].inspect}"
|
228
|
+
end
|
229
|
+
|
230
|
+
if !field[:max].is_a?(Numeric)
|
231
|
+
fail ":max not properly defined for #{name.inspect}: #{field[:max].inspect}"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
if db_schema.has_key?(:max_length)
|
236
|
+
if field[:max] != db_schema[:max_length]
|
237
|
+
fail Schema_Conflict, "#{name}: :max: #{db_schema[:max_length].inspect} != #{field[:max].inspect}"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
if !!db_schema[:primary_key] != !!field[:primary_key]
|
242
|
+
fail Schema_Conflict, "#{name}: :primary_key: #{db_schema[:primary_key].inspect} != #{field[:primary_key].inspect}"
|
243
|
+
end
|
244
|
+
|
245
|
+
# === match :type
|
246
|
+
db_type = Datoki.db_type_to_ruby db_schema[:db_type], db_schema[:type]
|
247
|
+
type = field[:type]
|
248
|
+
if db_type != type
|
249
|
+
fail Schema_Conflict, "#{name}: :type: #{db_type.inspect} != #{type.inspect}"
|
250
|
+
end
|
251
|
+
|
252
|
+
# === match :max_length
|
253
|
+
db_max = db_schema[:max_length]
|
254
|
+
max = field[:max]
|
255
|
+
if !db_max.nil? && db_max != max
|
256
|
+
fail Schema_Conflict, "#{name}: :max_length: #{db_max.inspect} != #{max.inspect}"
|
257
|
+
end
|
258
|
+
|
259
|
+
# === match :min_length
|
260
|
+
db_min = db_schema[:min_length]
|
261
|
+
min = field[:min]
|
262
|
+
if !db_min.nil? && db_min != min
|
263
|
+
fail Schema_Conflict, "#{name}: :min_length: #{db_min.inspect} != #{min.inspect}"
|
264
|
+
end
|
265
|
+
|
266
|
+
# === match :allow_null
|
267
|
+
if db_schema[:allow_null] != field[:allow][:null]
|
268
|
+
fail Schema_Conflict, "#{name}: :allow_null: #{db_schema[:allow_null].inspect} != #{field[:allow][:null].inspect}"
|
269
|
+
end
|
270
|
+
|
271
|
+
field[:schema_match] = true
|
272
|
+
end
|
273
|
+
|
274
|
+
def on action, meth_name_sym
|
275
|
+
fail "Invalid action: #{action.inspect}" unless Actions.include? action
|
276
|
+
if field
|
277
|
+
field[:on][action] ||= {}
|
278
|
+
field[:on][action][meth_name_sym] = true
|
279
|
+
else
|
280
|
+
@ons[action] ||= {}
|
281
|
+
@ons[action][meth_name_sym] = true
|
282
|
+
end
|
283
|
+
self
|
284
|
+
end
|
285
|
+
|
286
|
+
def primary_key
|
287
|
+
field[:primary_key] = true
|
288
|
+
if field?(:unknown)
|
289
|
+
if schema[field[:name]]
|
290
|
+
type schema[field[:name]][:type]
|
291
|
+
else
|
292
|
+
type :integer
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
true
|
297
|
+
end
|
298
|
+
|
299
|
+
def text *args
|
300
|
+
type :text, *args
|
301
|
+
end
|
302
|
+
|
303
|
+
def href *args
|
304
|
+
field[:html_escape] = :href
|
305
|
+
case args.map(&:class)
|
306
|
+
when []
|
307
|
+
varchar 0, 255
|
308
|
+
when [NilClass]
|
309
|
+
varchar nil, 1, (schema[field[:name]] ? schema[field[:name]][:max_length] : 255)
|
310
|
+
else
|
311
|
+
varchar *args
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
Types.each { |name|
|
316
|
+
eval <<-EOF
|
317
|
+
def #{name} *args
|
318
|
+
type :#{name}, *args
|
319
|
+
end
|
320
|
+
EOF
|
321
|
+
}
|
322
|
+
|
323
|
+
def type name, *args
|
324
|
+
field[:type] = name
|
325
|
+
|
326
|
+
if field? :chars
|
327
|
+
|
328
|
+
enable :strip
|
329
|
+
|
330
|
+
if field?(:text)
|
331
|
+
field[:max] ||= 4000
|
332
|
+
else
|
333
|
+
field[:max] ||= 255
|
334
|
+
end
|
335
|
+
|
336
|
+
if schema[name] && !schema[name][:allow_null]
|
337
|
+
field[:min] = 1
|
338
|
+
end
|
339
|
+
|
340
|
+
end # === if field? :chars
|
341
|
+
|
342
|
+
case args.map(&:class)
|
343
|
+
|
344
|
+
when []
|
345
|
+
# do nothing
|
346
|
+
|
347
|
+
when [Array]
|
348
|
+
field[:options] = args.first
|
349
|
+
enable(:null) if field[:options].include? nil
|
350
|
+
disable :min, :max
|
351
|
+
|
352
|
+
when [NilClass]
|
353
|
+
if field?(:chars)
|
354
|
+
fail "A :min and :max is required for String fields."
|
355
|
+
end
|
356
|
+
|
357
|
+
enable :null
|
358
|
+
|
359
|
+
when [NilClass, Fixnum, Fixnum]
|
360
|
+
field[:allow][:null] = true
|
361
|
+
field[:min] = args[-2]
|
362
|
+
field[:max] = args.last
|
363
|
+
|
364
|
+
when [Fixnum, Fixnum]
|
365
|
+
field[:min], field[:max] = args
|
366
|
+
|
367
|
+
else
|
368
|
+
fail "Unknown args: #{args.inspect}"
|
369
|
+
|
370
|
+
end # === case
|
371
|
+
|
372
|
+
end # === def
|
373
|
+
|
374
|
+
def enable *props
|
375
|
+
props.each { |prop|
|
376
|
+
case prop
|
377
|
+
when :strip, :null
|
378
|
+
field[:allow][prop] = true
|
379
|
+
else
|
380
|
+
field[:cleaners][prop] = true
|
381
|
+
end
|
382
|
+
}
|
383
|
+
end
|
384
|
+
|
385
|
+
def disable *props
|
386
|
+
props.each { |prop|
|
387
|
+
case prop
|
388
|
+
when :min, :max
|
389
|
+
field.delete prop
|
390
|
+
when :strip, :null
|
391
|
+
field[:allow][prop] = false
|
392
|
+
else
|
393
|
+
field[:cleaners][prop] = false
|
394
|
+
end
|
395
|
+
}
|
396
|
+
end
|
397
|
+
|
398
|
+
def set_to *args
|
399
|
+
field[:cleaners][:set_to] ||= []
|
400
|
+
field[:cleaners][:set_to].concat args
|
401
|
+
end
|
402
|
+
|
403
|
+
def equal_to *args
|
404
|
+
field[:cleaners][:equal_to] ||= []
|
405
|
+
field[:cleaners][:equal_to].concat args
|
406
|
+
end
|
407
|
+
|
408
|
+
def included_in arr
|
409
|
+
field[:cleaners][:included_in] ||= []
|
410
|
+
field[:cleaners][:included_in].concat arr
|
411
|
+
end
|
412
|
+
|
413
|
+
# === String-only methods ===========
|
414
|
+
%w{
|
415
|
+
upcase
|
416
|
+
to_i
|
417
|
+
}.each { |name|
|
418
|
+
eval <<-EOF
|
419
|
+
def #{name} *args
|
420
|
+
fail "Not allowed for \#{field[:type]}" unless field?(:chars)
|
421
|
+
enable :#{name}
|
422
|
+
end
|
423
|
+
EOF
|
424
|
+
}
|
425
|
+
|
426
|
+
def match *args
|
427
|
+
fail "Not allowed for #{field[:type].inspect}" unless field?(:chars)
|
428
|
+
field[:cleaners][:match] ||= []
|
429
|
+
field[:cleaners][:match] << args
|
430
|
+
end
|
431
|
+
|
432
|
+
def not_match *args
|
433
|
+
fail "Not allowed for #{field[:type].inspect}" unless field?(:chars)
|
434
|
+
field[:cleaners][:not_match] ||= []
|
435
|
+
field[:cleaners][:not_match] << args
|
436
|
+
self
|
437
|
+
end
|
438
|
+
|
439
|
+
def create h = {}
|
440
|
+
r = new
|
441
|
+
r.create h
|
442
|
+
end
|
443
|
+
|
444
|
+
end # === Def_Field
|
445
|
+
|
446
|
+
# ================= Instance Methods ===============
|
447
|
+
|
448
|
+
def initialize data = nil
|
449
|
+
@data = nil
|
450
|
+
@new_data = nil
|
451
|
+
@field_name = nil
|
452
|
+
@clean_data = nil
|
453
|
+
@errors = nil
|
454
|
+
|
455
|
+
self.class.schema_match(:all)
|
456
|
+
end
|
457
|
+
|
458
|
+
def errors
|
459
|
+
@errors ||= {}
|
460
|
+
end
|
461
|
+
|
462
|
+
def errors?
|
463
|
+
@errors && !@errors.empty?
|
464
|
+
end
|
465
|
+
|
466
|
+
def save_error msg
|
467
|
+
@errors ||= {}
|
468
|
+
@errors[field_name] ||= {}
|
469
|
+
@errors[field_name][:msg] = msg
|
470
|
+
@errors[field_name][:value] = val
|
471
|
+
end
|
472
|
+
|
473
|
+
def clean_data
|
474
|
+
@clean_data ||= {}
|
475
|
+
end
|
476
|
+
|
477
|
+
def new_data
|
478
|
+
@new_data ||= {}
|
479
|
+
end
|
480
|
+
|
481
|
+
def fail! msg
|
482
|
+
err_msg = msg.gsub(/!([a-z\_\-]+)/i) { |raw|
|
483
|
+
name = $1
|
484
|
+
case name
|
485
|
+
when "English_name"
|
486
|
+
self.class.fields[field_name][:english_name].capitalize.gsub('_', ' ')
|
487
|
+
when "ENGLISH_NAME"
|
488
|
+
self.class.fields[field_name][:english_name].upcase.gsub('_', ' ')
|
489
|
+
when "max", "min", "exact_size"
|
490
|
+
self.class.fields[field_name][name.downcase.to_sym]
|
491
|
+
else
|
492
|
+
fail "Unknown value: #{name}"
|
493
|
+
end
|
494
|
+
}
|
495
|
+
|
496
|
+
if self.class.record_errors?
|
497
|
+
save_error err_msg
|
498
|
+
throw :error_saved
|
499
|
+
else
|
500
|
+
fail Invalid, err_msg
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
def field_name *args
|
505
|
+
case args.size
|
506
|
+
when 0
|
507
|
+
fail "Field name not set." unless @field_name
|
508
|
+
@field_name
|
509
|
+
when 1
|
510
|
+
@field_name = args.first
|
511
|
+
else
|
512
|
+
fail "Unknown args: #{args.inspect}"
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
def val
|
517
|
+
if clean_data.has_key?(field_name)
|
518
|
+
clean_data[field_name]
|
519
|
+
else
|
520
|
+
new_data[field_name]
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
def val! new_val
|
525
|
+
clean_data[field_name] = new_val
|
526
|
+
end
|
527
|
+
|
528
|
+
def field *args
|
529
|
+
case args.size
|
530
|
+
when 0
|
531
|
+
self.class.fields[field_name]
|
532
|
+
when 1
|
533
|
+
self.class.fields[args.first]
|
534
|
+
else
|
535
|
+
fail "Unknown args: #{args.inspect}"
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
def field? *args
|
540
|
+
self.class.inspect_field? :type, field_name, *args
|
541
|
+
end
|
542
|
+
|
543
|
+
def run action
|
544
|
+
self.class.fields.each { |f_name, f_meta|
|
545
|
+
|
546
|
+
field_name f_name
|
547
|
+
is_set = new_data.has_key?(field_name)
|
548
|
+
is_update = action == :update
|
549
|
+
is_nil = is_set && new_data[field_name].nil?
|
550
|
+
|
551
|
+
# === Should the field be skipped? ===============
|
552
|
+
next if !is_set && is_update
|
553
|
+
next if !is_set && field[:primary_key]
|
554
|
+
next if field[:allow][:null] && (!is_set || is_nil)
|
555
|
+
|
556
|
+
if is_set
|
557
|
+
val! new_data[field_name]
|
558
|
+
elsif field.has_key?(:default)
|
559
|
+
val! field[:default]
|
560
|
+
end
|
561
|
+
|
562
|
+
if val.is_a?(String) && field[:allow][:strip]
|
563
|
+
val! val.strip
|
564
|
+
end
|
565
|
+
|
566
|
+
if field?(:chars) && !field.has_key?(:min) && val.is_a?(String) && field[:allow][:null]
|
567
|
+
val! nil
|
568
|
+
end
|
569
|
+
|
570
|
+
catch :error_saved do
|
571
|
+
|
572
|
+
if field?(:numeric) && val.is_a?(String)
|
573
|
+
clean_val = Integer(val) rescue String
|
574
|
+
if clean_val == String
|
575
|
+
fail! "!English_name must be numeric."
|
576
|
+
else
|
577
|
+
val! clean_val
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
# === check required. ============
|
582
|
+
if val.nil? && !field[:allow][:null]
|
583
|
+
fail! "!English_name is required."
|
584
|
+
end
|
585
|
+
|
586
|
+
if field?(:text) && val.is_a?(String) && val.empty? && field[:min].to_i > 0
|
587
|
+
fail! "!English_name is required."
|
588
|
+
end
|
589
|
+
# ================================
|
590
|
+
|
591
|
+
# === check min, max ======
|
592
|
+
if val.is_a?(String) || val.is_a?(Numeric)
|
593
|
+
case [field[:min], field[:max]].map(&:class)
|
594
|
+
|
595
|
+
when [NilClass, NilClass]
|
596
|
+
# do nothing
|
597
|
+
|
598
|
+
when [NilClass, Fixnum]
|
599
|
+
case
|
600
|
+
when val.is_a?(String) && val.size > field[:max]
|
601
|
+
fail! "!English_name can't be longer than !max characters."
|
602
|
+
when val.is_a?(Numeric) && val > field[:max]
|
603
|
+
fail! "!English_name can't be higher than !max."
|
604
|
+
end
|
605
|
+
|
606
|
+
when [Fixnum, NilClass]
|
607
|
+
case
|
608
|
+
when val.is_a?(String) && val.size < field[:min]
|
609
|
+
fail! "!English_name can't be shorter than !min characters."
|
610
|
+
when val.is_a?(Numeric) && val < field[:min]
|
611
|
+
fail! "!English_name can't be less than !min."
|
612
|
+
end
|
613
|
+
|
614
|
+
when [Fixnum, Fixnum]
|
615
|
+
case
|
616
|
+
when val.is_a?(String) && (val.size < field[:min] || val.size > field[:max])
|
617
|
+
fail! "!English_name must be between !min and !max characters."
|
618
|
+
when val.is_a?(Numeric) && (val < field[:min] || val > field[:max])
|
619
|
+
fail! "!English_name must be between !min and !max."
|
620
|
+
end
|
621
|
+
|
622
|
+
else
|
623
|
+
fail "Unknown values for :min, :max: #{field[:min].inspect}, #{field[:max].inspect}"
|
624
|
+
end
|
625
|
+
end # === if
|
626
|
+
# ================================
|
627
|
+
|
628
|
+
# === to_i if necessary ==========
|
629
|
+
if field?(:numeric)
|
630
|
+
val! val.to_i
|
631
|
+
end
|
632
|
+
# ================================
|
633
|
+
|
634
|
+
# === :strip if necessary ========
|
635
|
+
if field?(:chars) && field[:allow][:strip] && val.is_a?(String)
|
636
|
+
val! val.strip
|
637
|
+
end
|
638
|
+
# ================================
|
639
|
+
|
640
|
+
# === Is value in options? =======
|
641
|
+
if field[:options]
|
642
|
+
if !field[:options].include?(val)
|
643
|
+
fail! "!English_name can only be: #{field[:options].map(&:inspect).join ', '}"
|
644
|
+
end
|
645
|
+
end
|
646
|
+
# ================================
|
647
|
+
|
648
|
+
field[:cleaners].each { |cleaner, args|
|
649
|
+
next if args === false # === cleaner has been disabled.
|
650
|
+
|
651
|
+
case cleaner
|
652
|
+
|
653
|
+
when :type
|
654
|
+
case
|
655
|
+
when field?(:numeric) && !val.is_a?(Integer)
|
656
|
+
fail! "!English_name needs to be an integer."
|
657
|
+
when field?(:chars) && !val.is_a?(String)
|
658
|
+
fail! "!English_name needs to be a String."
|
659
|
+
end
|
660
|
+
|
661
|
+
when :exact_size
|
662
|
+
if val.size != field[:exact_size]
|
663
|
+
case
|
664
|
+
when field?(:chars) || val.is_a?(String)
|
665
|
+
fail! "!English_name needs to be !exact_size in length."
|
666
|
+
else
|
667
|
+
fail! "!English_name can only be !exact_size in size."
|
668
|
+
end
|
669
|
+
end
|
670
|
+
|
671
|
+
when :set_to
|
672
|
+
args.each { |meth|
|
673
|
+
val! send(meth)
|
674
|
+
}
|
675
|
+
|
676
|
+
when :equal_to
|
677
|
+
args.each { |pair|
|
678
|
+
meth, msg, other = pair
|
679
|
+
target = send(meth)
|
680
|
+
fail!(msg || "!English_name must be equal to: #{target.inspect}") unless val == target
|
681
|
+
}
|
682
|
+
|
683
|
+
when :included_in
|
684
|
+
arr, msg, other = args
|
685
|
+
fail!(msg || "!English_name must be one of these: #{arr.join ', '}") unless arr.include?(val)
|
686
|
+
|
687
|
+
when :upcase
|
688
|
+
val! val.upcase
|
689
|
+
|
690
|
+
when :match
|
691
|
+
args.each { |pair|
|
692
|
+
regex, msg, other = pair
|
693
|
+
if val !~ regex
|
694
|
+
fail!(msg || "!English_name must match #{regex.inspect}")
|
695
|
+
end
|
696
|
+
}
|
697
|
+
|
698
|
+
when :not_match
|
699
|
+
args.each { |pair|
|
700
|
+
regex, msg, other = pair
|
701
|
+
if val =~ regex
|
702
|
+
fail!(msg || "!English_name must not match #{regex.inspect}")
|
703
|
+
end
|
704
|
+
}
|
705
|
+
|
706
|
+
else
|
707
|
+
fail "Cleaner not implemented: #{cleaner.inspect}"
|
708
|
+
end # === case cleaner
|
709
|
+
|
710
|
+
|
711
|
+
} # === field[:cleaners].each
|
712
|
+
|
713
|
+
field[:on][action].each { |meth, is_enabled|
|
714
|
+
next unless is_enabled
|
715
|
+
send meth
|
716
|
+
} if field[:on][action]
|
717
|
+
|
718
|
+
end # === catch :error_saved
|
719
|
+
} # === field
|
720
|
+
|
721
|
+
return if errors?
|
722
|
+
|
723
|
+
self.class.ons.each { |action, meths|
|
724
|
+
meths.each { |meth, is_enabled|
|
725
|
+
next unless is_enabled
|
726
|
+
catch :error_saved do
|
727
|
+
send meth
|
728
|
+
end
|
729
|
+
}
|
730
|
+
}
|
731
|
+
end
|
732
|
+
|
733
|
+
def create new_data
|
734
|
+
@new_data = new_data
|
735
|
+
run :create
|
736
|
+
self
|
737
|
+
end
|
738
|
+
|
739
|
+
def update new_data
|
740
|
+
@new_data = new_data
|
741
|
+
run :update
|
742
|
+
self
|
743
|
+
end
|
744
|
+
|
745
|
+
end # === module Datoki ===
|
746
|
+
|
747
|
+
|
748
|
+
|