sequel 3.21.0 → 3.24.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 (104) hide show
  1. data/CHANGELOG +112 -0
  2. data/README.rdoc +15 -1
  3. data/doc/association_basics.rdoc +159 -40
  4. data/doc/model_hooks.rdoc +64 -27
  5. data/doc/prepared_statements.rdoc +8 -4
  6. data/doc/reflection.rdoc +8 -2
  7. data/doc/release_notes/3.22.0.txt +39 -0
  8. data/doc/release_notes/3.23.0.txt +172 -0
  9. data/doc/release_notes/3.24.0.txt +420 -0
  10. data/doc/virtual_rows.rdoc +2 -2
  11. data/lib/sequel/adapters/ado.rb +2 -1
  12. data/lib/sequel/adapters/db2.rb +8 -1
  13. data/lib/sequel/adapters/firebird.rb +25 -9
  14. data/lib/sequel/adapters/informix.rb +4 -19
  15. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  16. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  17. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  18. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  19. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  20. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  21. data/lib/sequel/adapters/jdbc.rb +39 -20
  22. data/lib/sequel/adapters/odbc.rb +2 -0
  23. data/lib/sequel/adapters/oracle.rb +12 -0
  24. data/lib/sequel/adapters/postgres.rb +30 -1
  25. data/lib/sequel/adapters/shared/access.rb +10 -0
  26. data/lib/sequel/adapters/shared/informix.rb +45 -0
  27. data/lib/sequel/adapters/shared/mssql.rb +106 -11
  28. data/lib/sequel/adapters/shared/mysql.rb +25 -7
  29. data/lib/sequel/adapters/shared/postgres.rb +39 -6
  30. data/lib/sequel/adapters/shared/sqlite.rb +57 -5
  31. data/lib/sequel/adapters/sqlite.rb +8 -3
  32. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  33. data/lib/sequel/adapters/tinytds.rb +4 -3
  34. data/lib/sequel/ast_transformer.rb +190 -0
  35. data/lib/sequel/core.rb +1 -1
  36. data/lib/sequel/database/connecting.rb +1 -1
  37. data/lib/sequel/database/misc.rb +6 -0
  38. data/lib/sequel/database/query.rb +33 -3
  39. data/lib/sequel/database/schema_methods.rb +13 -4
  40. data/lib/sequel/dataset/features.rb +6 -0
  41. data/lib/sequel/dataset/prepared_statements.rb +17 -2
  42. data/lib/sequel/dataset/query.rb +17 -0
  43. data/lib/sequel/dataset/sql.rb +2 -53
  44. data/lib/sequel/exceptions.rb +4 -0
  45. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  46. data/lib/sequel/extensions/migration.rb +4 -3
  47. data/lib/sequel/extensions/to_dot.rb +95 -83
  48. data/lib/sequel/model/associations.rb +234 -32
  49. data/lib/sequel/model/base.rb +187 -60
  50. data/lib/sequel/model/exceptions.rb +3 -1
  51. data/lib/sequel/model.rb +5 -0
  52. data/lib/sequel/plugins/association_pks.rb +6 -4
  53. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  54. data/lib/sequel/plugins/identity_map.rb +2 -2
  55. data/lib/sequel/plugins/many_through_many.rb +33 -3
  56. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  57. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  58. data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
  59. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  60. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  61. data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
  62. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  63. data/lib/sequel/sql.rb +8 -0
  64. data/lib/sequel/version.rb +1 -1
  65. data/spec/adapters/mssql_spec.rb +36 -0
  66. data/spec/adapters/mysql_spec.rb +6 -0
  67. data/spec/adapters/postgres_spec.rb +43 -18
  68. data/spec/adapters/spec_helper.rb +5 -0
  69. data/spec/core/connection_pool_spec.rb +56 -77
  70. data/spec/core/database_spec.rb +33 -0
  71. data/spec/core/dataset_spec.rb +127 -16
  72. data/spec/core/expression_filters_spec.rb +13 -0
  73. data/spec/core/schema_spec.rb +13 -1
  74. data/spec/core/spec_helper.rb +5 -0
  75. data/spec/extensions/association_pks_spec.rb +7 -0
  76. data/spec/extensions/columns_introspection_spec.rb +91 -0
  77. data/spec/extensions/defaults_setter_spec.rb +64 -0
  78. data/spec/extensions/many_through_many_spec.rb +77 -0
  79. data/spec/extensions/migration_spec.rb +17 -17
  80. data/spec/extensions/nested_attributes_spec.rb +1 -0
  81. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  82. data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
  83. data/spec/extensions/prepared_statements_spec.rb +72 -0
  84. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  85. data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
  86. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  87. data/spec/extensions/spec_helper.rb +3 -1
  88. data/spec/extensions/to_dot_spec.rb +3 -5
  89. data/spec/extensions/xml_serializer_spec.rb +12 -0
  90. data/spec/integration/associations_test.rb +212 -0
  91. data/spec/integration/dataset_test.rb +8 -1
  92. data/spec/integration/plugin_test.rb +134 -0
  93. data/spec/integration/prepared_statement_test.rb +72 -1
  94. data/spec/integration/schema_test.rb +66 -8
  95. data/spec/integration/spec_helper.rb +5 -0
  96. data/spec/integration/transaction_test.rb +40 -0
  97. data/spec/integration/type_test.rb +7 -0
  98. data/spec/model/associations_spec.rb +463 -5
  99. data/spec/model/base_spec.rb +59 -0
  100. data/spec/model/eager_loading_spec.rb +269 -1
  101. data/spec/model/hooks_spec.rb +161 -0
  102. data/spec/model/record_spec.rb +30 -0
  103. data/spec/model/spec_helper.rb +5 -0
  104. metadata +29 -4
