sequel 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/CHANGELOG +48 -0
  2. data/Rakefile +16 -6
  3. data/bin/sequel +0 -0
  4. data/doc/cheat_sheet.rdoc +4 -4
  5. data/doc/schema.rdoc +9 -0
  6. data/lib/sequel_core/adapters/jdbc.rb +7 -7
  7. data/lib/sequel_core/adapters/mysql.rb +6 -11
  8. data/lib/sequel_core/adapters/shared/mssql.rb +21 -1
  9. data/lib/sequel_core/adapters/shared/mysql.rb +19 -27
  10. data/lib/sequel_core/adapters/shared/postgres.rb +67 -11
  11. data/lib/sequel_core/adapters/shared/sqlite.rb +11 -22
  12. data/lib/sequel_core/adapters/sqlite.rb +8 -4
  13. data/lib/sequel_core/core_sql.rb +4 -4
  14. data/lib/sequel_core/database.rb +56 -31
  15. data/lib/sequel_core/database/schema.rb +13 -5
  16. data/lib/sequel_core/dataset/convenience.rb +1 -1
  17. data/lib/sequel_core/dataset/sql.rb +30 -15
  18. data/lib/sequel_core/migration.rb +7 -0
  19. data/lib/sequel_core/schema/generator.rb +13 -2
  20. data/lib/sequel_core/schema/sql.rb +27 -81
  21. data/lib/sequel_core/sql.rb +5 -2
  22. data/lib/sequel_model.rb +8 -2
  23. data/lib/sequel_model/associations.rb +7 -4
  24. data/lib/sequel_model/base.rb +10 -1
  25. data/lib/sequel_model/eager_loading.rb +29 -10
  26. data/lib/sequel_model/record.rb +19 -5
  27. data/spec/adapters/mysql_spec.rb +2 -2
  28. data/spec/adapters/postgres_spec.rb +26 -5
  29. data/spec/adapters/sqlite_spec.rb +42 -8
  30. data/spec/integration/eager_loader_test.rb +51 -58
  31. data/spec/integration/schema_test.rb +28 -4
  32. data/spec/sequel_core/core_sql_spec.rb +3 -0
  33. data/spec/sequel_core/database_spec.rb +24 -0
  34. data/spec/sequel_core/dataset_spec.rb +25 -17
  35. data/spec/sequel_core/schema_spec.rb +58 -26
  36. data/spec/sequel_model/eager_loading_spec.rb +65 -0
  37. data/spec/sequel_model/hooks_spec.rb +1 -1
  38. data/spec/sequel_model/model_spec.rb +1 -0
  39. data/spec/sequel_model/record_spec.rb +74 -1
  40. data/spec/sequel_model/spec_helper.rb +8 -0
  41. metadata +5 -3
@@ -1,21 +1,19 @@
1
1
  require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
2
 
3
3
  describe "Database schema parser" do
4
- before do
5
- INTEGRATION_DB.create_table!(:items){integer :number}
6
- clear_sqls
7
- end
8
4
  after do
9
5
  INTEGRATION_DB.drop_table(:items) if INTEGRATION_DB.table_exists?(:items)
10
6
  end
11
7
 
12
8
  specify "should be a hash with table_names as symbols" do
9
+ INTEGRATION_DB.create_table!(:items){integer :number}
13
10
  schema = INTEGRATION_DB.schema(nil, :reload=>true)
14
11
  schema.should be_a_kind_of(Hash)
15
12
  schema.should include(:items)
16
13
  end
17
14
 
18
15
  specify "should not issue an sql query if the schema has been loaded unless :reload is true" do
16
+ INTEGRATION_DB.create_table!(:items){integer :number}
19
17
  INTEGRATION_DB.schema(nil, :reload=>true)
20
18
  clear_sqls
21
19
  INTEGRATION_DB.schema
@@ -23,6 +21,7 @@ describe "Database schema parser" do
23
21
  end
24
22
 
25
23
  specify "should give the same result for a single table regardless of whether schema was called for a single table" do
24
+ INTEGRATION_DB.create_table!(:items){integer :number}
26
25
  INTEGRATION_DB.schema(:items, :reload=>true).should == INTEGRATION_DB.schema(nil, :reload=>true)[:items]
