datoki 1.0.1 → 2.0.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.
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
+