@@ -163,6 +163,12 @@ describe Sequel::Model, "#eager" do
163
163
  MODEL_DB.sqls.length.should == 2
164
164
  end
165
165
 
166
+ it "should correctly handle a :select=>[] option to many_to_many" do
167
+ EagerAlbum.many_to_many :sgenres, :clone=>:genres, :select=>[]
168
+ a = EagerAlbum.eager(:sgenres).all
169
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT *, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (1)))"]
170
+ end
171
+
166
172
  it "should eagerly load multiple associations in a single call" do
167
173
  a = EagerAlbum.eager(:genres, :tracks, :band).all
168
174
  a.should be_a_kind_of(Array)
@@ -646,6 +652,148 @@ describe Sequel::Model, "#eager" do
646
652
  a.should == EagerAlbum.load(:id => 1, :band_id => 2)
647
653
  a.al_genres.should == [EagerGenre.load(:id=>4)]
648
654
  end
655
+
656
+ it "should eagerly load a many_to_one association with custom eager block" do
657
+ a = EagerAlbum.eager(:band => proc {|ds| ds.select(:id, :name)}).all
658
+ a.should be_a_kind_of(Array)
659
+ a.size.should == 1
660
+ a.first.should be_a_kind_of(EagerAlbum)
661
+ a.first.values.should == {:id => 1, :band_id => 2}
662
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT id, name FROM bands WHERE (bands.id IN (2))']
663
+ a = a.first
664
+ a.band.should be_a_kind_of(EagerBand)
665
+ a.band.values.should == {:id => 2}
666
+ MODEL_DB.sqls.length.should == 2
667
+ end
668
+
669
+ it "should eagerly load a one_to_one association with custom eager block" do
670
+ EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id
671
+ a = EagerAlbum.eager(:track => proc {|ds| ds.select(:id)}).all
672
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
673
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT id FROM tracks WHERE (tracks.album_id IN (1))']
674
+ a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
675
+ MODEL_DB.sqls.length.should == 2
676
+ end
677
+
678
+ it "should eagerly load a one_to_many association with custom eager block" do
679
+ a = EagerAlbum.eager(:tracks => proc {|ds| ds.select(:id)}).all
680
+ a.should be_a_kind_of(Array)
681
+ a.size.should == 1
682
+ a.first.should be_a_kind_of(EagerAlbum)
683
+ a.first.values.should == {:id => 1, :band_id => 2}
684
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT id FROM tracks WHERE (tracks.album_id IN (1))']
685
+ a = a.first
686
+ a.tracks.should be_a_kind_of(Array)
687
+ a.tracks.size.should == 1
688
+ a.tracks.first.should be_a_kind_of(EagerTrack)
689
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
690
+ MODEL_DB.sqls.length.should == 2
691
+ end
692
+
693
+ it "should eagerly load a many_to_many association with custom eager block" do
694
+ a = EagerAlbum.eager(:genres => proc {|ds| ds.select(:name)}).all
695
+ a.should be_a_kind_of(Array)
696
+ a.size.should == 1
697
+ a.first.should be_a_kind_of(EagerAlbum)
698
+ a.first.values.should == {:id => 1, :band_id => 2}
699
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT name, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (1)))"]
700
+ a = a.first
701
+ a.genres.should be_a_kind_of(Array)
702
+ a.genres.size.should == 1
703
+ a.genres.first.should be_a_kind_of(EagerGenre)
704
+ a.genres.first.values.should == {:id => 4}
705
+ MODEL_DB.sqls.length.should == 2
706
+ end
707
+
708
+ it "should allow cascading of eager loading within a custom eager block" do
709
+ a = EagerTrack.eager(:album => proc {|ds| ds.eager(:band => :members)}).all
710
+ a.should be_a_kind_of(Array)
711
+ a.size.should == 1
712
+ a.first.should be_a_kind_of(EagerTrack)
713
+ a.first.values.should == {:id => 3, :album_id => 1}
714
+ MODEL_DB.sqls.length.should == 4
715
+ MODEL_DB.sqls.should == ['SELECT * FROM tracks',
716
+ 'SELECT * FROM albums WHERE (albums.id IN (1))',
717
+ 'SELECT * FROM bands WHERE (bands.id IN (2))',
718
+ "SELECT members.*, bm.band_id AS x_foreign_key_x FROM members INNER JOIN bm ON ((bm.member_id = members.id) AND (bm.band_id IN (2)))"]
719
+ a = a.first
720
+ a.album.should be_a_kind_of(EagerAlbum)
721
+ a.album.values.should == {:id => 1, :band_id => 2}
722
+ a.album.band.should be_a_kind_of(EagerBand)
723
+ a.album.band.values.should == {:id => 2}
724
+ a.album.band.members.should be_a_kind_of(Array)
725
+ a.album.band.members.size.should == 1
726
+ a.album.band.members.first.should be_a_kind_of(EagerBandMember)
727
+ a.album.band.members.first.values.should == {:id => 5}
728
+ MODEL_DB.sqls.length.should == 4
729
+ end
730
+
731
+ it "should allow cascading of eager loading with custom callback with hash value" do
732
+ a = EagerTrack.eager(:album=>{proc{|ds| ds.select(:id, :band_id)}=>{:band => :members}}).all
733
+ a.should be_a_kind_of(Array)
734
+ a.size.should == 1
735
+ a.first.should be_a_kind_of(EagerTrack)
736
+ a.first.values.should == {:id => 3, :album_id => 1}
737
+ MODEL_DB.sqls.length.should == 4
738
+ MODEL_DB.sqls.should == ['SELECT * FROM tracks',
739
+ 'SELECT id, band_id FROM albums WHERE (albums.id IN (1))',
740
+ 'SELECT * FROM bands WHERE (bands.id IN (2))',
741
+ "SELECT members.*, bm.band_id AS x_foreign_key_x FROM members INNER JOIN bm ON ((bm.member_id = members.id) AND (bm.band_id IN (2)))"]
742
+ a = a.first
743
+ a.album.should be_a_kind_of(EagerAlbum)
744
+ a.album.values.should == {:id => 1, :band_id => 2}
745
+ a.album.band.should be_a_kind_of(EagerBand)
746
+ a.album.band.values.should == {:id => 2}
747
+ a.album.band.members.should be_a_kind_of(Array)
748
+ a.album.band.members.size.should == 1
749
+ a.album.band.members.first.should be_a_kind_of(EagerBandMember)
750
+ a.album.band.members.first.values.should == {:id => 5}
751
+ MODEL_DB.sqls.length.should == 4
752
+ end
753
+
754
+ it "should allow cascading of eager loading with custom callback with symbol value" do
755
+ a = EagerTrack.eager(:album=>{proc{|ds| ds.select(:id, :band_id)}=>:band}).all
756
+ a.should be_a_kind_of(Array)
757
+ a.size.should == 1
758
+ a.first.should be_a_kind_of(EagerTrack)
759
+ a.first.values.should == {:id => 3, :album_id => 1}
760
+ MODEL_DB.sqls.length.should == 3
761
+ MODEL_DB.sqls.should == ['SELECT * FROM tracks',
762
+ 'SELECT id, band_id FROM albums WHERE (albums.id IN (1))',
763
+ 'SELECT * FROM bands WHERE (bands.id IN (2))']
764
+ a = a.first
765
+ a.album.should be_a_kind_of(EagerAlbum)
766
+ a.album.values.should == {:id => 1, :band_id => 2}
767
+ a.album.band.should be_a_kind_of(EagerBand)
768
+ a.album.band.values.should == {:id => 2}
769
+ MODEL_DB.sqls.length.should == 3
770
+ end
771
+
772
+ it "should allow cascading of eager loading with custom callback with array value" do
773
+ a = EagerTrack.eager(:album=>{proc{|ds| ds.select(:id, :band_id)}=>[:band, :band_name]}).all
774
+ a.should be_a_kind_of(Array)
775
+ a.size.should == 1
776
+ a.first.should be_a_kind_of(EagerTrack)
777
+ a.first.values.should == {:id => 3, :album_id => 1}
778
+ MODEL_DB.sqls.length.should == 4
779
+ MODEL_DB.sqls[0..1].should == ['SELECT * FROM tracks',
780
+ 'SELECT id, band_id FROM albums WHERE (albums.id IN (1))']
781
+ MODEL_DB.sqls[2..-1].sort.should == ['SELECT * FROM bands WHERE (bands.id IN (2))',
782
+ 'SELECT id, name FROM bands WHERE (bands.id IN (2))']
783
+ a = a.first
784
+ a.album.should be_a_kind_of(EagerAlbum)
785
+ a.album.values.should == {:id => 1, :band_id => 2}
786
+ a.album.band.should be_a_kind_of(EagerBand)
787
+ a.album.band.values.should == {:id => 2}
788
+ a.album.band_name.should be_a_kind_of(EagerBand)
789
+ a.album.band_name.values.should == {:id => 2}
790
+ MODEL_DB.sqls.length.should == 4
791
+ end
792
+
793
+ it "should call both association and custom eager blocks" do
794
+ EagerBand.eager(:good_albums => proc {|ds| ds.select(:name)}).all
795
+ MODEL_DB.sqls.should == ['SELECT * FROM bands', "SELECT name FROM albums WHERE ((albums.band_id IN (2)) AND (name = 'good'))"]
796
+ end
649
797
  end