27
26
  end
28
27
 
@@ -42,6 +41,31 @@ describe "Database schema parser" do
42
41
  INTEGRATION_DB.schema(:items)
43
42
  sqls_should_be
44
43
  end
44
+
45
+ specify "should parse primary keys from the schema properly" do
46
+ INTEGRATION_DB.create_table!(:items){integer :number}
47
+ INTEGRATION_DB.schema(:items).collect{|k,v| k if v[:primary_key]}.compact.should == []
48
+ INTEGRATION_DB.create_table!(:items){primary_key :number}
49
+ INTEGRATION_DB.schema(:items).collect{|k,v| k if v[:primary_key]}.compact.should == [:number]
50
+ INTEGRATION_DB.create_table!(:items){integer :number1; integer :number2; primary_key [:number1, :number2]}
51
+ INTEGRATION_DB.schema(:items).collect{|k,v| k if v[:primary_key]}.compact.should == [:number1, :number2]
52
+ end
53
+
54
+ specify "should parse NULL/NOT NULL from the schema properly" do
55
+ INTEGRATION_DB.create_table!(:items){integer :number, :null=>true}
56
+ INTEGRATION_DB.schema(:items).first.last[:allow_null].should == true
57
+ INTEGRATION_DB.create_table!(:items){integer :number, :null=>false}
58
+ INTEGRATION_DB.schema(:items).first.last[:allow_null].should == false
59
+ end
60
+
61
+ specify "should parse defaults from the schema properly" do
62
+ INTEGRATION_DB.create_table!(:items){integer :number}
63
+ INTEGRATION_DB.schema(:items).first.last[:default].should == nil
64
+ INTEGRATION_DB.create_table!(:items){integer :number, :default=>0}
65
+ INTEGRATION_DB.schema(:items).first.last[:default].to_s.should == "0"
66
+ INTEGRATION_DB.create_table!(:items){varchar :a, :default=>"blah", :size=>4}
67
+ INTEGRATION_DB.schema(:items).first.last[:default].gsub(/::character varying\z/, '').gsub("'", '').should == "blah"
68
+ end
45
69
  end
46
70
 
47
71
  describe "Database schema modifiers" do
@@ -30,10 +30,13 @@ context "Array#case and Hash#case" do
30
30
 
31
31
  specify "should return SQL CASE expression" do
32
32
  @d.literal({:x=>:y}.case(:z)).should == '(CASE WHEN x THEN y ELSE z END)'
33
+ @d.literal({:x=>:y}.case(:z, :exp)).should == '(CASE exp WHEN x THEN y ELSE z END)'
33
34
  ['(CASE WHEN x THEN y WHEN a THEN b ELSE z END)',
34
35
  '(CASE WHEN a THEN b WHEN x THEN y ELSE z END)'].should(include(@d.literal({:x=>:y, :a=>:b}.case(:z))))
35
36
  @d.literal([[:x, :y]].case(:z)).should == '(CASE WHEN x THEN y ELSE z END)'
36
37
  @d.literal([[:x, :y], [:a, :b]].case(:z)).should == '(CASE WHEN x THEN y WHEN a THEN b ELSE z END)'
38
+ @d.literal([[:x, :y], [:a, :b]].case(:z, :exp)).should == '(CASE exp WHEN x THEN y WHEN a THEN b ELSE z END)'
39
+ @d.literal([[:x, :y], [:a, :b]].case(:z, :exp__w)).should == '(CASE exp.w WHEN x THEN y WHEN a THEN b ELSE z END)'
37
40
  end
38
41
 
39
42
  specify "should raise an error if an array that isn't all two pairs is used" do
@@ -1015,3 +1015,27 @@ context "Database#raise_error" do
1015
1015
  proc{MockDatabase.new.send(:raise_error, Interrupt.new(''))}.should raise_error(Sequel::DatabaseError)
1016
1016
  end
1017
1017
  end
