sequel 3.9.0 → 3.10.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.
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