650
798
 
651
799
  describe Sequel::Model, "#eager_graph" do
@@ -664,6 +812,11 @@ describe Sequel::Model, "#eager_graph" do
664
812
  c.instance_variable_set(:@columns, (@columns.dup if @columns))
665
813
  c
666
814
  end
815
+ def select_columns(*a)
816
+ ds = select(*a)
817
+ ds.instance_variable_set(:@columns, a)
818
+ ds
819
+ end
667
820
  end
668
821
 
669
822
  class ::GraphAlbum < Sequel::Model(:albums)
@@ -1284,13 +1437,17 @@ describe Sequel::Model, "#eager_graph" do
1284
1437
  GraphAlbum.many_to_one :active_band, :class=>'GraphBand', :key=>:band_id, :eager_grapher=>proc{|ds, aa, ta| ds.graph(GraphBand, {:active=>true}, :table_alias=>aa, :join_type=>:inner)}
1285
1438
  GraphAlbum.eager_graph(:active_band).sql.should == "SELECT albums.id, albums.band_id, active_band.id AS active_band_id, active_band.vocalist_id FROM albums INNER JOIN bands AS active_band ON (active_band.active IS TRUE)"
1286
1439
 
1287
- GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :eager_grapher=>proc{|ds, aa, ta| ds.graph(GraphTrack, nil, :join_type=>:natural, :table_alias=>aa)}
1440
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :eager_grapher=>proc{|eo| eo[:self].graph(GraphTrack, nil, :join_type=>:natural, :table_alias=>eo[:table_alias])}
1288
1441
  GraphAlbum.eager_graph(:right_tracks).sql.should == 'SELECT albums.id, albums.band_id, right_tracks.id AS right_tracks_id, right_tracks.album_id FROM albums NATURAL JOIN tracks AS right_tracks'