1018
+
1019
+ context "Database#typecast_value" do
1020
+ setup do
1021
+ @db = Sequel::Database.new(1 => 2, :logger => 3)
1022
+ end
1023
+ specify "should raise InvalidValue when setting invalid integer" do
1024
+ proc{@db.typecast_value(:integer, "13a")}.should raise_error(Sequel::Error::InvalidValue)
1025
+ end
1026
+ specify "should raise InvalidValue when setting invalid float" do
1027
+ proc{@db.typecast_value(:float, "4.e2")}.should raise_error(Sequel::Error::InvalidValue)
1028
+ end
1029
+ specify "should raise InvalidValue when setting invalid decimal" do
1030
+ proc{@db.typecast_value(:decimal, :invalid_value)}.should raise_error(Sequel::Error::InvalidValue)
1031
+ end
1032
+ specify "should raise InvalidValue when setting invalid date" do
1033
+ proc{@db.typecast_value(:date, Object.new)}.should raise_error(Sequel::Error::InvalidValue)
1034
+ end
1035
+ specify "should raise InvalidValue when setting invalid time" do
1036
+ proc{@db.typecast_value(:time, Date.new)}.should raise_error(Sequel::Error::InvalidValue)
1037
+ end
1038
+ specify "should raise InvalidValue when setting invalid datetime" do
1039
+ proc{@db.typecast_value(:datetime, 4)}.should raise_error(Sequel::Error::InvalidValue)
1040
+ end
1041
+ end
@@ -558,7 +558,7 @@ context "a grouped dataset" do
558
558
  specify "should format the right statement for counting (as a subquery)" do
559
559
  db = MockDatabase.new
560
560
  db[:test].select(:name).group(:name).count
561
- db.sqls.should == ["SELECT COUNT(*) FROM (SELECT name FROM test GROUP BY name) t1 LIMIT 1"]
561
+ db.sqls.should == ["SELECT COUNT(*) FROM (SELECT name FROM test GROUP BY name) AS t1 LIMIT 1"]
562
562
  end
563
563
  end
564
564
 
@@ -684,37 +684,34 @@ context "Dataset#from" do
684
684
 
685
685
  specify "should format a Dataset as a subquery if it has had options set" do
686
686
  @dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should ==
687
- "SELECT * FROM (SELECT * FROM a WHERE (a = 1)) t1"
687
+ "SELECT * FROM (SELECT * FROM a WHERE (a = 1)) AS t1"
688
688
  end
689
689
 
690
690
  specify "should automatically alias sub-queries" do
691
691
  @dataset.from(@dataset.from(:a).group(:b)).select_sql.should ==
692
- "SELECT * FROM (SELECT * FROM a GROUP BY b) t1"
692
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1"
693
693
 
694
694
  d1 = @dataset.from(:a).group(:b)
695
695
  d2 = @dataset.from(:c).group(:d)
696
696
 
697
697
  @dataset.from(d1, d2).sql.should ==
698
- "SELECT * FROM (SELECT * FROM a GROUP BY b) t1, (SELECT * FROM c GROUP BY d) t2"
698
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1, (SELECT * FROM c GROUP BY d) AS t2"
699
699
  end
700
700
 
701
701
  specify "should accept a hash for aliasing" do
702
702
  @dataset.from(:a => :b).sql.should ==
703
- "SELECT * FROM a b"
703
+ "SELECT * FROM a AS b"
704
704
 
705
705
  @dataset.from(:a => 'b').sql.should ==
706
- "SELECT * FROM a b"
707
-
708
- @dataset.from(:a => :c[:d]).sql.should ==
709
- "SELECT * FROM a c(d)"
706
+ "SELECT * FROM a AS b"
710
707
 
711
708
  @dataset.from(@dataset.from(:a).group(:b) => :c).sql.should ==
712
- "SELECT * FROM (SELECT * FROM a GROUP BY b) c"
709
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
713
710
  end
714
711
 
715
712
  specify "should always use a subquery if given a dataset" do
716
713
  @dataset.from(@dataset.from(:a)).select_sql.should ==
717
- "SELECT * FROM (SELECT * FROM a) t1"
714
+ "SELECT * FROM (SELECT * FROM a) AS t1"
718
715
  end
719
716
 
720
717
  specify "should raise if no source is given" do
@@ -985,7 +982,7 @@ context "Dataset#limit" do
985
982
  specify "should work with fixed sql datasets" do
986
983
  @dataset.opts[:sql] = 'select * from cccc'
987
984
  @dataset.limit(6, 10).sql.should ==
