datoki 1.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/datoki.rb CHANGED
@@ -15,6 +15,10 @@ module Datoki
15
15
  Numeric_Types = [:smallint, :integer, :bigint, :decimal, :numeric]
16
16
  Types = Char_Types + Numeric_Types + [:datetime]
17
17
 
18
+ Key_Not_Found = lambda { |hash, key|
19
+ fail ArgumentError, "Key not found: #{key.inspect}"
20
+ }
21
+
18
22
  class << self
19
23
 
20
24
  def included klass
@@ -44,16 +48,17 @@ module Datoki
44
48
 
45
49
  module Def_Field
46
50
 
47
- attr_reader :ons, :fields
51
+ attr_reader :ons, :fields, :fields_as_required
48
52
 
49
53
  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
54
+ @on_doc = []
55
+ @ons = {}
56
+ @fields = {} # Ex: {:name=>{}, :age=>{}}
57
+ @fields_as_required = {} # Ex: {:name!=>:name}
58
+ @current_field = nil
59
+ @schema = {}
60
+ @schema_match = false
61
+ @table_name = nil
57
62
  name = self.to_s.downcase.to_sym
58
63
  table(name) if Datoki.db.tables.include?(name)
59
64
  end
@@ -62,14 +67,6 @@ module Datoki
62
67
  @schema_match
63
68
  end
64
69
 
65
- def record_errors?
66
- @record_errors
67
- end
68
-
69
- def record_errors
70
- @record_errors = true
71
- end
72
-
73
70
  def table name
74
71
  if !@schema.empty? || @table_name
75
72
  fail "Schema/table already defined: #{@table_name.inspect}"
@@ -137,6 +134,10 @@ module Datoki
137
134
  inspect_field?(:type, field[:name], *args)
138
135
  end
139
136
 
137
+ def allow sym
138
+ fields[@current_field][:allow][sym] = true;
139
+ end
140
+
140
141
  def field *args
141
142
  return fields[@current_field] if args.empty?
142
143
  return fields[args.first] unless block_given?
@@ -144,6 +145,7 @@ module Datoki
144
145
  name = args.first
145
146
 
146
147
  fail "#{name.inspect} already defined." if fields[name]
148
+ fields_as_required[:"#{name}!"] = name
147
149
 