1289
1442
 
1290
1443
  GraphAlbum.many_to_many :active_genres, :class=>'GraphGenre', :eager_grapher=>proc{|ds, aa, ta| ds.graph(:ag, {:album_id=>:id}, :table_alias=>:a123, :implicit_qualifier=>ta).graph(GraphGenre, [:album_id], :table_alias=>aa)}
1291
1444
  GraphAlbum.eager_graph(:active_genres).sql.should == "SELECT albums.id, albums.band_id, active_genres.id AS active_genres_id FROM albums LEFT OUTER JOIN ag AS a123 ON (a123.album_id = albums.id) LEFT OUTER JOIN genres AS active_genres USING (album_id)"
1292
1445
  end
1293
1446
 
1447
+ it "should raise an error if you use an :eager_grapher proc with the wrong arity" do
1448
+ proc{GraphAlbum.many_to_one :special_band, :eager_grapher=>proc{|a, b|}}.should raise_error(Sequel::Error)
1449
+ end
1450
+
1294
1451
  it "should respect the association's :graph_only_conditions option" do
1295
1452
  GraphAlbum.many_to_one :active_band, :class=>'GraphBand', :key=>:band_id, :graph_only_conditions=>{:active=>true}
1296
1453
  GraphAlbum.eager_graph(:active_band).sql.should == "SELECT albums.id, albums.band_id, active_band.id AS active_band_id, active_band.vocalist_id FROM albums LEFT OUTER JOIN bands AS active_band ON (active_band.active IS TRUE)"
@@ -1367,6 +1524,8 @@ describe Sequel::Model, "#eager_graph" do
1367
1524
  as = ds.all
1368
1525
  as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
1369
1526
  as.first.inner_genres.should == [GraphGenre.load(:id=>5), GraphGenre.load(:id=>6)]
1527
+ GraphAlbum.set_primary_key :id
1528
+ GraphGenre.set_primary_key :id
1370
1529
  end
1371
1530
 
1372
1531
  it "should handle eager loading with schemas and aliases of different types" do
@@ -1392,4 +1551,113 @@ describe Sequel::Model, "#eager_graph" do
1392
1551
  ds = c1.join(:s__t, [:b_id]).eager_graph(:a_genres)
1393
1552
  ds.sql.should == 'SELECT a.id, a_genres.id AS a_genres_id FROM (SELECT * FROM s.a INNER JOIN s.t USING (b_id)) AS a LEFT OUTER JOIN s.ag AS ag ON (ag.album_id = a.id) LEFT OUTER JOIN s.g AS a_genres ON (a_genres.id = ag.genre_id)'
1394
1553
  end