988
- 'SELECT * FROM (select * from cccc) t1 LIMIT 6 OFFSET 10'
985
+ 'SELECT * FROM (select * from cccc) AS t1 LIMIT 6 OFFSET 10'
989
986
  end
990
987
 
991
988
  specify "should raise an error if an invalid limit or offset is used" do
@@ -1102,7 +1099,7 @@ context "Dataset#uniq" do
1102
1099
 
1103
1100
  specify "should do a subselect for count" do
1104
1101
  @dataset.uniq.count
1105
- @db.sqls.should == ['SELECT COUNT(*) FROM (SELECT DISTINCT name FROM test) t1 LIMIT 1']
1102
+ @db.sqls.should == ['SELECT COUNT(*) FROM (SELECT DISTINCT name FROM test) AS t1 LIMIT 1']
1106
1103
  end
1107
1104
  end
1108
1105
 
@@ -1138,12 +1135,12 @@ context "Dataset#count" do
1138
1135
  specify "should count properly for datasets with fixed sql" do
1139
1136
  @dataset.opts[:sql] = "select abc from xyz"
1140
1137
  @dataset.count.should == 1
1141
- @c.sql.should == "SELECT COUNT(*) FROM (select abc from xyz) t1 LIMIT 1"
1138
+ @c.sql.should == "SELECT COUNT(*) FROM (select abc from xyz) AS t1 LIMIT 1"
1142
1139
  end
1143
1140
 
1144
1141
  specify "should return limit if count is greater than it" do
1145
1142
  @dataset.limit(5).count.should == 1
1146
- @c.sql.should == "SELECT COUNT(*) FROM (SELECT * FROM test LIMIT 5) t1 LIMIT 1"
1143
+ @c.sql.should == "SELECT COUNT(*) FROM (SELECT * FROM test LIMIT 5) AS t1 LIMIT 1"
1147
1144
  end
1148
1145
 
1149
1146
  it "should work on a graphed_dataset" do
@@ -1284,7 +1281,7 @@ context "Dataset#join_table" do
1284
1281
  ds = MockDataset.new(nil).from(:foo => :f)
1285
1282
  ds.quote_identifiers = true
1286
1283
  ds.join_table(:inner, :bar, :id => :bar_id).sql.should ==
1287
- 'SELECT * FROM "foo" "f" INNER JOIN "bar" ON ("bar"."id" = "f"."bar_id")'
1284
+ 'SELECT * FROM "foo" AS "f" INNER JOIN "bar" ON ("bar"."id" = "f"."bar_id")'
1288
1285
  end
1289
1286
 
1290
1287
  specify "should allow for arbitrary conditions in the JOIN clause" do
@@ -2162,7 +2159,7 @@ context "A paginated dataset" do
2162
2159
  specify "should work with fixed sql" do
2163
2160
  ds = @d.clone(:sql => 'select * from blah')
2164
2161
  ds.meta_def(:count) {150}
2165
- ds.paginate(2, 50).sql.should == 'SELECT * FROM (select * from blah) t1 LIMIT 50 OFFSET 50'
2162
+ ds.paginate(2, 50).sql.should == 'SELECT * FROM (select * from blah) AS t1 LIMIT 50 OFFSET 50'
2166
2163
  end
2167
2164
  end
2168
2165
 
@@ -2322,6 +2319,17 @@ context "Dataset#multi_insert" do
2322
2319
  ]
2323
2320
  end
2324
2321
 
2322
+ specify "should accept string keys as column names" do
2323
+ @ds.multi_insert([{'x'=>1, 'y'=>2}, {'x'=>3, 'y'=>4}])
2324
+ @ds.multi_insert(['x', 'y'], [[1, 2], [3, 4]])
2325
+ @db.sqls.should == [
2326
+ 'BEGIN',
2327
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2328
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2329
+ 'COMMIT'
2330
+ ] * 2
2331
+ end
2332
+
2325
2333
  specify "should accept a columns array and a values array" do
2326
2334
  @ds.multi_insert([:x, :y], [[1, 2], [3, 4]])
