sequel 3.9.0 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/CHANGELOG +56 -0
  2. data/README.rdoc +1 -1
  3. data/Rakefile +1 -1
  4. data/doc/advanced_associations.rdoc +7 -10
  5. data/doc/release_notes/3.10.0.txt +286 -0
  6. data/lib/sequel/adapters/do/mysql.rb +4 -0
  7. data/lib/sequel/adapters/jdbc.rb +5 -0
  8. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  9. data/lib/sequel/adapters/jdbc/oracle.rb +30 -0
  10. data/lib/sequel/adapters/shared/mssql.rb +23 -9
  11. data/lib/sequel/adapters/shared/mysql.rb +12 -1
  12. data/lib/sequel/adapters/shared/postgres.rb +7 -18
  13. data/lib/sequel/adapters/shared/sqlite.rb +5 -0
  14. data/lib/sequel/adapters/sqlite.rb +5 -0
  15. data/lib/sequel/connection_pool/single.rb +3 -3
  16. data/lib/sequel/database.rb +3 -2
  17. data/lib/sequel/dataset.rb +6 -5
  18. data/lib/sequel/dataset/convenience.rb +3 -3
  19. data/lib/sequel/dataset/query.rb +13 -0
  20. data/lib/sequel/dataset/sql.rb +31 -1
  21. data/lib/sequel/extensions/schema_dumper.rb +3 -3
  22. data/lib/sequel/model.rb +8 -6
  23. data/lib/sequel/model/associations.rb +144 -102
  24. data/lib/sequel/model/base.rb +21 -1
  25. data/lib/sequel/model/plugins.rb +3 -1
  26. data/lib/sequel/plugins/association_dependencies.rb +14 -7
  27. data/lib/sequel/plugins/caching.rb +4 -0
  28. data/lib/sequel/plugins/composition.rb +138 -0
  29. data/lib/sequel/plugins/identity_map.rb +2 -2
  30. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  31. data/lib/sequel/plugins/nested_attributes.rb +3 -2
  32. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  33. data/lib/sequel/plugins/typecast_on_load.rb +16 -5
  34. data/lib/sequel/sql.rb +18 -1
  35. data/lib/sequel/version.rb +1 -1
  36. data/spec/adapters/mssql_spec.rb +4 -0
  37. data/spec/adapters/mysql_spec.rb +4 -0
  38. data/spec/adapters/postgres_spec.rb +55 -5
  39. data/spec/core/database_spec.rb +5 -3
  40. data/spec/core/dataset_spec.rb +86 -15
  41. data/spec/core/expression_filters_spec.rb +23 -6
  42. data/spec/extensions/association_dependencies_spec.rb +24 -5
  43. data/spec/extensions/association_proxies_spec.rb +3 -0
  44. data/spec/extensions/composition_spec.rb +194 -0
  45. data/spec/extensions/identity_map_spec.rb +16 -0
  46. data/spec/extensions/nested_attributes_spec.rb +44 -1
  47. data/spec/extensions/rcte_tree_spec.rb +205 -0
  48. data/spec/extensions/schema_dumper_spec.rb +6 -0
  49. data/spec/extensions/spec_helper.rb +6 -0
  50. data/spec/extensions/typecast_on_load_spec.rb +9 -0
  51. data/spec/extensions/validation_helpers_spec.rb +5 -5
  52. data/spec/integration/dataset_test.rb +13 -9
  53. data/spec/integration/eager_loader_test.rb +56 -1
  54. data/spec/integration/model_test.rb +8 -0
  55. data/spec/integration/plugin_test.rb +270 -0
  56. data/spec/integration/schema_test.rb +1 -1
  57. data/spec/model/associations_spec.rb +541 -118
  58. data/spec/model/eager_loading_spec.rb +24 -3
  59. data/spec/model/record_spec.rb +34 -0
  60. metadata +9 -2
@@ -78,7 +78,7 @@ describe "Database schema parser" do
78
78
  INTEGRATION_DB.schema(:items).first.last[:ruby_default].should == 'blah'
79
79
  end
80
80
 
81
- cspecify "should parse types from the schema properly", [:do, :mysql], [:jdbc, :mysql] do
81
+ cspecify "should parse types from the schema properly", [:jdbc, :mysql] do
82
82
  INTEGRATION_DB.create_table!(:items){Integer :number}
83
83
  INTEGRATION_DB.schema(:items).first.last[:type].should == :integer
84
84
  INTEGRATION_DB.create_table!(:items){Fixnum :number}
@@ -382,6 +382,28 @@ describe Sequel::Model, "many_to_one" do
382
382
  MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.parent_id = 2)']
383
383
  end
384
384
 
385
+ it "should have many_to_one setter deal with a one_to_one reciprocal" do
386
+ @c2.many_to_one :parent, :class => @c2, :key=>:parent_id
387
+ @c2.one_to_one :child, :class => @c2, :key=>:parent_id
388
+
389
+ d = @c2.new(:id => 1)
390
+ e = @c2.new(:id => 2)
391
+ e.associations[:child] = nil
392
+ d.parent = e
393
+ e.child.should == d
394
+ d.parent = nil
395
+ e.child.should == nil
396
+ d.parent = e
397
+ e.child.should == d
398
+
399
+ f = @c2.new(:id => 3)
400
+ d.parent = nil
401
+ e.child.should == nil
402
+ e.associations[:child] = f
403
+ d.parent = e
404
+ e.child.should == d
405
+ end
406
+
385
407
  it "should have the setter remove the object from the previous associated object's reciprocal one_to_many cached association list if it exists" do