1554
+
1555
+ it "should eagerly load a many_to_one association with a custom callback" do
1556
+ ds = GraphAlbum.eager_graph(:band => proc {|ds| ds.select_columns(:id)})
1557
+ ds.sql.should == 'SELECT albums.id, albums.band_id, band.id AS band_id_0 FROM albums LEFT OUTER JOIN (SELECT id FROM bands) AS band ON (band.id = albums.band_id)'
1558
+ def ds.fetch_rows(sql, &block)
1559
+ yield({:id=>1, :band_id=>2, :band_id_0=>2})
1560
+ end
1561
+ a = ds.all
1562
+ a.should be_a_kind_of(Array)
1563
+ a.size.should == 1
1564
+ a.first.should be_a_kind_of(GraphAlbum)
1565
+ a.first.values.should == {:id => 1, :band_id => 2}
1566
+ a = a.first
1567
+ a.band.should be_a_kind_of(GraphBand)
1568
+ a.band.values.should == {:id => 2}
1569
+ end
1570
+
1571
+ it "should eagerly load a one_to_one association with a custom callback" do
1572
+ GraphAlbum.one_to_one :track, :class=>'GraphTrack', :key=>:album_id
1573
+ ds = GraphAlbum.eager_graph(:track => proc {|ds| ds.select_columns(:album_id)})
1574
+ ds.sql.should == 'SELECT albums.id, albums.band_id, track.album_id FROM albums LEFT OUTER JOIN (SELECT album_id FROM tracks) AS track ON (track.album_id = albums.id)'
1575
+ def ds.fetch_rows(sql, &block)
1576
+ yield({:id=>1, :band_id=>2, :album_id=>1})
1577
+ end
1578
+ a = ds.all
1579
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1580
+ a.first.track.should == GraphTrack.load(:album_id=>1)
1581
+ end
1582
+
1583
+ it "should eagerly load a one_to_many association with a custom callback" do
1584
+ ds = GraphAlbum.eager_graph(:tracks => proc {|ds| ds.select_columns(:album_id)})
1585
+ ds.sql.should == 'SELECT albums.id, albums.band_id, tracks.album_id FROM albums LEFT OUTER JOIN (SELECT album_id FROM tracks) AS tracks ON (tracks.album_id = albums.id)'
1586
+ def ds.fetch_rows(sql, &block)
1587
+ yield({:id=>1, :band_id=>2, :album_id=>1})
1588
+ end
1589
+ a = ds.all
1590
+ a.should be_a_kind_of(Array)
1591
+ a.size.should == 1
1592
+ a.first.should be_a_kind_of(GraphAlbum)
1593
+ a.first.values.should == {:id => 1, :band_id => 2}
1594
+ a = a.first
1595
+ a.tracks.should be_a_kind_of(Array)
1596
+ a.tracks.size.should == 1
1597
+ a.tracks.first.should be_a_kind_of(GraphTrack)
1598
+ a.tracks.first.values.should == {:album_id=>1}
1599
+ end
1600
+
1601
+ it "should eagerly load a many_to_many association with a custom callback" do
1602
+ ds = GraphAlbum.eager_graph(:genres => proc {|ds| ds.select_columns(:id)})
1603
+ ds.sql.should == 'SELECT albums.id, albums.band_id, genres.id AS genres_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN (SELECT id FROM genres) AS genres ON (genres.id = ag.genre_id)'
1604
+ def ds.fetch_rows(sql, &block)
1605
+ yield({:id=>1, :band_id=>2, :genres_id=>4})
1606
+ end
1607
+ a = ds.all
1608
+ a.should be_a_kind_of(Array)
1609
+ a.size.should == 1
1610
+ a.first.should be_a_kind_of(GraphAlbum)
1611
+ a.first.values.should == {:id => 1, :band_id => 2}
1612
+ a = a.first
1613
+ a.genres.should be_a_kind_of(Array)
1614
+ a.genres.size.should == 1
1615
+ a.genres.first.should be_a_kind_of(GraphGenre)
1616
+ a.genres.first.values.should == {:id => 4}
1617
+ end
1618
+
1619
+ it "should allow cascading of eager loading with a custom callback with hash value" do
1620
+ ds = GraphTrack.eager_graph(:album=>{proc{|ds| ds.select_columns(:id, :band_id)}=>{:band=>:members}})
1621
+ ds.sql.should == 'SELECT tracks.id, tracks.album_id, album.id AS album_id_0, album.band_id, band.id AS band_id_0, band.vocalist_id, members.id AS members_id FROM tracks LEFT OUTER JOIN (SELECT id, band_id FROM albums) AS album ON (album.id = tracks.album_id) LEFT OUTER JOIN bands AS band ON (band.id = album.band_id) LEFT OUTER JOIN bm ON (bm.band_id = band.id) LEFT OUTER JOIN members ON (members.id = bm.member_id)'
1622
+ def ds.fetch_rows(sql, &block)
1623
+ yield({:id=>3, :album_id=>1, :album_id_0=>1, :band_id=>2, :members_id=>5, :band_id_0=>2, :vocalist_id=>6})
1624
+ end
1625
+ a = ds.all
1626
+ a.should be_a_kind_of(Array)
1627
+ a.size.should == 1
1628
+ a.first.should be_a_kind_of(GraphTrack)
1629
+ a.first.values.should == {:id => 3, :album_id => 1}
1630
+ a = a.first
1631
+ a.album.should be_a_kind_of(GraphAlbum)
1632
+ a.album.values.should == {:id => 1, :band_id => 2}
1633
+ a.album.band.should be_a_kind_of(GraphBand)
1634
+ a.album.band.values.should == {:id => 2, :vocalist_id=>6}
1635
+ a.album.band.members.should be_a_kind_of(Array)
1636
+ a.album.band.members.size.should == 1
1637
+ a.album.band.members.first.should be_a_kind_of(GraphBandMember)
1638
+ a.album.band.members.first.values.should == {:id => 5}
1639
+ end
1640
+
1641
+ it "should allow cascading of eager loading with a custom callback with array value" do
1642
+ ds = GraphTrack.eager_graph(:album=>{proc{|ds| ds.select_columns(:id, :band_id)}=>[:band, :tracks]})
1643
+ ds.sql.should == 'SELECT tracks.id, tracks.album_id, album.id AS album_id_0, album.band_id, band.id AS band_id_0, band.vocalist_id, tracks_0.id AS tracks_0_id, tracks_0.album_id AS tracks_0_album_id FROM tracks LEFT OUTER JOIN (SELECT id, band_id FROM albums) AS album ON (album.id = tracks.album_id) LEFT OUTER JOIN bands AS band ON (band.id = album.band_id) LEFT OUTER JOIN tracks AS tracks_0 ON (tracks_0.album_id = album.id)'
1644
+ def ds.fetch_rows(sql, &block)
1645
+ yield({:id=>3, :album_id=>1, :album_id_0=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>6, :tracks_0_id=>3, :tracks_0_album_id=>1})
1646
+ end
1647
+ a = ds.all
1648
+ a.should be_a_kind_of(Array)
1649
+ a.size.should == 1
1650
+ a.first.should be_a_kind_of(GraphTrack)
1651
+ a.first.values.should == {:id => 3, :album_id => 1}
1652
+ a = a.first
1653
+ a.album.should be_a_kind_of(GraphAlbum)
1654
+ a.album.values.should == {:id => 1, :band_id => 2}
1655
+ a.album.band.should be_a_kind_of(GraphBand)
1656
+ a.album.band.values.should == {:id => 2, :vocalist_id=>6}
1657
+ a.album.tracks.should be_a_kind_of(Array)
1658
+ a.album.tracks.size.should == 1
1659
+ a.album.tracks.first.should be_a_kind_of(GraphTrack)
1660
+ a.album.tracks.first.values.should == {:id => 3, :album_id => 1}
1661
+ end
1662
+
1395
1663
  end