2327
2335
  @db.sqls.should == [
@@ -244,7 +244,7 @@ context "DB#create_table" do
244
244
  text :name
245
245
  unique :name
246
246
  end
247
- @db.sqls.should == ["CREATE TABLE cats (name text, UNIQUE (name))", "CREATE UNIQUE INDEX cats_name_index ON cats (name)"]
247
+ @db.sqls.should == ["CREATE TABLE cats (name text, UNIQUE (name))"]
248
248
  end
249
249
 
250
250
  specify "should raise on full-text index definitions" do
@@ -331,6 +331,13 @@ context "DB#create_table" do
331
331
  @db.sqls.should == ["CREATE TABLE cats (CHECK (price < 100))"]
332
332
  end
333
333
 
334
+ specify "should accept hash constraints" do
335
+ @db.create_table(:cats) do
336
+ check :price=>100
337
+ end
338
+ @db.sqls.should == ["CREATE TABLE cats (CHECK (price = 100))"]
339
+ end
340
+
334
341
  specify "should accept named constraint definitions" do
335
342
  @db.create_table(:cats) do
336
343
  integer :score
@@ -459,6 +466,20 @@ context "DB#alter_table" do
459
466
  setup do
460
467
  @db = SchemaDummyDatabase.new
461
468
  end
469
+
470
+ specify "should allow adding not null constraint" do
471
+ @db.alter_table(:cats) do
472
+ set_column_allow_null :score, false
473
+ end
474
+ @db.sqls.should == ["ALTER TABLE cats ALTER COLUMN score SET NOT NULL"]
475
+ end
476
+
477
+ specify "should allow droping not null constraint" do
478
+ @db.alter_table(:cats) do
479
+ set_column_allow_null :score, true
480
+ end
481
+ @db.sqls.should == ["ALTER TABLE cats ALTER COLUMN score DROP NOT NULL"]
482
+ end
462
483
 
463
484
  specify "should support add_column" do
464
485
  @db.alter_table(:cats) do
@@ -598,44 +619,54 @@ end
598
619
 
599
620
  context "Schema Parser" do
600
621
  setup do
601
- sqls = @sqls = []
622
+ @sqls = []
602
623
  @db = Sequel::Database.new
603
- @db.meta_def(:dataset) do
604
- ds = super()
605
- ds.instance_variable_set(:@sqls, sqls)
606
- def ds.fetch_rows(sql)
607
- @sqls << sql
608
- table = /'(.*)'/.match(sql)[1]
609
- h = {:column=>"a", :db_type=>table, :max_chars=>nil, :numeric_precision=>nil, :default=>'', :allow_null=>'YES'}
610
- (h[:column] = h[:table_name] = :x) if sql =~ /BASE TABLE/
611
- yield h
612
- end
613
- ds
614
- end
615
624
  end
616
625
  after do
617
626
  Sequel.convert_tinyint_to_bool = true
618
627
  end
619
628
 
620
629
  specify "should parse the schema correctly for a single table" do
621
- @db.schema(:x).should == [[:a, {:type=>nil, :db_type=>"x", :max_chars=>nil, :numeric_precision=>nil, :default=>nil, :allow_null=>true}]]
622
- @sqls.should == ["SELECT column_name AS column, data_type AS db_type, character_maximum_length AS max_chars, numeric_precision, column_default AS default, is_nullable AS allow_null FROM information_schema.tables AS t INNER JOIN information_schema.columns AS c USING (table_catalog, table_schema, table_name) WHERE (c.table_name = 'x')"]
623
- @db.schema(:x).should == [[:a, {:type=>nil, :db_type=>"x", :max_chars=>nil, :numeric_precision=>nil, :default=>nil, :allow_null=>true}]]
624
- @sqls.length.should == 1
625
- @db.schema(:x, :reload=>true).should == [[:a, {:type=>nil, :db_type=>"x", :max_chars=>nil, :numeric_precision=>nil, :default=>nil, :allow_null=>true}]]
626
- @sqls.length.should == 2
630
+ sqls = @sqls
631
+ proc{@db.schema(:x)}.should raise_error(Sequel::Error)
632
+ @db.meta_def(:schema_parse_table) do |t, opts|
633
+ sqls << t
634
+ [[:a, {:db_type=>t.to_s}]]
635
+ end
636
+ @db.schema(:x).should == [[:a, {:db_type=>"x"}]]
637
+ @sqls.should == [:x]
638
+ @db.schema(:x).should == [[:a, {:db_type=>"x"}]]
639
+ @sqls.should == [:x]
640
+ @db.schema(:x, :reload=>true).should == [[:a, {:db_type=>"x"}]]
641
+ @sqls.should == [:x, :x]
627
642
  end
628
643
 
629
644
  specify "should parse the schema correctly for all tables" do
630
- @db.schema.should == {:x=>[[:x, {:type=>nil, :db_type=>"BASE TABLE", :max_chars=>nil, :numeric_precision=>nil, :default=>nil, :allow_null=>true}]]}
631
- @sqls.should == ["SELECT column_name AS column, data_type AS db_type, character_maximum_length AS max_chars, numeric_precision, column_default AS default, is_nullable AS allow_null, c.table_name FROM information_schema.tables AS t INNER JOIN information_schema.columns AS c USING (table_catalog, table_schema, table_name) WHERE (t.table_type = 'BASE TABLE')"]
632
- @db.schema.should == {:x=>[[:x, {:type=>nil, :db_type=>"BASE TABLE", :max_chars=>nil, :numeric_precision=>nil, :default=>nil, :allow_null=>true}]]}
633
- @sqls.length.should == 1
634
- @db.schema(nil, :reload=>true).should == {:x=>[[:x, {:type=>nil, :db_type=>"BASE TABLE", :max_chars=>nil, :numeric_precision=>nil, :default=>nil, :allow_null=>true}]]}
635
- @sqls.length.should == 2
645
+ sqls = @sqls
646
+ proc{@db.schema}.should raise_error(Sequel::Error)
647
+ @db.meta_def(:tables){[:x]}
648
+ @db.meta_def(:schema_parse_table) do |t, opts|
649
+ sqls << t
650
+ [[:x, {:db_type=>t.to_s}]]
651
+ end
652
+ @db.schema.should == {:x=>[[:x, {:db_type=>"x"}]]}
653
+ @sqls.should == [:x]
654
+ @db.schema.should == {:x=>[[:x, {:db_type=>"x"}]]}
655
+ @sqls.should == [:x]
656
+ @db.schema(nil, :reload=>true).should == {:x=>[[:x, {:db_type=>"x"}]]}
657
+ @sqls.should == [:x, :x]
658
+ @db.meta_def(:schema_parse_tables) do |opts|
659
+ sqls << 1
660
+ {:x=>[[:a, {:db_type=>"1"}]]}
661
+ end
662
+ @db.schema(nil, :reload=>true).should == {:x=>[[:a, {:db_type=>"1"}]]}
663
+ @sqls.should == [:x, :x, 1]
636
664
  end
637
665
 
638
666
  specify "should correctly parse all supported data types" do
667
+ @db.meta_def(:schema_parse_table) do |t, opts|
668
+ [[:x, {:type=>schema_column_type(t.to_s)}]]
669
+ end
639
670
  @db.schema(:tinyint).first.last[:type].should == :boolean
640
671
  Sequel.convert_tinyint_to_bool = false
641
672
  @db.schema(:tinyint, :reload=>true).first.last[:type].should == :integer
@@ -646,6 +677,7 @@ context "Schema Parser" do
646
677
  @db.schema(:character).first.last[:type].should == :string
647
678
  @db.schema(:"character varying").first.last[:type].should == :string
648
679
  @db.schema(:varchar).first.last[:type].should == :string
680
+ @db.schema(:"varchar(255)").first.last[:type].should == :string
649
681
  @db.schema(:text).first.last[:type].should == :string
650
682
  @db.schema(:date).first.last[:type].should == :date
651
683
  @db.schema(:datetime).first.last[:type].should == :datetime
@@ -638,6 +638,37 @@ describe Sequel::Model, "#eager_graph" do
638
638
  a.album.band.members.first.values.should == {:id => 5}
639
639
  end
640
640
 
641
+ it "should allow cascading of eager loading for multiple *_to_many associations, eliminating duplicates caused by cartesian products" do
642
+ ds = GraphBand.eager_graph({:albums=>:tracks}, :members)
643
+ ds.sql.should == 'SELECT bands.id, bands.vocalist_id, albums.id AS albums_id, albums.band_id, tracks.id AS tracks_id, tracks.album_id, members.id AS members_id FROM bands LEFT OUTER JOIN albums ON (albums.band_id = bands.id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id) LEFT OUTER JOIN bm ON (bm.band_id = bands.id) LEFT OUTER JOIN members ON (members.id = bm.member_id)'
644
+ def ds.fetch_rows(sql, &block)
645
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>4, :album_id=>3, :members_id=>5})
646
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>4, :album_id=>3, :members_id=>6})
647
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>5, :album_id=>3, :members_id=>5})
648
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>5, :album_id=>3, :members_id=>6})
649
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>6, :album_id=>4, :members_id=>5})
650
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>6, :album_id=>4, :members_id=>6})
651
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>7, :album_id=>4, :members_id=>5})
652
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>7, :album_id=>4, :members_id=>6})
653
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>8, :album_id=>5, :members_id=>5})
654
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>8, :album_id=>5, :members_id=>6})
655
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>9, :album_id=>5, :members_id=>5})
656
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>9, :album_id=>5, :members_id=>6})
657
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>1, :album_id=>6, :members_id=>5})
658
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>1, :album_id=>6, :members_id=>6})
659
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>2, :album_id=>6, :members_id=>5})
660
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>2, :album_id=>6, :members_id=>6})
661
+ end
662
+ a = ds.all
663
+ a.should == [GraphBand.load(:id=>1, :vocalist_id=>2), GraphBand.load(:id=>2, :vocalist_id=>2)]
664
+ members = a.map{|x| x.members}
665
+ members.should == [[GraphBandMember.load(:id=>5), GraphBandMember.load(:id=>6)], [GraphBandMember.load(:id=>5), GraphBandMember.load(:id=>6)]]
666
+ albums = a.map{|x| x.albums}
667
+ albums.should == [[GraphAlbum.load(:id=>3, :band_id=>1), GraphAlbum.load(:id=>4, :band_id=>1)], [GraphAlbum.load(:id=>5, :band_id=>2), GraphAlbum.load(:id=>6, :band_id=>2)]]
668
+ tracks = albums.map{|x| x.map{|y| y.tracks}}
669
+ tracks.should == [[[GraphTrack.load(:id=>4, :album_id=>3), GraphTrack.load(:id=>5, :album_id=>3)], [GraphTrack.load(:id=>6, :album_id=>4), GraphTrack.load(:id=>7, :album_id=>4)]], [[GraphTrack.load(:id=>8, :album_id=>5), GraphTrack.load(:id=>9, :album_id=>5)], [GraphTrack.load(:id=>1, :album_id=>6), GraphTrack.load(:id=>2, :album_id=>6)]]]
670
+ end
671
+
641
672
  it "should populate the reciprocal many_to_one association when eagerly loading the one_to_many association" do