386
408
  @c2.many_to_one :parent, :class => @c2
387
409
  @c2.one_to_many :children, :class => @c2, :key=>:parent_id
@@ -459,16 +481,16 @@ describe Sequel::Model, "many_to_one" do
459
481
  p.instance_variable_get(:@x).should == c
460
482
  end
461
483
 
462
- it "should support (before|after)_(add|remove) callbacks" do
484
+ it "should support (before|after)_set callbacks" do
463
485
  h = []
464
- @c2.many_to_one :parent, :class => @c2, :before_add=>[proc{|x,y| h << x.pk; h << -y.pk}, :blah], :after_add=>proc{h << 3}, :before_remove=>:blah, :after_remove=>[:blahr]
486
+ @c2.many_to_one :parent, :class => @c2, :before_set=>[proc{|x,y| h << x.pk; h << (y ? -y.pk : :y)}, :blah], :after_set=>proc{h << 3}
465
487
  @c2.class_eval do
466
488
  @@blah = h
467
489
  def []=(a, v)
468
490
  a == :parent_id ? (@@blah << (v ? 4 : 5)) : super
469
491
  end
470
492
  def blah(x)
471
- @@blah << x.pk
493
+ @@blah << (x ? x.pk : :x)
472
494
  end
473
495
  def blahr(x)
474
496
  @@blah << 6
@@ -480,7 +502,7 @@ describe Sequel::Model, "many_to_one" do
480
502
  p.parent = c
481
503
  h.should == [10, -123, 123, 4, 3]
482
504
  p.parent = nil
483
- h.should == [10, -123, 123, 4, 3, 123, 5, 6]
505
+ h.should == [10, -123, 123, 4, 3, 10, :y, :x, 5, 3]
484
506
  end
485
507
 
486
508
  it "should support after_load association callback" do
@@ -507,19 +529,19 @@ describe Sequel::Model, "many_to_one" do
507
529
  p = @c2.new
508
530
  c = @c2.load(:id=>123)
509
531
  p.raise_on_save_failure = false
510
- @c2.many_to_one :parent, :class => @c2, :before_add=>:ba, :before_remove=>:br
511
- p.should_receive(:ba).once.with(c).and_return(false)
532
+ @c2.many_to_one :parent, :class => @c2, :before_set=>:bs
533
+ p.meta_def(:bs){|x| false}
512
534
  p.should_not_receive(:_parent=)
513
535
  proc{p.parent = c}.should raise_error(Sequel::Error)
536
+
514
537
  p.parent.should == nil
515
538
  p.associations[:parent] = c
516
539
  p.parent.should == c
517
- p.should_receive(:br).once.with(c).and_return(false)
518
540
  proc{p.parent = nil}.should raise_error(Sequel::Error)
519
541
  end
520
542
 
521
543
  it "should raise an error if a callback is not a proc or symbol" do
522
- @c2.many_to_one :parent, :class => @c2, :before_add=>Object.new
544
+ @c2.many_to_one :parent, :class => @c2, :before_set=>Object.new
523
545
  proc{@c2.new.parent = @c2.load(:id=>1)}.should raise_error(Sequel::Error)
524
546
  end
525
547
 
@@ -529,27 +551,507 @@ describe Sequel::Model, "many_to_one" do
529
551
  p = @c2.new
530
552
  p.associations[:parent] = d
531
553
  h = []
532
- @c2.many_to_one :parent, :class => @c2, :before_add=>:ba, :before_remove=>:br, :after_add=>:aa, :after_remove=>:ar
554
+ @c2.many_to_one :parent, :class => @c2, :before_set=>:bs, :after_set=>:as
533
555
  @c2.class_eval do
534
556
  @@blah = h
535
557
  def []=(a, v)
536
558
  a == :parent_id ? (@@blah << 5) : super
537
559
  end
538
- def ba(x)
560
+ def bs(x)
539
561
  @@blah << x.pk
540
562
  end
541
- def br(x)
542
- @@blah << x.pk * -1
543
- end
544
- def aa(x)
563
+ def as(x)
545
564
  @@blah << x.pk * 2
546
565
  end