@@ -268,4 +268,165 @@ describe "Model#before_validation && Model#after_validation" do
268
268
  @c.load(:id => 2233).save.should == nil
269
269
  MODEL_DB.sqls.should == []
270
270
  end
271
+
272
+ specify "#valid? should return false if before_validation returns false" do
273
+ @c.send(:define_method, :before_validation){false}
274
+ @c.load(:id => 2233).valid?.should == false
275
+ end
276
+ end
277
+
278
+ describe "Model around filters" do
279
+ before do
280
+ MODEL_DB.reset
281
+
282
+ @c = Class.new(Sequel::Model(:items))
283
+ @c.class_eval do
284
+ columns :id, :x
285
+ def _save_refresh(*a) end
286
+ end
287
+ end
288
+
289
+ specify "around_create should be called around new record creation" do
290
+ @c.class_eval do
291
+ def around_create
292
+ MODEL_DB << 'ac_before'
293
+ super
294
+ MODEL_DB << 'ac_after'
295
+ end
296
+ end
297
+ @c.create(:x => 2)
298
+ MODEL_DB.sqls.should == [ 'ac_before', 'INSERT INTO items (x) VALUES (2)', 'ac_after' ]
299
+ end
300
+
301
+ specify "around_delete should be called around record destruction" do
302
+ @c.class_eval do
303
+ def around_destroy
304
+ MODEL_DB << 'ad_before'
305
+ super
306
+ MODEL_DB << 'ad_after'
307
+ end
308
+ end
309
+ @c.load(:id=>1, :x => 2).destroy
310
+ MODEL_DB.sqls.should == [ 'ad_before', 'DELETE FROM items WHERE (id = 1)', 'ad_after' ]
311
+ end
312
+
313
+ specify "around_update should be called around updating existing records" do
314
+ @c.class_eval do
315
+ def around_update
316
+ MODEL_DB << 'au_before'
317
+ super
318
+ MODEL_DB << 'au_after'
319
+ end
320
+ end
321
+ @c.load(:id=>1, :x => 2).save
322
+ MODEL_DB.sqls.should == [ 'au_before', 'UPDATE items SET x = 2 WHERE (id = 1)', 'au_after' ]
323
+ end
324
+
325
+ specify "around_update should be called around saving both new and existing records, around either after_create and after_update" do
326
+ @c.class_eval do
327
+ def around_update
328
+ MODEL_DB << 'au_before'
329
+ super
330
+ MODEL_DB << 'au_after'
331
+ end
332
+ def around_create
333
+ MODEL_DB << 'ac_before'
334
+ super
335
+ MODEL_DB << 'ac_after'
336
+ end
337
+ def around_save
338
+ MODEL_DB << 'as_before'
339
+ super
340
+ MODEL_DB << 'as_after'
341
+ end
342
+ end
343
+ @c.create(:x => 2)
344
+ MODEL_DB.sqls.should == [ 'as_before', 'ac_before', 'INSERT INTO items (x) VALUES (2)', 'ac_after', 'as_after' ]
345
+ MODEL_DB.sqls.clear
346
+ @c.load(:id=>1, :x => 2).save
347
+ MODEL_DB.sqls.should == [ 'as_before', 'au_before', 'UPDATE items SET x = 2 WHERE (id = 1)', 'au_after', 'as_after' ]
348
+ end
349
+
350
+ specify "around_validation should be called around validating records" do
351
+ @c.class_eval do
352
+ def around_validation
353
+ MODEL_DB << 'av_before'
354
+ super
355
+ MODEL_DB << 'av_after'
356
+ end
357
+ def validate
358
+ MODEL_DB << 'validate'
359
+ end
360
+ end
361
+ @c.new(:x => 2).valid?.should == true
362
+ MODEL_DB.sqls.should == [ 'av_before', 'validate', 'av_after' ]
363
+ end
364
+
365
+ specify "around_validation should be able to catch validation errors and modify them" do
366
+ @c.class_eval do
367
+ def validate
368
+ errors.add(:x, 'foo')
369
+ end
370
+ end
371
+ @c.new(:x => 2).valid?.should == false
372
+ @c.class_eval do
373
+ def around_validation
374
+ super
375
+ errors.clear
376
+ end
377
+ end
378
+ @c.new(:x => 2).valid?.should == true
379
+ end
380
+
381
+ specify "around_create that doesn't call super should raise a HookFailed" do
382
+ @c.send(:define_method, :around_create){}
383
+ proc{@c.create(:x => 2)}.should raise_error(Sequel::HookFailed)
384
+ end
385
+
386
+ specify "around_update that doesn't call super should raise a HookFailed" do
387
+ @c.send(:define_method, :around_update){}
388
+ proc{@c.load(:x => 2).save}.should raise_error(Sequel::HookFailed)
389
+ end
390
+
391
+ specify "around_save that doesn't call super should raise a HookFailed" do
392
+ @c.send(:define_method, :around_save){}
393
+ proc{@c.create(:x => 2)}.should raise_error(Sequel::HookFailed)
394
+ proc{@c.load(:x => 2).save}.should raise_error(Sequel::HookFailed)
395
+ end
396
+
397
+ specify "around_destroy that doesn't call super should raise a HookFailed" do
398
+ @c.send(:define_method, :around_destroy){}
399
+ proc{@c.load(:x => 2).destroy}.should raise_error(Sequel::HookFailed)
400
+ end
401
+
402
+ specify "around_validation that doesn't call super should raise a HookFailed" do
403
+ @c.send(:define_method, :around_validation){}
404
+ proc{@c.new.save}.should raise_error(Sequel::HookFailed)
405
+ end
406
+
407
+ specify "around_validation that doesn't call super should have valid? return false" do
408
+ @c.send(:define_method, :around_validation){}
409
+ @c.new.valid?.should == false
410
+ end
411
+
412
+ specify "around_* that doesn't call super should return nil if raise_on_save_failure is false" do
413
+ @c.raise_on_save_failure = false
414
+
415
+ o = @c.load(:id => 1)
416
+ o.meta_def(:around_save){}
417
+ o.save.should == nil
418
+
419
+ o = @c.load(:id => 1)
420
+ o.meta_def(:around_update){}
421
+ o.save.should == nil
422
+
423
+ o = @c.new
424
+ o.meta_def(:around_create){}
425
+ o.save.should == nil
426
+
427
+ o = @c.new
428
+ o.meta_def(:around_validation){}
429
+ o.save.should == nil
430
+ end
431
+
271
432
  end