148
150
  fields[name] = {
149
151
  :name => name,
@@ -271,7 +273,14 @@ module Datoki
271
273
  field[:schema_match] = true
272
274
  end
273
275
 
274
- def on action, meth_name_sym
276
+ attr_reader :on_doc
277
+ def on *args
278
+ return(field_on *args) if !block_given?
279
+ @on_doc << [args, Proc.new]
280
+ self
281
+ end
282
+
283
+ def field_on action, meth_name_sym
275
284
  fail "Invalid action: #{action.inspect}" unless Actions.include? action
276
285
  if field
277
286
  field[:on][action] ||= {}
@@ -304,7 +313,7 @@ module Datoki
304
313
  field[:html_escape] = :href
305
314
  case args.map(&:class)
306
315
  when []
307
- varchar 0, 255
316
+ varchar 1, 255
308
317
  when [NilClass]
309
318
  varchar nil, 1, (schema[field[:name]] ? schema[field[:name]][:max_length] : 255)
310
319
  else
@@ -436,48 +445,296 @@ module Datoki
436
445
  self
437
446
  end
438
447
 
439
- def create h = {}
440
- r = new
441
- r.create h
448
+ def create raw
449
+ raw[:create] = self
450
+ new raw
442
451
  end
443
452
 
444
- end # === Def_Field
453
+ end # === Def_Field =====================================================
445
454
 
446
455
  # ================= Instance Methods ===============
447
456
 
448
- def initialize data = nil
457
+ attr_reader :error
458
+ def initialize unknown = nil
449
459
  @data = nil
450
- @new_data = nil
451
460
  @field_name = nil
452
- @clean_data = nil
453
- @errors = nil
461
+ @clean = nil
462
+ @error = nil
463
+ @skips = {}
464
+
465
+ if unknown
466
+ if unknown.keys.all? { |f| self.class.fields.has_key?(f) }
467
+ @data = unknown
468
+ @data.default_proc = Key_Not_Found
469
+ else
470
+ @raw = unknown
471
+ @raw.default_proc = Key_Not_Found
472
+ end
473
+ end
474
+
475
+ if @raw
476
+ self.class.on_doc.each { |raw_arr|
477
+
478
+ conds = raw_arr.first
479
+ func = raw_arr.last
480
+ instance_eval(&func) if conds.all? { |cond|
481
+ case cond
482
+ when Symbol
483
+ send(cond)
484
+ when Proc
485
+ cond.arity == 1 ? cond.call(@raw) : instance_eval(&cond)
486
+ when TrueClass, FalseClass
487
+ cond
488
+ else
489
+ fail ArgumentError, "Unknown: #{cond.inspect}"
490
+ end
491
+ }
492
+
493
+ } # === on_doc.each
494
+
495
+ if !@clean
496
+ @raw.each { |k, v|
497
+ clean(k) if self.class.fields.has_key?(k)
498
+ }
499
+ end
500
+
501
+ if create?
502
+ self.class.fields.each { |k, meta|
503
+ if !clean.has_key?(k) && !meta[:allow][:null] && !meta[:primary_key]
504
+ fail ArgumentError, "#{k.inspect} is not set."
505
+ end
506
+ }
507
+ end
508
+
509
+ case
510
+ when create?
511
+ insert_into_table unless !respond_to?(:insert_into_table)
512
+ when update?
513
+ alter_record unless !respond_to?(:alter_record)
514
+ when delete?
515
+ delete_from_table unless !respond_to?(:delete_from_table)
516
+ end unless @skips[:db]
517
+ end # === if @raw
454
518
 
455
519
  self.class.schema_match(:all)
456
520
  end
457
521
 
458
- def errors
459
- @errors ||= {}
522
+ def skip name
523
+ @skips[name] = true
460
524
  end
461
525
 
462
- def errors?
463
- @errors && !@errors.empty?
526
+ def error?
527
+ @error && !@error.empty?
464
528
  end
465
529
 
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
530
+ def clean *args
531
+ if args.empty?
532
+ @clean ||= begin
533
+ h = {}
534
+ h.default_proc = Key_Not_Found
535
+ h
536
+ end
537
+ return @clean
538
+ end
472
539
 
473
- def clean_data
474
- @clean_data ||= {}
475
- end
540
+ if args.size > 1
541
+ return args.each { |f| clean f }
542
+ end
543
+
544
+ name = args.first
545
+ required = false
546
+
547
+ if self.class.fields_as_required[name]
548
+ name = self.class.fields_as_required[name]
549
+ required = true
550
+ end
551
+
552
+ field_name(name)
553
+ f_meta = self.class.fields[name]
554
+ required = true if (!field[:allow][:null] && (!@raw.has_key?(name) || @raw[name] == nil))
555
+
556
+ # === Did the programmer forget to set the value?:
557
+ if required && (!@raw.has_key?(name) || @raw[name].nil?)
558
+ fail ArgumentError, "#{name.inspect} is not set."
559
+ end
560
+
561
+ # === Skip this if nothing is set and is null-able:
562
+ if !required && field[:allow][:null] && !@raw.has_key?(name) && !clean.has_key?(name)
563
+ return nil
564
+ end
565
+
566
+ clean[name] = @raw[name] unless clean.has_key?(name)
567
+
568
+ # === Should we let the DB set the value?
569
+ if self.class.schema[name] && self.class.schema[name][:default] && (!clean.has_key?(name) || !clean[name])
570
+ clean.delete name
571
+ return self.class.schema[name][:default]
572
+ end
573
+
574
+ # === Strip the value:
575
+ if clean[name].is_a?(String) && field[:allow][:strip]
576
+ clean[name].strip!
577
+ end
578
+
579
+ if field?(:chars) && !field.has_key?(:min) && clean[name].is_a?(String) && field[:allow][:null]
580
+ clean[name] = nil
581
+ end
582
+
583
+ if field?(:numeric) && clean[name].is_a?(String)
584
+ clean_val = Integer(clean[name]) rescue String
585
+ if clean_val == String
586
+ fail! "!English_name must be numeric."
587
+ else
588
+ clean[name] = clean_val
589
+ end
590
+ end
591
+
592
+ if field?(:text) && clean[name].is_a?(String) && clean[name].empty? && field[:min].to_i > 0
593
+ fail! "!English_name is required."
594
+ end
595
+ # ================================
596
+
597
+ # === check min, max ======
598
+ if clean[name].is_a?(String) || clean[name].is_a?(Numeric)
599
+ case [field[:min], field[:max]].map(&:class)
600
+
601
+ when [NilClass, NilClass]
602
+ # do nothing
603
+
604
+ when [NilClass, Fixnum]
605
+ case
606
+ when clean[name].is_a?(String) && clean[name].size > field[:max]
607
+ fail! "!English_name can't be longer than !max characters."
608
+ when clean[name].is_a?(Numeric) && clean[name] > field[:max]
609
+ fail! "!English_name can't be higher than !max."
610
+ end
611
+
612
+ when [Fixnum, NilClass]
613
+ case
614
+ when clean[name].is_a?(String) && clean[name].size < field[:min]
615
+ fail! "!English_name can't be shorter than !min characters."
616
+ when clean[name].is_a?(Numeric) && clean[name] < field[:min]
617
+ fail! "!English_name can't be less than !min."
618
+ end
619
+
620
+ when [Fixnum, Fixnum]
621
+ case
622
+ when clean[name].is_a?(String) && (clean[name].size < field[:min] || clean[name].size > field[:max])
623
+ fail! "!English_name must be between !min and !max characters."
624
+ when clean[name].is_a?(Numeric) && (clean[name] < field[:min] || clean[name] > field[:max])
625
+ fail! "!English_name must be between !min and !max."
626
+ end
627
+
628
+ else
629
+ fail "Unknown values for :min, :max: #{field[:min].inspect}, #{field[:max].inspect}"
630
+ end
631
+ end # === if
632
+ # ================================
633
+
634
+ # === to_i if necessary ==========
635
+ if field?(:numeric)
636
+ if clean[name].nil? && !field[:allow][:null]
637
+ clean[name] = clean[name].to_i
638
+ end
639
+ end
640
+ # ================================
641
+
642
+ # === :strip if necessary ========
643
+ if field?(:chars) && field[:allow][:strip] && clean[name].is_a?(String)
644
+ clean[name] = clean[name].strip
645
+ end
646
+ # ================================
647
+
648
+ # === Is value in options? =======
649
+ if field[:options]
650
+ if !field[:options].include?(clean[name])
651
+ fail! "!English_name can only be: #{field[:options].map(&:inspect).join ', '}"
652
+ end
653
+ end
654
+ # ================================
655
+
656
+ field[:cleaners].each { |cleaner, args|
657
+ next if args === false # === cleaner has been disabled.
658
+
659
+ case cleaner
660
+
661
+ when :type
662
+ case
663
+ when field?(:numeric) && !clean[name].is_a?(Integer)
664
+ fail! "!English_name needs to be an integer."
665
+ when field?(:chars) && !clean[name].is_a?(String)
666
+ fail! "!English_name needs to be a String."
667
+ end
668
+
669
+ when :exact_size
670
+ if clean[name].size != field[:exact_size]
671
+ case
672
+ when field?(:chars) || clean[name].is_a?(String)
673
+ fail! "!English_name needs to be !exact_size in length."
674
+ else
675
+ fail! "!English_name can only be !exact_size in size."
676
+ end
677
+ end
678
+
679
+ when :set_to
680
+ args.each { |meth|
681
+ clean[name] = send(meth)
682
+ }
683
+
684
+ when :equal_to
685
+ args.each { |pair|
686
+ meth, msg, other = pair
687
+ target = send(meth)
688
+ fail!(msg || "!English_name must be equal to: #{target.inspect}") unless clean[name] == target
689
+ }
690
+
691
+ when :included_in
692
+ arr, msg, other = args
693
+ fail!(msg || "!English_name must be one of these: #{arr.join ', '}") unless arr.include?(clean[name])
694
+
695
+ when :upcase
696
+ clean[name] = clean[name].upcase
697
+
698
+ when :match
699
+ args.each { |pair|
700
+ regex, msg, other = pair
701
+ if clean[name] !~ regex
702
+ fail!(msg || "!English_name must match #{regex.inspect}")
703
+ end
704
+ }
705
+
706
+ when :not_match
707
+ args.each { |pair|
708
+ regex, msg, other = pair
709
+ if clean[name] =~ regex
710
+ fail!(msg || "!English_name must not match #{regex.inspect}")
711
+ end
712
+ }
713
+
714
+ else
715
+ fail "Cleaner not implemented: #{cleaner.inspect}"
716
+ end # === case cleaner
717
+ } # === field[:cleaners].each
718
+ end # === def clean
476
719
 
477
720
  def new_data
478
721
  @new_data ||= {}
479
722
  end
480
723
 
724
+ def on *args
725
+ fail ArgumentError, "No conditions." if args.empty?
726
+ yield if args.all? { |cond|
727
+ case cond
728
+ when Symbol
729
+ send(cond)
730
+ when TrueClass, FalseClass
731
+ cond
732
+ else
733
+ fail ArgumentError, "Unknown value: #{cond.inspect}"
734
+ end
735
+ }
736
+ end
737
+
481
738
  def fail! msg
482
739
  err_msg = msg.gsub(/!([a-z\_\-]+)/i) { |raw|
483
740
  name = $1
@@ -493,12 +750,8 @@ module Datoki
493
750
  end
494
751
  }
495
752
 
496
- if self.class.record_errors?
497
- save_error err_msg
498
- throw :error_saved
499
- else
500
- fail Invalid, err_msg
501
- end
753
+ @error = {:field_name=>field_name, :msg=>err_msg, :value=>clean[field_name]}
754
+ throw :invalid, self
502
755
  end
503
756
 
504
757
  def field_name *args
@@ -507,24 +760,13 @@ module Datoki
507
760
  fail "Field name not set." unless @field_name
508
761
  @field_name
509
762
  when 1
763
+ fail ArgumentError, "Unknown field: #{args.first.inspect}" unless self.class.fields[args.first]
510
764
  @field_name = args.first
511
765
  else
512
766
  fail "Unknown args: #{args.inspect}"
513
767
  end
514
768
  end
515
769
 
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
770
  def field *args
529
771
  case args.size
530
772
  when 0
@@ -540,196 +782,6 @@ module Datoki
540
782
  self.class.inspect_field? :type, field_name, *args
541
783
  end
542
784
 
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
785
  def create new_data
734
786
  @new_data = new_data
735
787
  run :create
@@ -742,6 +794,29 @@ module Datoki
742
794
  self
743
795
  end
744
796
 
797
+ def primary_key
798
+ arr = self.class.fields.detect { |k, v| v[:primary_key] }
799
+ fail "Primary key not found." unless arr
800
+ arr.last
801
+ end
802
+
803
+ def create?
804
+ (@raw.has_key?(:create) && @raw[:create]) ||
805
+ @raw.has_key?(primary_key[:name]) && !@raw[primary_key[:name]]
806
+ end
807
+
808
+ def read?
809
+ !!(@raw.has_key?(:read) && @raw[:read])
810
+ end
811
+
812
+ def update?
813
+ !!(@raw.has_key?(:update) && @raw[:update])
814
+ end
815
+
816
+ def delete?
817
+ !!(@raw.has_key?(:delete) && !@raw[:delete])
818
+ end
819
+
745
820
  end # === module Datoki ===
746
821
 
747
822
 
@@ -0,0 +1,31 @@
1
+
2
+ describe 'Datoki.db :new' do
3
+
4
+ before {
5
+ CACHE[:datoki_db_new] ||= begin
6
+ reset_db <<-EOF
7
+ CREATE TABLE "datoki_test" (
8
+ id serial NOT NULL PRIMARY KEY,
9
+ parent_id smallint NOT NULL,
10
+ title varchar(123) NOT NULL,
11
+ body text
12
+ );
13
+ EOF
14
+ end
15
+ }
16
+
17
+ it "raises Schema_Conflict if field has not been defined, but exists in the db schema" do
18
+ should.raise(Datoki::Schema_Conflict) {
19
+ Class.new {
20
+ include Datoki
21
+ table :datoki_test
22
+ field(:id) { primary_key }
23
+ field(:parent_id) { smallint }
24
+ field(:body) { text nil, 1, 222 }
25
+ }.new
26
+ }.message.should.match /:title has not been defined/
27
+ end
28
+
29
+ end # === describe Datoki.db :new
30
+
31
+