547
- def ar(x)
548
- @@blah << x.pk * -2
566
+ end
567
+ p.parent = c
568
+ h.should == [123, 5, 246]
569
+ end
570
+ end
571
+
572
+ describe Sequel::Model, "one_to_one" do
573
+ before do
574
+ @c1 = Class.new(Sequel::Model(:attributes)) do
575
+ def _refresh(ds); end
576
+ unrestrict_primary_key
577
+ columns :id, :node_id, :y
578
+ end
579
+
580
+ @c2 = Class.new(Sequel::Model(:nodes)) do
581
+ def _refresh(ds); end
582
+ unrestrict_primary_key
583
+ attr_accessor :xxx
584
+
585
+ def self.name; 'Node'; end
586
+ def self.to_s; 'Node'; end
587
+ columns :id, :x, :parent_id, :par_parent_id, :blah, :node_id
588
+ end
589
+ @dataset = @c2.dataset
590
+
591
+ @c2.dataset.extend(Module.new {
592
+ def empty?; false; end
593
+ def fetch_rows(sql)
594
+ @db << sql
595
+ yield Hash.new
596
+ end
597
+ })
598
+
599
+ @c1.dataset.extend(Module.new {
600
+ def empty?; opts.has_key?(:empty) ? (super; true) : false; end
601
+ def fetch_rows(sql)
602
+ @db << sql
603
+ yield Hash.new
604
+ end
605
+ })
606
+
607
+ @dataset = @c2.dataset
608
+ MODEL_DB.reset
609
+ end
610
+
611
+ it "should have the getter method return a single object if the :one_to_one option is true" do
612
+ @c2.one_to_one :attribute, :class => @c1
613
+ att = @c2.new(:id => 1234).attribute
614
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234) LIMIT 1']
615
+ att.should be_a_kind_of(@c1)
616
+ att.values.should == {}
617
+ end
618
+
619
+ it "should not add a setter method if the :read_only option is true" do
620
+ @c2.one_to_one :attribute, :class => @c1, :read_only=>true
621
+ im = @c2.instance_methods.collect{|x| x.to_s}
622
+ im.should(include('attribute'))
623
+ im.should_not(include('attribute='))
624
+ end
625
+
626
+ it "should add a setter method" do
627
+ @c2.one_to_one :attribute, :class => @c1
628
+ attrib = @c1.new(:id=>3)
629
+ d = @c1.dataset
630
+ @c1.class_eval{remove_method :_refresh}
631
+ def d.fetch_rows(s); yield({:id=>3}) end
632
+ @c2.new(:id => 1234).attribute = attrib
633
+ ['INSERT INTO attributes (node_id, id) VALUES (1234, 3)',
634
+ 'INSERT INTO attributes (id, node_id) VALUES (3, 1234)'].should(include(MODEL_DB.sqls.last))
635
+ MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))'
636
+ MODEL_DB.sqls.length.should == 2
637
+ @c2.new(:id => 1234).attribute.should == attrib
638
+ MODEL_DB.sqls.clear
639
+ attrib = @c1.load(:id=>3)
640
+ @c2.new(:id => 1234).attribute = attrib
641
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))',
642
+ "UPDATE attributes SET node_id = 1234 WHERE (id = 3)"]
643
+ end
644
+
645
+ it "should use a transaction in the setter method" do
646
+ @c2.one_to_one :attribute, :class => @c1
647
+ @c2.use_transactions = true
648
+ MODEL_DB.sqls.clear
649
+ attrib = @c1.load(:id=>3)
650
+ @c2.new(:id => 1234).attribute = attrib
651
+ MODEL_DB.sqls.should == ['BEGIN',
652
+ 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))',
653
+ "UPDATE attributes SET node_id = 1234 WHERE (id = 3)",
654
+ 'COMMIT']
655
+ end
656
+
657
+ it "should have setter method respect association filters" do
658
+ @c2.one_to_one :attribute, :class => @c1, :conditions=>{:a=>1} do |ds|
659
+ ds.filter(:b=>2)
660
+ end
661
+ MODEL_DB.sqls.clear
662
+ attrib = @c1.load(:id=>3)
663
+ @c2.new(:id => 1234).attribute = attrib
664
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (a = 1) AND (b = 2) AND (id != 3))',
665
+ "UPDATE attributes SET node_id = 1234 WHERE (id = 3)"]
666
+ end
667
+
668
+ it "should have the setter method respect the :primary_key option" do
669
+ @c2.one_to_one :attribute, :class => @c1, :primary_key=>:xxx
670
+ attrib = @c1.new(:id=>3)
671
+ d = @c1.dataset
672
+ @c1.class_eval{remove_method :_refresh}
673
+ def d.fetch_rows(s); yield({:id=>3}) end
674
+ @c2.new(:id => 1234, :xxx=>5).attribute = attrib
675
+ ['INSERT INTO attributes (node_id, id) VALUES (5, 3)',
676
+ 'INSERT INTO attributes (id, node_id) VALUES (3, 5)'].should(include(MODEL_DB.sqls.last))
677
+ MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))'
678
+ MODEL_DB.sqls.length.should == 2
679
+ @c2.new(:id => 321, :xxx=>5).attribute.should == attrib
680
+ MODEL_DB.sqls.clear
681
+ attrib = @c1.load(:id=>3)
682
+ @c2.new(:id => 621, :xxx=>5).attribute = attrib
683
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))',
684
+ 'UPDATE attributes SET node_id = 5 WHERE (id = 3)']
685
+ end
686
+
687
+ it "should have the setter method respect composite keys" do
688
+ @c2.one_to_one :attribute, :class => @c1, :key=>[:node_id, :y], :primary_key=>[:id, :x]
689
+ attrib = @c1.load(:id=>3, :y=>6)
690
+ d = @c1.dataset
691
+ def d.fetch_rows(s); yield({:id=>3, :y=>6}) end
692
+ @c2.load(:id => 1234, :x=>5).attribute = attrib
693
+ MODEL_DB.sqls.last.should =~ /UPDATE attributes SET (node_id = 1234|y = 5), (node_id = 1234|y = 5) WHERE \(id = 3\)/
694
+ MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id|y) = NULL, (node_id|y) = NULL WHERE \(\(node_id = 1234\) AND \(y = 5\) AND \(id != 3\)\)/
695
+ end
696
+
697
+ it "should use implicit key if omitted" do
698
+ @c2.one_to_one :parent, :class => @c2
699
+
700
+ d = @c2.new(:id => 234)
701
+ p = d.parent
702
+ p.class.should == @c2
703
+ p.values.should == {}
704
+
705
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.node_id = 234) LIMIT 1"]
706
+ end
707
+
708
+ it "should use implicit class if omitted" do
709
+ class ::ParParent < Sequel::Model
710
+ end
711
+
712
+ @c2.one_to_one :par_parent
713
+
714
+ d = @c2.new(:id => 234)
715
+ p = d.par_parent
716
+ p.class.should == ParParent
717
+
718
+ MODEL_DB.sqls.should == ["SELECT * FROM par_parents WHERE (par_parents.node_id = 234) LIMIT 1"]
719
+ end
720
+
721
+ it "should use class inside module if given as a string" do
722
+ module ::Par
723
+ class Parent < Sequel::Model
724
+ end
725
+ end
726
+
727
+ @c2.one_to_one :par_parent, :class=>"Par::Parent"
728
+
729
+ d = @c2.new(:id => 234)
730
+ p = d.par_parent
731
+ p.class.should == Par::Parent
732
+
733
+ MODEL_DB.sqls.should == ["SELECT * FROM parents WHERE (parents.node_id = 234) LIMIT 1"]
734
+ end
735
+
736
+ it "should use explicit key if given" do
737
+ @c2.one_to_one :parent, :class => @c2, :key => :blah
738
+
739
+ d = @c2.new(:id => 234)
740
+ p = d.parent
741
+ p.class.should == @c2
742
+ p.values.should == {}
743
+
744
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.blah = 234) LIMIT 1"]
745
+ end
746
+
747
+ it "should use :primary_key option if given" do
748
+ @c2.one_to_one :parent, :class => @c2, :key => :pk, :primary_key => :blah
749
+ @c2.new(:id => 1, :blah => 567).parent
750
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.pk = 567) LIMIT 1"]
751
+ end
752
+
753
+ it "should support composite keys" do
754
+ @c2.one_to_one :parent, :class => @c2, :primary_key=>[:id, :parent_id], :key=>[:parent_id, :id]
755
+ @c2.new(:id => 1, :parent_id => 234).parent
756
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.parent_id = 1) AND (nodes.id = 234)) LIMIT 1"]
757
+ end
758
+
759
+ it "should not issue query if not all keys have values" do
760
+ @c2.one_to_one :parent, :class => @c2, :key=>[:id, :parent_id], :primary_key=>[:parent_id, :id]
761
+ @c2.new(:id => 1, :parent_id => nil).parent.should == nil
762
+ MODEL_DB.sqls.should == []
763
+ end
764
+
765
+ it "should raise an Error unless same number of composite keys used" do
766
+ proc{@c2.one_to_one :parent, :class => @c2, :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
767
+ proc{@c2.one_to_one :parent, :class => @c2, :key=>[:id, :parent_id], :primary_key=>:id}.should raise_error(Sequel::Error)
768
+ proc{@c2.one_to_one :parent, :class => @c2, :key=>:id, :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
769
+ proc{@c2.one_to_one :parent, :class => @c2, :key=>[:id, :parent_id, :blah], :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
770
+ end
771
+
772
+ it "should use :select option if given" do
773
+ @c2.one_to_one :parent, :class => @c2, :select=>[:id, :name]
774
+ @c2.new(:id => 567).parent
775
+ MODEL_DB.sqls.should == ["SELECT id, name FROM nodes WHERE (nodes.node_id = 567) LIMIT 1"]
776
+ end
777
+
778
+ it "should use :conditions option if given" do
779
+ @c2.one_to_one :parent, :class => @c2, :conditions=>{:a=>32}
780
+ @c2.new(:id => 567).parent
781
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.node_id = 567) AND (a = 32)) LIMIT 1"]
782
+
783
+ @c2.one_to_one :parent, :class => @c2, :conditions=>:a
784
+ MODEL_DB.sqls.clear
785
+ @c2.new(:id => 567).parent
786
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.node_id = 567) AND a) LIMIT 1"]
787
+ end
788
+
789
+ it "should support :order, :limit (only for offset), and :dataset options, as well as a block" do
790
+ c2 = @c2
791
+ @c2.one_to_one :child_20, :class => @c2, :key=>:id, :dataset=>proc{c2.filter(:parent_id=>pk)}, :limit=>[10,20], :order=>:name do |ds|
792
+ ds.filter(:x.sql_number > 1)
793
+ end
794
+ @c2.load(:id => 100).child_20
795
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((parent_id = 100) AND (x > 1)) ORDER BY name LIMIT 1 OFFSET 20"]
796
+ end
797
+
798
+ it "should return nil if primary_key value is nil" do
799
+ @c2.one_to_one :parent, :class => @c2, :primary_key=>:node_id
800
+
801
+ d = @c2.new(:id => 1)
802
+ d.parent.should == nil
803
+ MODEL_DB.sqls.should == []
804
+ end
805
+
806
+ it "should cache negative lookup" do
807
+ @c2.one_to_one :parent, :class => @c2
808
+ ds = @c2.dataset
809
+ def ds.fetch_rows(sql, &block)
810
+ MODEL_DB.sqls << sql
811
+ end
812
+
813
+ d = @c2.new(:id => 555)
814
+ MODEL_DB.sqls.should == []
815
+ d.parent.should == nil
816
+ MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.node_id = 555) LIMIT 1']
817
+ d.parent.should == nil
818
+ MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.node_id = 555) LIMIT 1']
819
+ end
820
+
821
+ it "should define a setter method" do
822
+ @c2.one_to_one :parent, :class => @c2
823
+
824
+ d = @c2.new(:id => 1)
825
+ f = @c2.new(:id => 3, :node_id=> 4321)
826
+ d.parent = f
827
+ f.values.should == {:id => 3, :node_id=>1}
828
+ d.parent.should == f
829
+
830
+ d.parent = nil
831
+ d.parent.should == nil
832
+ end
833
+
834
+ it "should have the setter method respect the :primary_key option" do
835
+ @c2.one_to_one :parent, :class => @c2, :primary_key=>:blah
836
+ d = @c2.new(:id => 1, :blah => 3)
837
+ e = @c2.new(:id => 4321, :node_id=>444)
838
+ d.parent = e
839
+ e.values.should == {:id => 4321, :node_id => 3}
840
+ end
841
+
842
+ it "should have the setter method respect the :key option" do
843
+ @c2.one_to_one :parent, :class => @c2, :key=>:blah
844
+ d = @c2.new(:id => 3)
845
+ e = @c2.new(:id => 4321, :blah=>444)
846
+ d.parent = e
847
+ e.values.should == {:id => 4321, :blah => 3}
848
+ end
849
+
850
+ it "should persist changes to associated object when the setter is called" do
851
+ @c2.one_to_one :parent, :class => @c2
852
+ d = @c2.load(:id => 1)
853
+ d.parent = @c2.load(:id => 3, :node_id=>345)
854
+ MODEL_DB.sqls.should == ["UPDATE nodes SET node_id = NULL WHERE ((node_id = 1) AND (id != 3))",
855
+ "UPDATE nodes SET node_id = 1 WHERE (id = 3)"]
856
+ end
857
+
858
+ it "should set cached instance variable when accessed" do
859
+ @c2.one_to_one :parent, :class => @c2
860
+
861
+ d = @c2.load(:id => 1)
862
+ d.associations[:parent].should == nil
863
+ ds = @c2.dataset
864
+ def ds.fetch_rows(sql, &block); MODEL_DB.sqls << sql; yield({:id=>234}) end
865
+ e = d.parent
866
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.node_id = 1) LIMIT 1"]
867
+ d.parent
868
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.node_id = 1) LIMIT 1"]
869
+ d.associations[:parent].should == e
870
+ end
871
+
872
+ it "should set cached instance variable when assigned" do
873
+ @c2.one_to_one :parent, :class => @c2
874
+
875
+ d = @c2.load(:id => 1)
876
+ d.associations[:parent].should == nil
877
+ e = @c2.load(:id => 234)
878
+ d.parent = e
879
+ f = d.parent
880
+ d.associations[:parent].should == e
881
+ e.should == f
882
+ end
883
+
884
+ it "should use cached instance variable if available" do
885
+ @c2.one_to_one :parent, :class => @c2
886
+ d = @c2.load(:id => 1, :parent_id => 234)
887
+ d.associations[:parent] = 42
888
+ d.parent.should == 42
889
+ MODEL_DB.sqls.should == []
890
+ end
891
+
892
+ it "should not use cached instance variable if asked to reload" do
893
+ @c2.one_to_one :parent, :class => @c2
894
+ d = @c2.load(:id => 1)
895
+ d.associations[:parent] = [42]
896
+ d.parent(true).should_not == 42
897
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.node_id = 1) LIMIT 1"]
898
+ end
899
+
900
+ it "should have the setter set the reciprocal many_to_one cached association" do
901
+ @c2.one_to_one :parent, :class => @c2, :key=>:parent_id
902
+ @c2.many_to_one :child, :class => @c2, :key=>:parent_id
903
+
904
+ d = @c2.load(:id => 1)
905
+ e = @c2.load(:id => 2)
906
+ d.parent = e
907
+ e.child.should == d
908
+ MODEL_DB.sqls.should == ["UPDATE nodes SET parent_id = NULL WHERE ((parent_id = 1) AND (id != 2))",
909
+ "UPDATE nodes SET parent_id = 1 WHERE (id = 2)"]
910
+ MODEL_DB.reset
911
+ d.parent = nil
912
+ e.child.should == nil
913
+ MODEL_DB.sqls.should == ["UPDATE nodes SET parent_id = NULL WHERE (parent_id = 1)"]
914
+ end
915
+
916
+ it "should have the setter remove the object from the previous associated object's reciprocal many_to_one cached association list if it exists" do
917
+ @c2.one_to_one :parent, :class => @c2, :key=>:parent_id
918
+ @c2.many_to_one :child, :class => @c2, :key=>:parent_id
919
+ ds = @c2.dataset
920
+ def ds.fetch_rows(sql, &block)
921
+ MODEL_DB.sqls << sql
922
+ end
923
+
924
+ d = @c2.load(:id => 1)
925
+ e = @c2.load(:id => 2)
926
+ f = @c2.load(:id => 3)
927
+ e.child.should == nil
928
+ f.child.should == nil
929
+ MODEL_DB.reset
930
+ d.parent = e
931
+ e.child.should == d
932
+ d.parent = f
933
+ f.child.should == d
934
+ e.child.should == nil
935
+ d.parent = nil
936
+ f.child.should == nil
937
+ end
938
+
939
+ it "should not add associations methods directly to class" do
940
+ @c2.one_to_one :parent, :class => @c2
941
+ @c2.instance_methods.collect{|x| x.to_s}.should(include('parent'))
942
+ @c2.instance_methods.collect{|x| x.to_s}.should(include('parent='))
943
+ @c2.instance_methods(false).collect{|x| x.to_s}.should_not(include('parent'))
944
+ @c2.instance_methods(false).collect{|x| x.to_s}.should_not(include('parent='))
945
+ end
946
+
947
+ it "should raise an error if the current model object that doesn't have a valid primary key" do
948
+ @c2.one_to_one :parent, :class => @c2
949
+ p = @c2.new
950
+ c = @c2.load(:id=>123)
951
+ proc{p.parent = c}.should raise_error(Sequel::Error)
952
+ end
953
+
954
+ it "should make the change to the foreign_key value inside a _association= method" do
955
+ @c2.one_to_one :parent, :class => @c2
956
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_parent="))
957
+ c = @c2.new
958
+ p = @c2.load(:id=>123)
959
+ def p._parent=(x)
960
+ @x = x
961
+ end
962
+ p.should_not_receive(:parent_id=)
963
+ p.parent = c
964
+ p.instance_variable_get(:@x).should == c
965
+ end
966
+
967
+ it "should support (before|after)_set callbacks" do
968
+ h = []
969
+ @c2.one_to_one :parent, :class => @c2, :before_set=>[proc{|x,y| h << x.pk; h << (y ? -y.pk : :y)}, :blah], :after_set=>proc{h << 3}
970
+ @c2.class_eval do
971
+ @@blah = h
972
+ def blah(x)
973
+ @@blah << (x ? x.pk : :x)
974
+ end
975
+ def blahr(x)
976
+ @@blah << 6
977
+ end
978
+ end
979
+ p = @c2.load(:id=>10)
980
+ c = @c2.load(:id=>123)
981
+ h.should == []
982
+ p.parent = c
983
+ h.should == [10, -123, 123, 3]
984
+ p.parent = nil
985
+ h.should == [10, -123, 123, 3, 10, :y, :x, 3]
986
+ end
987
+
988
+ it "should support after_load association callback" do
989
+ h = []
990
+ @c2.one_to_one :parent, :class => @c2, :after_load=>[proc{|x,y| h << [x.pk, y.pk]}, :al]
991
+ @c2.class_eval do
992
+ @@blah = h
993
+ def al(v)
994
+ @@blah << v.pk
995
+ end
996
+ def @dataset.fetch_rows(sql)
997
+ yield({:id=>20})
998
+ end
999
+ end
1000
+ p = @c2.load(:id=>10)
1001
+ parent = p.parent
1002
+ h.should == [[10, 20], 20]
1003
+ parent.pk.should == 20
1004
+ end
1005
+
1006
+ it "should raise error and not call internal add or remove method if before callback returns false, even if raise_on_save_failure is false" do
1007
+ # The reason for this is that assignment in ruby always returns the argument instead of the result
1008
+ # of the method, so we can't return nil to signal that the association callback prevented the modification
1009
+ p = @c2.new
1010
+ c = @c2.load(:id=>123)
1011
+ p.raise_on_save_failure = false
1012
+ @c2.one_to_one :parent, :class => @c2, :before_set=>:bs
1013
+ p.meta_def(:bs){|x| false}
1014
+ p.should_not_receive(:_parent=)
1015
+ proc{p.parent = c}.should raise_error(Sequel::Error)
1016
+
1017
+ p.parent.should == nil
1018
+ p.associations[:parent] = c
1019
+ p.parent.should == c
1020
+ proc{p.parent = nil}.should raise_error(Sequel::Error)
1021
+ end
1022
+
1023
+ it "should raise an error if a callback is not a proc or symbol" do
1024
+ @c2.one_to_one :parent, :class => @c2, :before_set=>Object.new
1025
+ proc{@c2.new.parent = @c2.load(:id=>1)}.should raise_error(Sequel::Error)
1026
+ end
1027
+
1028
+ it "should call the set callbacks" do
1029
+ c = @c2.load(:id=>123)
1030
+ d = @c2.load(:id=>321)
1031
+ p = @c2.load(:id=>32)
1032
+ p.associations[:parent] = [d]
1033
+ h = []
1034
+ @c2.one_to_one :parent, :class => @c2, :before_set=>:bs, :after_set=>:as
1035
+ @c2.class_eval do
1036
+ @@blah = h
1037
+ def []=(a, v)
1038
+ a == :node_id ? (@@blah << 5) : super
1039
+ end
1040
+ def bs(x)
1041
+ @@blah << x.pk
1042
+ end
1043
+ def as(x)
1044
+ @@blah << x.pk * 2
549
1045
  end