@@ -52,6 +52,7 @@ describe "Model#save" do
52
52
 
53
53
  it "should use dataset's insert_select method if present" do
54
54
  ds = @c.dataset = @c.dataset.clone
55
+ def ds.supports_insert_select?() true end
55
56
  def ds.insert_select(hash)
56
57
  execute("INSERT INTO items (y) VALUES (2)")
57
58
  {:y=>2}
@@ -63,6 +64,12 @@ describe "Model#save" do
63
64
  MODEL_DB.sqls.should == ["INSERT INTO items (y) VALUES (2)"]
64
65
  end
65
66
 
67
+ it "should not use dataset's insert_select method if specific columns are selected" do
68
+ ds = @c.dataset = @c.dataset.select(:y)
69
+ ds.should_not_receive(:insert_select)
70
+ @c.new(:x => 1).save
71
+ end
72
+
66
73
  it "should use value returned by insert as the primary key and refresh the object" do
67
74
  @c.dataset.meta_def(:insert){|h| super(h); 13}
68
75
  o = @c.new(:x => 11)
@@ -1161,6 +1168,29 @@ describe Sequel::Model, "#initialize" do
1161
1168
  m.values.should == {:x => 2}
1162
1169
  end
1163
1170
  end
1171
+
1172
+ describe Sequel::Model, "#initialize_set" do
1173
+ before do
1174
+ @c = Class.new(Sequel::Model){columns :id, :x, :y}
1175
+ end
1176
+
1177
+ specify "should be called by initialize to set the column values" do
1178
+ @c.send(:define_method, :initialize_set){|h| set(:y => 3)}
1179
+ @c.new(:x => 2).values.should == {:y => 3}
1180
+ end
1181
+
1182
+ specify "should be called with the hash given to initialize " do
1183
+ x = nil
1184
+ @c.send(:define_method, :initialize_set){|y| x = y}
1185
+ @c.new(:x => 2)
1186
+ x.should == {:x => 2}
1187
+ end
1188
+
1189
+ specify "should not cause columns modified by the method to be considered as changed" do
1190
+ @c.send(:define_method, :initialize_set){|h| set(:y => 3)}
1191
+ @c.new(:x => 2).changed_columns.should == []
1192
+ end
1193
+ end
1164
1194
 
1165
1195
  describe Sequel::Model, ".create" do
1166
1196
 
@@ -8,6 +8,11 @@ unless Sequel.const_defined?('Model')
8
8
  require 'sequel/model'
9
9
  end