642
673
  MODEL_DB.reset
643
674
  ds = GraphAlbum.eager_graph(:tracks)
@@ -961,4 +992,38 @@ describe Sequel::Model, "#eager_graph" do
961
992
  it "should create unique table aliases for all associations" do
962
993
  GraphAlbum.eager_graph(:previous_album=>{:previous_album=>:previous_album}).sql.should == "SELECT albums.id, albums.band_id, previous_album.id AS previous_album_id, previous_album.band_id AS previous_album_band_id, previous_album_0.id AS previous_album_0_id, previous_album_0.band_id AS previous_album_0_band_id, previous_album_1.id AS previous_album_1_id, previous_album_1.band_id AS previous_album_1_band_id FROM albums LEFT OUTER JOIN albums AS previous_album ON (previous_album.id = albums.previous_album_id) LEFT OUTER JOIN albums AS previous_album_0 ON (previous_album_0.id = previous_album.previous_album_id) LEFT OUTER JOIN albums AS previous_album_1 ON (previous_album_1.id = previous_album_0.previous_album_id)"
963
994
  end
995
+
996
+ it "should respect the association's :order" do
997
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id]
998
+ 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 LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id) ORDER BY right_tracks.id, right_tracks.album_id'
999
+ end
1000
+
1001
+ it "should only qualify unqualified symbols, identifiers, or ordered versions in association's :order" do
1002
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:blah__id.identifier, :blah__id.identifier.desc, :blah__id.desc, :blah__id, :album_id, :album_id.desc, 1, 'RANDOM()'.lit, :a.qualify(:b)]
1003
+ 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 LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id) ORDER BY right_tracks.blah__id, right_tracks.blah__id DESC, blah.id DESC, blah.id, right_tracks.album_id, right_tracks.album_id DESC, 1, RANDOM(), b.a'
1004
+ end
1005
+
1006
+ it "should not respect the association's :order if :order_eager_graph is false" do
1007
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id], :order_eager_graph=>false
1008
+ 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 LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id)'
1009
+ end
1010
+
1011
+ it "should add the association's :order to the existing order" do
1012
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id]
1013
+ GraphAlbum.order(:band_id).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 LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id) ORDER BY band_id, right_tracks.id, right_tracks.album_id'
1014
+ end
1015
+
1016
+ it "should add the association's :order for cascading associations" do
1017
+ GraphBand.one_to_many :a_albums, :class=>'GraphAlbum', :key=>:band_id, :order=>:name
1018
+ GraphAlbum.one_to_many :b_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id]
1019
+ GraphBand.eager_graph(:a_albums=>:b_tracks).sql.should == 'SELECT bands.id, bands.vocalist_id, a_albums.id AS a_albums_id, a_albums.band_id, b_tracks.id AS b_tracks_id, b_tracks.album_id FROM bands LEFT OUTER JOIN albums AS a_albums ON (a_albums.band_id = bands.id) LEFT OUTER JOIN tracks AS b_tracks ON (b_tracks.album_id = a_albums.id) ORDER BY a_albums.name, b_tracks.id, b_tracks.album_id'
1020
+ GraphAlbum.one_to_many :albums, :class=>'GraphAlbum', :key=>:band_id, :order=>[:band_id, :id]
1021
+ GraphAlbum.eager_graph(:albums=>{:albums=>:albums}).sql.should == 'SELECT albums.id, albums.band_id, albums_0.id AS albums_0_id, albums_0.band_id AS albums_0_band_id, albums_1.id AS albums_1_id, albums_1.band_id AS albums_1_band_id, albums_2.id AS albums_2_id, albums_2.band_id AS albums_2_band_id FROM albums LEFT OUTER JOIN albums AS albums_0 ON (albums_0.band_id = albums.id) LEFT OUTER JOIN albums AS albums_1 ON (albums_1.band_id = albums_0.id) LEFT OUTER JOIN albums AS albums_2 ON (albums_2.band_id = albums_1.id) ORDER BY albums_0.band_id, albums_0.id, albums_1.band_id, albums_1.id, albums_2.band_id, albums_2.id'
1022
+ end
1023
+
1024
+ it "should add the associations :order for multiple associations" do
1025
+ GraphAlbum.many_to_many :a_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :order=>:id
1026
+ GraphAlbum.one_to_many :b_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id]
1027
+ GraphAlbum.eager_graph(:a_genres, :b_tracks).sql.should == 'SELECT albums.id, albums.band_id, a_genres.id AS a_genres_id, b_tracks.id AS b_tracks_id, b_tracks.album_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS a_genres ON (a_genres.id = ag.genre_id) LEFT OUTER JOIN tracks AS b_tracks ON (b_tracks.album_id = albums.id) ORDER BY a_genres.id, b_tracks.id, b_tracks.album_id'
1028
+ end
964
1029
  end
@@ -305,7 +305,7 @@ describe "Model#before_destroy && Model#after_destroy" do
305
305
 
306
306
  specify "should be called around record destruction" do
307
307
  @c.before_destroy {MODEL_DB << "BLAH before"}
308
- m = @c.new(:id => 2233)
308
+ m = @c.load(:id => 2233)
309
309
  m.destroy
310
310
  MODEL_DB.sqls.should == [
311
311
  'BLAH before',
@@ -171,6 +171,7 @@ describe Sequel::Model, "new" do
171
171
  before(:each) do
172
172
  @m = Class.new(Sequel::Model) do
173
173
  set_dataset MODEL_DB[:items]
174
+ columns :x, :id
174
175
  end
175
176
  end
176
177