550
1046
  end
551
1047
  p.parent = c
552
- h.should == [-321, 123, 5, 246, -642]
1048
+ h.should == [123, 5, 246]
1049
+ end
1050
+
1051
+ it "should work_correctly when used with associate" do
1052
+ @c2.associate :one_to_one, :parent, :class => @c2
1053
+ @c2.load(:id => 567).parent.should == @c2.load({})
1054
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.node_id = 567) LIMIT 1"]
553
1055
  end
554
1056
  end
555
1057
 
@@ -791,7 +1293,7 @@ describe Sequel::Model, "one_to_many" do
791
1293
  MODEL_DB.reset
792
1294
  @c1.load(:node_id => nil, :y => 5, :id => 234).should == n.remove_attribute([234, 5])
793
1295
  MODEL_DB.sqls.length.should == 2
794
- MODEL_DB.sqls.first.should =~ /SELECT \* FROM attributes WHERE \(\(attributes.node_id = 123\) AND \(\((id|y) = (234|5)\) AND \((id|y) = (234|5)\)\)\) LIMIT 1/
1296
+ MODEL_DB.sqls.first.should =~ /SELECT \* FROM attributes WHERE \(\(attributes.node_id = 123\) AND \((id|y) = (234|5)\) AND \((id|y) = (234|5)\)\) LIMIT 1/
795
1297
  MODEL_DB.sqls.last.should =~ /UPDATE attributes SET node_id = NULL WHERE \(\((id|y) = (234|5)\) AND \((id|y) = (234|5)\)\)/