10
10
 
11
+ if ENV['SEQUEL_COLUMNS_INTROSPECTION']
12
+ Sequel.extension :columns_introspection
13
+ Sequel::Dataset.introspect_all_columns
14
+ end
15
+
11
16
  class MockDataset < Sequel::Dataset
12
17
  def insert(*args)
13
18
  @db.execute insert_sql(*args)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- hash: 83
4
+ hash: 103
5
5
  prerelease: false
6
6
  segments:
7
7
  - 3
8
- - 21
8
+ - 24
9
9
  - 0
10
- version: 3.21.0
10
+ version: 3.24.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jeremy Evans
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-03-01 00:00:00 -08:00
18
+ date: 2011-06-01 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -85,6 +85,9 @@ extra_rdoc_files:
85
85
  - doc/release_notes/3.19.0.txt
86
86
  - doc/release_notes/3.20.0.txt
87
87
  - doc/release_notes/3.21.0.txt
88
+ - doc/release_notes/3.22.0.txt
89
+ - doc/release_notes/3.23.0.txt
90
+ - doc/release_notes/3.24.0.txt
88
91
  files:
89
92
  - MIT-LICENSE
90
93
  - CHANGELOG
@@ -137,6 +140,9 @@ files:
137
140
  - doc/release_notes/3.19.0.txt
138
141
  - doc/release_notes/3.20.0.txt
139
142
  - doc/release_notes/3.21.0.txt
143
+ - doc/release_notes/3.22.0.txt
144
+ - doc/release_notes/3.23.0.txt
145
+ - doc/release_notes/3.24.0.txt
140
146
  - doc/sharding.rdoc
141
147
  - doc/sql.rdoc
142
148
  - doc/virtual_rows.rdoc
@@ -216,6 +222,13 @@ files:
216
222
  - spec/extensions/xml_serializer_spec.rb
217
223
  - spec/extensions/to_dot_spec.rb
218
224
  - spec/extensions/association_autoreloading_spec.rb
225
+ - spec/extensions/serialization_modification_detection_spec.rb
226
+ - spec/extensions/defaults_setter_spec.rb
227
+ - spec/extensions/prepared_statements_spec.rb
228
+ - spec/extensions/prepared_statements_safe_spec.rb
229
+ - spec/extensions/prepared_statements_with_pk_spec.rb
230
+ - spec/extensions/prepared_statements_associations_spec.rb
231
+ - spec/extensions/columns_introspection_spec.rb
219
232
  - spec/integration/associations_test.rb
220
233
  - spec/integration/database_test.rb
221
234
  - spec/integration/dataset_test.rb
@@ -304,6 +317,9 @@ files:
304
317
  - lib/sequel/adapters/jdbc/postgresql.rb
305
318
  - lib/sequel/adapters/jdbc/sqlite.rb
306
319
  - lib/sequel/adapters/jdbc/as400.rb
320
+ - lib/sequel/adapters/jdbc/informix.rb
321
+ - lib/sequel/adapters/jdbc/jtds.rb
322
+ - lib/sequel/adapters/jdbc/sqlserver.rb
307
323
  - lib/sequel/adapters/mysql.rb
308
324
  - lib/sequel/adapters/odbc.rb
309
325
  - lib/sequel/adapters/odbc/mssql.rb
@@ -317,6 +333,7 @@ files:
317
333
  - lib/sequel/adapters/shared/progress.rb
318
334
  - lib/sequel/adapters/shared/sqlite.rb
319
335
  - lib/sequel/adapters/shared/access.rb
336
+ - lib/sequel/adapters/shared/informix.rb
320
337
  - lib/sequel/adapters/sqlite.rb
321
338
  - lib/sequel/adapters/utils/stored_procedures.rb
322
339
  - lib/sequel/adapters/mysql2.rb
@@ -364,6 +381,7 @@ files:
364
381
  - lib/sequel/extensions/string_date_time.rb
365
382
  - lib/sequel/extensions/thread_local_timezones.rb
366
383
  - lib/sequel/extensions/to_dot.rb
384
+ - lib/sequel/extensions/columns_introspection.rb
367
385
  - lib/sequel/metaprogramming.rb
368
386
  - lib/sequel/model.rb
369
387
  - lib/sequel/model/associations.rb
@@ -410,9 +428,16 @@ files:
410
428
  - lib/sequel/plugins/tree.rb
411
429
  - lib/sequel/plugins/xml_serializer.rb
412
430
  - lib/sequel/plugins/association_autoreloading.rb
431
+ - lib/sequel/plugins/serialization_modification_detection.rb
432
+ - lib/sequel/plugins/defaults_setter.rb
433
+ - lib/sequel/plugins/prepared_statements.rb
434
+ - lib/sequel/plugins/prepared_statements_safe.rb
435
+ - lib/sequel/plugins/prepared_statements_with_pk.rb
436
+ - lib/sequel/plugins/prepared_statements_associations.rb
413
437
  - lib/sequel/sql.rb
414
438
  - lib/sequel/timezones.rb
415
439
  - lib/sequel/version.rb
440
+ - lib/sequel/ast_transformer.rb
416
441
  - lib/sequel_core.rb
417
442
  - lib/sequel_model.rb
418
443
  has_rdoc: true