796
1298
  end
797
1299
 
@@ -1078,6 +1580,14 @@ describe Sequel::Model, "one_to_many" do
1078
1580
  MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE (node_id = 1234)'
1079
1581
  end
1080
1582
 
1583
+ it "should have remove_all method respect association filters" do
1584
+ @c2.one_to_many :attributes, :class => @c1, :conditions=>{:a=>1} do |ds|
1585
+ ds.filter(:b=>2)
1586
+ end
1587
+ @c2.new(:id => 1234).remove_all_attributes
1588
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (a = 1) AND (b = 2))']
1589
+ end
1590
+
1081
1591
  it "should have the remove_all_ method respect the :primary_key option" do
1082
1592
  @c2.one_to_many :attributes, :class => @c1, :primary_key=>:xxx
1083
1593
  @c2.new(:id => 1234, :xxx=>5).remove_all_attributes
@@ -1133,106 +1643,6 @@ describe Sequel::Model, "one_to_many" do
1133
1643
  attrib.associations.fetch(:node, 2).should == nil
1134
1644
  end
1135
1645
 
1136
- it "should add a getter method if the :one_to_one option is true" do
1137
- @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
1138
- att = @c2.new(:id => 1234).attribute
1139
- MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
1140
- att.should be_a_kind_of(@c1)
1141
- att.values.should == {}
1142
- end
1143
-
1144
- it "should not add a setter method if the :one_to_one option is true and :read_only option is true" do
1145
- @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true, :read_only=>true
1146
- im = @c2.instance_methods.collect{|x| x.to_s}
1147
- im.should(include('attribute'))
1148
- im.should_not(include('attribute='))
1149
- end
1150
-
1151
- it "should have the getter method raise an error if more than one record is found" do
1152
- @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
1153
- d = @c1.dataset
1154
- def d.fetch_rows(s); 2.times{yield Hash.new} end
1155
- proc{@c2.new(:id => 1234).attribute}.should raise_error(Sequel::Error)
1156
- end
1157
-
1158
- it "should add a setter method if the :one_to_one option is true" do
1159
- @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
1160
- attrib = @c1.new(:id=>3)
1161
- d = @c1.dataset
1162
- @c1.class_eval{remove_method :_refresh}
1163
- def d.fetch_rows(s); yield({:id=>3}) end
1164
- @c2.new(:id => 1234).attribute = attrib
1165
- ['INSERT INTO attributes (node_id, id) VALUES (1234, 3)',
1166
- 'INSERT INTO attributes (id, node_id) VALUES (3, 1234)'].should(include(MODEL_DB.sqls.first))
1167
- MODEL_DB.sqls.last.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))'
1168
- MODEL_DB.sqls.length.should == 2
1169
- @c2.new(:id => 1234).attribute.should == attrib
1170
- MODEL_DB.sqls.clear
1171
- attrib = @c1.load(:id=>3)
1172
- @c2.new(:id => 1234).attribute = attrib
1173
- MODEL_DB.sqls.should == ["UPDATE attributes SET node_id = 1234 WHERE (id = 3)",
1174
- 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))']
1175
- end
1176
-
1177
- it "should use a transaction in the setter method if the :one_to_one option is true" do
1178
- @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
1179
- @c2.use_transactions = true
1180
- MODEL_DB.sqls.clear
1181
- attrib = @c1.load(:id=>3)
1182
- @c2.new(:id => 1234).attribute = attrib
1183
- MODEL_DB.sqls.should == ['BEGIN',
1184
- "UPDATE attributes SET node_id = 1234 WHERE (id = 3)",
1185
- 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))',
1186
- 'COMMIT']
1187
- end
1188
-
1189
- it "should have the setter method for the :one_to_one option respect the :primary_key option" do
1190
- @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true, :primary_key=>:xxx
1191
- attrib = @c1.new(:id=>3)
1192
- d = @c1.dataset
1193
- @c1.class_eval{remove_method :_refresh}
1194
- def d.fetch_rows(s); yield({:id=>3}) end
1195
- @c2.new(:id => 1234, :xxx=>5).attribute = attrib
1196
- ['INSERT INTO attributes (node_id, id) VALUES (5, 3)',
1197
- 'INSERT INTO attributes (id, node_id) VALUES (3, 5)'].should(include(MODEL_DB.sqls.first))
1198
- MODEL_DB.sqls.last.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))'
1199
- MODEL_DB.sqls.length.should == 2
1200
- @c2.new(:id => 321, :xxx=>5).attribute.should == attrib
1201
- MODEL_DB.sqls.clear
1202
- attrib = @c1.load(:id=>3)
1203
- @c2.new(:id => 621, :xxx=>5).attribute = attrib
1204
- MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 5 WHERE (id = 3)',
1205
- 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))']
1206
- end
1207
-
1208
- it "should have the setter method for the :one_to_one option respect composite keys" do
1209
- @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true, :key=>[:node_id, :y], :primary_key=>[:id, :x]
1210
- attrib = @c1.load(:id=>3, :y=>6)
1211
- d = @c1.dataset
1212
- def d.fetch_rows(s); yield({:id=>3, :y=>6}) end
1213
- @c2.load(:id => 1234, :x=>5).attribute = attrib
1214
- MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id = 1234|y = 5), (node_id = 1234|y = 5) WHERE \(id = 3\)/
1215
- MODEL_DB.sqls.last.should =~ /UPDATE attributes SET (node_id|y) = NULL, (node_id|y) = NULL WHERE \(\(\(node_id = 1234\) AND \(y = 5\)\) AND \(id != 3\)\)/
1216
- end
1217
-
1218
- it "should raise an error if the one_to_one getter would be the same as the association name" do
1219
- proc{@c2.one_to_many :song, :class => @c1, :one_to_one=>true}.should raise_error(Sequel::Error)
1220
- end
1221
-
1222
- it "should not create remove_ and remove_all methods if :one_to_one option is used" do
1223
- @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
1224
- @c2.new.should_not(respond_to(:remove_attribute))
1225
- @c2.new.should_not(respond_to(:remove_all_attributes))
1226
- end
1227
-
1228
- it "should make non getter and setter methods private if :one_to_one option is used" do
1229
- @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true do |ds| end
1230
- meths = @c2.private_instance_methods.collect{|x| x.to_s}
1231
- meths.should(include("attributes"))
1232
- meths.should(include("add_attribute"))
1233
- meths.should(include("attributes_dataset"))
1234
- end
1235
-
1236
1646
  it "should call an _add_ method internally to add attributes" do
1237
1647
  @c2.one_to_many :attributes, :class => @c1
1238
1648
  @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_add_attribute"))
@@ -1390,6 +1800,11 @@ describe Sequel::Model, "one_to_many" do
1390
1800
  p.remove_attribute(c).should == nil
1391
1801
  p.attributes.should == [c]
1392
1802
  end
1803
+
1804
+ it "should raise an error if trying to use the :one_to_one option" do
1805
+ proc{@c2.one_to_many :attribute, :class => @c1, :one_to_one=>true}.should raise_error(Sequel::Error)
1806
+ proc{@c2.associate :one_to_many, :attribute, :class => @c1, :one_to_one=>true}.should raise_error(Sequel::Error)
1807
+ end
1393
1808
  end
1394
1809
 
1395
1810
  describe Sequel::Model, "many_to_many" do
@@ -1918,6 +2333,14 @@ describe Sequel::Model, "many_to_many" do
1918
2333
  MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE (node_id = 1234)'
1919
2334
  end
1920
2335
 
2336
+ it "should have remove_all method respect association filters" do
2337
+ @c2.many_to_many :attributes, :class => @c1, :conditions=>{:a=>1} do |ds|
2338
+ ds.filter(:b=>2)
2339
+ end
2340
+ @c2.new(:id => 1234).remove_all_attributes
2341
+ MODEL_DB.sqls.should == ['DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (a = 1) AND (b = 2))']
2342
+ end
2343
+
1921
2344
  it "should have the remove_all_ method respect the :left_primary_key option" do
1922
2345
  @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx
1923
2346
  @c2.new(:id => 1234, :xxx=>5).remove_all_attributes