sequel 2.2.0 → 2.3.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 (98) hide show
  1. data/CHANGELOG +1551 -4
  2. data/README +306 -19
  3. data/Rakefile +84 -56
  4. data/bin/sequel +106 -0
  5. data/doc/cheat_sheet.rdoc +225 -0
  6. data/doc/dataset_filtering.rdoc +182 -0
  7. data/lib/sequel_core.rb +136 -0
  8. data/lib/sequel_core/adapters/adapter_skeleton.rb +54 -0
  9. data/lib/sequel_core/adapters/ado.rb +80 -0
  10. data/lib/sequel_core/adapters/db2.rb +148 -0
  11. data/lib/sequel_core/adapters/dbi.rb +117 -0
  12. data/lib/sequel_core/adapters/informix.rb +78 -0
  13. data/lib/sequel_core/adapters/jdbc.rb +186 -0
  14. data/lib/sequel_core/adapters/jdbc/mysql.rb +55 -0
  15. data/lib/sequel_core/adapters/jdbc/postgresql.rb +66 -0
  16. data/lib/sequel_core/adapters/jdbc/sqlite.rb +47 -0
  17. data/lib/sequel_core/adapters/mysql.rb +231 -0
  18. data/lib/sequel_core/adapters/odbc.rb +155 -0
  19. data/lib/sequel_core/adapters/odbc_mssql.rb +106 -0
  20. data/lib/sequel_core/adapters/openbase.rb +64 -0
  21. data/lib/sequel_core/adapters/oracle.rb +170 -0
  22. data/lib/sequel_core/adapters/postgres.rb +199 -0
  23. data/lib/sequel_core/adapters/shared/mysql.rb +275 -0
  24. data/lib/sequel_core/adapters/shared/postgres.rb +351 -0
  25. data/lib/sequel_core/adapters/shared/sqlite.rb +146 -0
  26. data/lib/sequel_core/adapters/sqlite.rb +138 -0
  27. data/lib/sequel_core/connection_pool.rb +194 -0
  28. data/lib/sequel_core/core_ext.rb +203 -0
  29. data/lib/sequel_core/core_sql.rb +184 -0
  30. data/lib/sequel_core/database.rb +471 -0
  31. data/lib/sequel_core/database/schema.rb +156 -0
  32. data/lib/sequel_core/dataset.rb +457 -0
  33. data/lib/sequel_core/dataset/callback.rb +13 -0
  34. data/lib/sequel_core/dataset/convenience.rb +245 -0
  35. data/lib/sequel_core/dataset/pagination.rb +96 -0
  36. data/lib/sequel_core/dataset/query.rb +41 -0
  37. data/lib/sequel_core/dataset/schema.rb +15 -0
  38. data/lib/sequel_core/dataset/sql.rb +889 -0
  39. data/lib/sequel_core/deprecated.rb +26 -0
  40. data/lib/sequel_core/exceptions.rb +42 -0
  41. data/lib/sequel_core/migration.rb +187 -0
  42. data/lib/sequel_core/object_graph.rb +216 -0
  43. data/lib/sequel_core/pretty_table.rb +71 -0
  44. data/lib/sequel_core/schema.rb +2 -0
  45. data/lib/sequel_core/schema/generator.rb +239 -0
  46. data/lib/sequel_core/schema/sql.rb +325 -0
  47. data/lib/sequel_core/sql.rb +812 -0
  48. data/lib/sequel_model.rb +5 -1
  49. data/lib/sequel_model/association_reflection.rb +3 -8
  50. data/lib/sequel_model/base.rb +15 -10
  51. data/lib/sequel_model/inflector.rb +3 -5
  52. data/lib/sequel_model/plugins.rb +1 -1
  53. data/lib/sequel_model/record.rb +11 -3
  54. data/lib/sequel_model/schema.rb +4 -4
  55. data/lib/sequel_model/validations.rb +6 -1
  56. data/spec/adapters/ado_spec.rb +17 -0
  57. data/spec/adapters/informix_spec.rb +96 -0
  58. data/spec/adapters/mysql_spec.rb +764 -0
  59. data/spec/adapters/oracle_spec.rb +222 -0
  60. data/spec/adapters/postgres_spec.rb +441 -0
  61. data/spec/adapters/spec_helper.rb +7 -0
  62. data/spec/adapters/sqlite_spec.rb +400 -0
  63. data/spec/integration/dataset_test.rb +51 -0
  64. data/spec/integration/eager_loader_test.rb +702 -0
  65. data/spec/integration/schema_test.rb +102 -0
  66. data/spec/integration/spec_helper.rb +44 -0
  67. data/spec/integration/type_test.rb +43 -0
  68. data/spec/rcov.opts +2 -0
  69. data/spec/sequel_core/connection_pool_spec.rb +363 -0
  70. data/spec/sequel_core/core_ext_spec.rb +156 -0
  71. data/spec/sequel_core/core_sql_spec.rb +427 -0
  72. data/spec/sequel_core/database_spec.rb +964 -0
  73. data/spec/sequel_core/dataset_spec.rb +2977 -0
  74. data/spec/sequel_core/expression_filters_spec.rb +346 -0
  75. data/spec/sequel_core/migration_spec.rb +261 -0
  76. data/spec/sequel_core/object_graph_spec.rb +234 -0
  77. data/spec/sequel_core/pretty_table_spec.rb +58 -0
  78. data/spec/sequel_core/schema_generator_spec.rb +122 -0
  79. data/spec/sequel_core/schema_spec.rb +497 -0
  80. data/spec/sequel_core/spec_helper.rb +51 -0
  81. data/spec/{association_reflection_spec.rb → sequel_model/association_reflection_spec.rb} +6 -6
  82. data/spec/{associations_spec.rb → sequel_model/associations_spec.rb} +47 -18
  83. data/spec/{base_spec.rb → sequel_model/base_spec.rb} +2 -1
  84. data/spec/{caching_spec.rb → sequel_model/caching_spec.rb} +0 -0
  85. data/spec/{dataset_methods_spec.rb → sequel_model/dataset_methods_spec.rb} +13 -1
  86. data/spec/{eager_loading_spec.rb → sequel_model/eager_loading_spec.rb} +75 -14
  87. data/spec/{hooks_spec.rb → sequel_model/hooks_spec.rb} +4 -4
  88. data/spec/sequel_model/inflector_spec.rb +119 -0
  89. data/spec/{model_spec.rb → sequel_model/model_spec.rb} +30 -11
  90. data/spec/{plugins_spec.rb → sequel_model/plugins_spec.rb} +0 -0
  91. data/spec/{record_spec.rb → sequel_model/record_spec.rb} +47 -6
  92. data/spec/{schema_spec.rb → sequel_model/schema_spec.rb} +18 -4
  93. data/spec/{spec_helper.rb → sequel_model/spec_helper.rb} +3 -2
  94. data/spec/{validations_spec.rb → sequel_model/validations_spec.rb} +37 -17
  95. data/spec/spec_config.rb +9 -0
  96. data/spec/spec_config.rb.example +10 -0
  97. metadata +110 -37
  98. data/spec/inflector_spec.rb +0 -34
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ unless Object.const_defined?('Sequel')
3
+ $:.unshift(File.join(File.dirname(__FILE__), "../../lib/"))
4
+ require 'sequel_core'
5
+ end
6
+
7
+ class MockDataset < Sequel::Dataset
8
+ def insert(*args)
9
+ @db.execute insert_sql(*args)
10
+ end
11
+
12
+ def update(*args)
13
+ @db.execute update_sql(*args)
14
+ end
15
+
16
+ def fetch_rows(sql)
17
+ @db.execute(sql)
18
+ yield({:id => 1, :x => 1})
19
+ end
20
+
21
+ def quoted_identifier(c)
22
+ "\"#{c}\""
23
+ end
24
+ end
25
+
26
+ class MockDatabase < Sequel::Database
27
+ @@quote_identifiers = false
28
+ attr_reader :sqls
29
+
30
+ def execute(sql)
31
+ @sqls ||= []
32
+ @sqls << sql
33
+ end
34
+
35
+ def reset
36
+ @sqls = []
37
+ end
38
+
39
+ def transaction; yield; end
40
+
41
+ def dataset; MockDataset.new(self); end
42
+ end
43
+
44
+ class SchemaDummyDatabase < Sequel::Database
45
+ attr_reader :sqls
46
+
47
+ def execute(sql)
48
+ @sqls ||= []
49
+ @sqls << sql
50
+ end
51
+ end
@@ -3,7 +3,7 @@ require File.join(File.dirname(__FILE__), "spec_helper")
3
3
  describe Sequel::Model::Associations::AssociationReflection, "#associated_class" do
4
4
  before do
5
5
  @c = Class.new(Sequel::Model)
6
- class ParParent < Sequel::Model; end
6
+ class ::ParParent < Sequel::Model; end
7
7
  end
8
8
 
9
9
  it "should use the :class value if present" do
@@ -21,7 +21,7 @@ end
21
21
  describe Sequel::Model::Associations::AssociationReflection, "#associated_primary_key" do
22
22
  before do
23
23
  @c = Class.new(Sequel::Model)
24
- class ParParent < Sequel::Model; end
24
+ class ::ParParent < Sequel::Model; end
25
25
  end
26
26
 
27
27
  it "should use the :right_primary_key value if present" do
@@ -46,9 +46,9 @@ describe Sequel::Model::Associations::AssociationReflection, "#reciprocal" do
46
46
  end
47
47
 
48
48
  it "should figure out the reciprocal if the :reciprocal value is not present" do
49
- class ParParent < Sequel::Model; end
50
- class ParParentTwo < Sequel::Model; end
51
- class ParParentThree < Sequel::Model; end
49
+ class ::ParParent < Sequel::Model; end
50
+ class ::ParParentTwo < Sequel::Model; end
51
+ class ::ParParentThree < Sequel::Model; end
52
52
  ParParent.many_to_one :par_parent_two
53
53
  ParParentTwo.one_to_many :par_parents
54
54
  ParParent.many_to_many :par_parent_threes
@@ -68,7 +68,7 @@ end
68
68
  describe Sequel::Model::Associations::AssociationReflection, "#select" do
69
69
  before do
70
70
  @c = Class.new(Sequel::Model)
71
- class ParParent < Sequel::Model; end
71
+ class ::ParParent < Sequel::Model; end
72
72
  end
73
73
 
74
74
  it "should use the :select value if present" do
@@ -4,7 +4,7 @@ describe Sequel::Model, "associate" do
4
4
  it "should use explicit class if given a class, symbol, or string" do
5
5
  MODEL_DB.reset
6
6
  klass = Class.new(Sequel::Model(:nodes))
7
- class ParParent < Sequel::Model
7
+ class ::ParParent < Sequel::Model
8
8
  end
9
9
 
10
10
  klass.associate :many_to_one, :par_parent0, :class=>ParParent
@@ -48,6 +48,7 @@ describe Sequel::Model, "many_to_one" do
48
48
  MODEL_DB.reset
49
49
 
50
50
  @c2 = Class.new(Sequel::Model(:nodes)) do
51
+ unrestrict_primary_key
51
52
  columns :id, :parent_id, :par_parent_id, :blah
52
53
  end
53
54
 
@@ -66,7 +67,7 @@ describe Sequel::Model, "many_to_one" do
66
67
  end
67
68
 
68
69
  it "should use implicit class if omitted" do
69
- class ParParent < Sequel::Model
70
+ class ::ParParent < Sequel::Model
70
71
  end
71
72
 
72
73
  @c2.many_to_one :par_parent
@@ -79,7 +80,7 @@ describe Sequel::Model, "many_to_one" do
79
80
  end
80
81
 
81
82
  it "should use class inside module if given as a string" do
82
- module Par
83
+ module ::Par
83
84
  class Parent < Sequel::Model
84
85
  end
85
86
  end
@@ -263,10 +264,29 @@ describe Sequel::Model, "many_to_one" do
263
264
  MODEL_DB.sqls.should == []
264
265
  end
265
266
 
267
+ it "should get all matching records and only return the first if :key option is set to nil" do
268
+ c2 = @c2
269
+ @c2.one_to_many :children, :class => @c2, :key=>:parent_id
270
+ @c2.many_to_one :first_grand_parent, :class => @c2, :key=>nil, :eager_graph=>:children, :dataset=>proc{c2.filter(:children_id=>parent_id)}
271
+ ds = @c2.dataset
272
+ def ds.columns
273
+ [:id, :parent_id, :par_parent_id, :blah]
274
+ end
275
+ def ds.fetch_rows(sql, &block)
276
+ MODEL_DB.sqls << sql
277
+ yield({:id=>1, :parent_id=>0, :par_parent_id=>3, :blah=>4, :children_id=>2, :children_parent_id=>1, :children_par_parent_id=>5, :children_blah=>6})
278
+ end
279
+ p = @c2.new(:parent_id=>2)
280
+ fgp = p.first_grand_parent
281
+ MODEL_DB.sqls.should == ["SELECT nodes.id, nodes.parent_id, nodes.par_parent_id, nodes.blah, children.id AS children_id, children.parent_id AS children_parent_id, children.par_parent_id AS children_par_parent_id, children.blah AS children_blah FROM nodes LEFT OUTER JOIN nodes AS children ON (children.parent_id = nodes.id) WHERE (children_id = 2)"]
282
+ fgp.values.should == {:id=>1, :parent_id=>0, :par_parent_id=>3, :blah=>4}
283
+ fgp.children.first.values.should == {:id=>2, :parent_id=>1, :par_parent_id=>5, :blah=>6}
284
+ end
285
+
266
286
  it "should not create the setter method if :read_only option is used" do
267
287
  @c2.many_to_one :parent, :class => @c2, :read_only=>true
268
- @c2.instance_methods.should(include('parent'))
269
- @c2.instance_methods.should_not(include('parent='))
288
+ @c2.instance_methods.collect{|x| x.to_s}.should(include('parent'))
289
+ @c2.instance_methods.collect{|x| x.to_s}.should_not(include('parent='))
270
290
  end
271
291
 
272
292
  it "should raise an error if trying to set a model object that doesn't have a valid primary key" do
@@ -292,7 +312,7 @@ describe Sequel::Model, "many_to_one" do
292
312
 
293
313
  it "should make the change to the foreign_key value inside a _association= method" do
294
314
  @c2.many_to_one :parent, :class => @c2
295
- @c2.private_instance_methods.sort.should(include("_parent="))
315
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_parent="))
296
316
  p = @c2.new
297
317
  c = @c2.load(:id=>123)
298
318
  def p._parent=(x)
@@ -362,6 +382,11 @@ describe Sequel::Model, "many_to_one" do
362
382
  proc{p.parent = nil}.should raise_error(Sequel::Error)
363
383
  end
364
384
 
385
+ it "should raise an error if a callback is not a proc or symbol" do
386
+ @c2.many_to_one :parent, :class => @c2, :before_add=>Object.new
387
+ proc{@c2.new.parent = @c2.load(:id=>1)}.should raise_error(Sequel::Error)
388
+ end
389
+
365
390
  it "should call the remove callbacks for the previous object and the add callbacks for the new object" do
366
391
  c = @c2.load(:id=>123)
367
392
  d = @c2.load(:id=>321)
@@ -398,10 +423,12 @@ describe Sequel::Model, "one_to_many" do
398
423
  MODEL_DB.reset
399
424
 
400
425
  @c1 = Class.new(Sequel::Model(:attributes)) do
426
+ unrestrict_primary_key
401
427
  columns :id, :node_id
402
428
  end
403
429
 
404
430
  @c2 = Class.new(Sequel::Model(:nodes)) do
431
+ unrestrict_primary_key
405
432
  attr_accessor :xxx
406
433
 
407
434
  def self.name; 'Node'; end
@@ -435,7 +462,7 @@ describe Sequel::Model, "one_to_many" do
435
462
  end
436
463
 
437
464
  it "should use implicit class if omitted" do
438
- class HistoricalValue < Sequel::Model
465
+ class ::HistoricalValue < Sequel::Model
439
466
  end
440
467
 
441
468
  @c2.one_to_many :historical_values
@@ -448,7 +475,7 @@ describe Sequel::Model, "one_to_many" do
448
475
  end
449
476
 
450
477
  it "should use class inside a module if given as a string" do
451
- module Historical
478
+ module ::Historical
452
479
  class Value < Sequel::Model
453
480
  end
454
481
  end
@@ -692,7 +719,7 @@ describe Sequel::Model, "one_to_many" do
692
719
 
693
720
  it "should not create the add_, remove_, or remove_all_ methods if :read_only option is used" do
694
721
  @c2.one_to_many :attributes, :class => @c1, :read_only=>true
695
- im = @c2.instance_methods
722
+ im = @c2.instance_methods.collect{|x| x.to_s}
696
723
  im.should(include('attributes'))
697
724
  im.should(include('attributes_dataset'))
698
725
  im.should_not(include('add_attribute'))
@@ -803,7 +830,7 @@ describe Sequel::Model, "one_to_many" do
803
830
 
804
831
  it "should not add a getter method if the :one_to_one option is true and :read_only option is true" do
805
832
  @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true, :read_only=>true
806
- im = @c2.instance_methods
833
+ im = @c2.instance_methods.collect{|x| x.to_s}
807
834
  im.should(include('attribute'))
808
835
  im.should_not(include('attribute='))
809
836
  end
@@ -846,7 +873,7 @@ describe Sequel::Model, "one_to_many" do
846
873
 
847
874
  it "should make non getter and setter methods private if :one_to_one option is used" do
848
875
  @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true do |ds| end
849
- meths = @c2.private_instance_methods(false)
876
+ meths = @c2.private_instance_methods(false).collect{|x| x.to_s}
850
877
  meths.should(include("attributes"))
851
878
  meths.should(include("add_attribute"))
852
879
  meths.should(include("attributes_dataset"))
@@ -854,7 +881,7 @@ describe Sequel::Model, "one_to_many" do
854
881
 
855
882
  it "should call an _add_ method internally to add attributes" do
856
883
  @c2.one_to_many :attributes, :class => @c1
857
- @c2.private_instance_methods.sort.should(include("_add_attribute"))
884
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_add_attribute"))
858
885
  p = @c2.load(:id=>10)
859
886
  c = @c1.load(:id=>123)
860
887
  def p._add_attribute(x)
@@ -867,7 +894,7 @@ describe Sequel::Model, "one_to_many" do
867
894
 
868
895
  it "should call a _remove_ method internally to remove attributes" do
869
896
  @c2.one_to_many :attributes, :class => @c1
870
- @c2.private_instance_methods.sort.should(include("_remove_attribute"))
897
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_remove_attribute"))
871
898
  p = @c2.load(:id=>10)
872
899
  c = @c1.load(:id=>123)
873
900
  def p._remove_attribute(x)
@@ -966,12 +993,14 @@ describe Sequel::Model, "many_to_many" do
966
993
  MODEL_DB.reset
967
994
 
968
995
  @c1 = Class.new(Sequel::Model(:attributes)) do
996
+ unrestrict_primary_key
969
997
  def self.name; 'Attribute'; end
970
998
  def self.to_s; 'Attribute'; end
971
999
  columns :id
972
1000
  end
973
1001
 
974
1002
  @c2 = Class.new(Sequel::Model(:nodes)) do
1003
+ unrestrict_primary_key
975
1004
  attr_accessor :xxx
976
1005
 
977
1006
  def self.name; 'Node'; end
@@ -1000,7 +1029,7 @@ describe Sequel::Model, "many_to_many" do
1000
1029
  end
1001
1030
 
1002
1031
  it "should use implicit class if omitted" do
1003
- class Tag < Sequel::Model
1032
+ class ::Tag < Sequel::Model
1004
1033
  end
1005
1034
 
1006
1035
  @c2.many_to_many :tags
@@ -1012,7 +1041,7 @@ describe Sequel::Model, "many_to_many" do
1012
1041
  end
1013
1042
 
1014
1043
  it "should use class inside module if given as a string" do
1015
- module Historical
1044
+ module ::Historical
1016
1045
  class Tag < Sequel::Model
1017
1046
  end
1018
1047
  end
@@ -1263,7 +1292,7 @@ describe Sequel::Model, "many_to_many" do
1263
1292
 
1264
1293
  it "should not create the add_, remove_, or remove_all_ methods if :read_only option is used" do
1265
1294
  @c2.many_to_many :attributes, :class => @c1, :read_only=>true
1266
- im = @c2.instance_methods
1295
+ im = @c2.instance_methods.collect{|x| x.to_s}
1267
1296
  im.should(include('attributes'))
1268
1297
  im.should(include('attributes_dataset'))
1269
1298
  im.should_not(include('add_attribute'))
@@ -1329,7 +1358,7 @@ describe Sequel::Model, "many_to_many" do
1329
1358
 
1330
1359
  it "should call an _add_ method internally to add attributes" do
1331
1360
  @c2.many_to_many :attributes, :class => @c1
1332
- @c2.private_instance_methods.sort.should(include("_add_attribute"))
1361
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_add_attribute"))
1333
1362
  p = @c2.load(:id=>10)
1334
1363
  c = @c1.load(:id=>123)
1335
1364
  def p._add_attribute(x)
@@ -1342,7 +1371,7 @@ describe Sequel::Model, "many_to_many" do
1342
1371
 
1343
1372
  it "should call a _remove_ method internally to remove attributes" do
1344
1373
  @c2.many_to_many :attributes, :class => @c1
1345
- @c2.private_instance_methods.sort.should(include("_remove_attribute"))
1374
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_remove_attribute"))
1346
1375
  p = @c2.load(:id=>10)
1347
1376
  c = @c1.load(:id=>123)
1348
1377
  def p._remove_attribute(x)
@@ -79,10 +79,11 @@ describe "Model#serialize" do
79
79
 
80
80
  @c.create(:abc => 1)
81
81
  @c.create(:abc => "hello")
82
+ x = [Marshal.dump("hello")].pack('m')
82
83
 
83
84
  MODEL_DB.sqls.should == [ \
84
85
  "INSERT INTO items (abc) VALUES ('BAhpBg==\n')", \
85
- "INSERT INTO items (abc) VALUES ('BAgiCmhlbGxv\n')", \
86
+ "INSERT INTO items (abc) VALUES ('#{x}')", \
86
87
  ]
87
88
  end
88
89
 
@@ -56,7 +56,19 @@ describe Sequel::Model::DatasetMethods, "#to_hash" do
56
56
  h.should be_a_kind_of(Hash)
57
57
  a = h.to_a
58
58
  a.collect{|x| x[1].class}.should == [@c, @c]
59
- [[[1, {:name=>1}], [2, {:name=>2}]], [[2, {:name=>2}], [1, {:name=>1}]]].should(include(a.collect{|x| [x[0], x[1].values]}))
59
+ a.sort_by{|x| x[0]}.collect{|x| [x[0], x[1].values]}.should == [[1, {:name=>1}], [2, {:name=>2}]]
60
+ end
61
+
62
+ it "should result in a hash with given value keys and model object values" do
63
+ def @d.fetch_rows(sql)
64
+ yield({:name=>1, :number=>3})
65
+ yield({:name=>2, :number=>4})
66
+ end
67
+ h = @d.to_hash(:number)
68
+ h.should be_a_kind_of(Hash)
69
+ a = h.to_a
70
+ a.collect{|x| x[1].class}.should == [@c, @c]
71
+ a.sort_by{|x| x[0]}.collect{|x| [x[0], x[1].values]}.should == [[3, {:name=>1, :number=>3}], [4, {:name=>2, :number=>4}]]
60
72
  end
61
73
 
62
74
  it "should raise an error if the class doesn't have a primary key" do
@@ -4,7 +4,7 @@ describe Sequel::Model, "#eager" do
4
4
  before(:each) do
5
5
  MODEL_DB.reset
6
6
 
7
- class EagerAlbum < Sequel::Model(:albums)
7
+ class ::EagerAlbum < Sequel::Model(:albums)
8
8
  columns :id, :band_id
9
9
  many_to_one :band, :class=>'EagerBand', :key=>:band_id
10
10
  one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id
@@ -17,7 +17,7 @@ describe Sequel::Model, "#eager" do
17
17
  many_to_many :genre_names, :class=>'EagerGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :select=>[:id]
18
18
  end
19
19
 
20
- class EagerBand < Sequel::Model(:bands)
20
+ class ::EagerBand < Sequel::Model(:bands)
21
21
  columns :id
22
22
  one_to_many :albums, :class=>'EagerAlbum', :key=>:band_id, :eager=>:tracks
23
23
  one_to_many :graph_albums, :class=>'EagerAlbum', :key=>:band_id, :eager_graph=>:tracks
@@ -32,17 +32,17 @@ describe Sequel::Model, "#eager" do
32
32
  one_to_many :top_10_albums, :class=>'EagerAlbum', :key=>:band_id, :limit=>10
33
33
  end
34
34
 
35
- class EagerTrack < Sequel::Model(:tracks)
35
+ class ::EagerTrack < Sequel::Model(:tracks)
36
36
  columns :id, :album_id
37
37
  many_to_one :album, :class=>'EagerAlbum', :key=>:album_id
38
38
  end
39
39
 
40
- class EagerGenre < Sequel::Model(:genres)
40
+ class ::EagerGenre < Sequel::Model(:genres)
41
41
  columns :id
42
42
  many_to_many :albums, :class=>'EagerAlbum', :left_key=>:genre_id, :right_key=>:album_id, :join_table=>:ag
43
43
  end
44
44
 
45
- class EagerBandMember < Sequel::Model(:members)
45
+ class ::EagerBandMember < Sequel::Model(:members)
46
46
  columns :id
47
47
  many_to_many :bands, :class=>'EagerBand', :left_key=>:member_id, :right_key=>:band_id, :join_table=>:bm, :order =>:id
48
48
  end
@@ -106,6 +106,10 @@ describe Sequel::Model, "#eager" do
106
106
  })
107
107
  end
108
108
 
109
+ it "should raise an error if called without a symbol or hash" do
110
+ proc{EagerAlbum.eager(Object.new)}.should raise_error(Sequel::Error)
111
+ end
112
+
109
113
  it "should eagerly load a single many_to_one association" do
110
114
  a = EagerAlbum.eager(:band).all
111
115
  a.should be_a_kind_of(Array)
@@ -149,7 +153,7 @@ describe Sequel::Model, "#eager" do
149
153
  MODEL_DB.sqls.length.should == 2
150
154
  end
151
155
 
152
- it "should eagerly load multiple associations" do
156
+ it "should eagerly load multiple associations in a single call" do
153
157
  a = EagerAlbum.eager(:genres, :tracks, :band).all
154
158
  a.should be_a_kind_of(Array)
155
159
  a.size.should == 1
@@ -174,6 +178,31 @@ describe Sequel::Model, "#eager" do
174
178
  MODEL_DB.sqls.length.should == 4
175
179
  end
176
180
 
181
+ it "should eagerly load multiple associations in separate calls" do
182
+ a = EagerAlbum.eager(:genres).eager(:tracks).eager(:band).all
183
+ a.should be_a_kind_of(Array)
184
+ a.size.should == 1
185
+ a.first.should be_a_kind_of(EagerAlbum)
186
+ a.first.values.should == {:id => 1, :band_id => 2}
187
+ MODEL_DB.sqls.length.should == 4
188
+ MODEL_DB.sqls[0].should == 'SELECT * FROM albums'
189
+ MODEL_DB.sqls[1..-1].should(include('SELECT * FROM bands WHERE (bands.id IN (2))'))
190
+ MODEL_DB.sqls[1..-1].should(include('SELECT * FROM tracks WHERE (tracks.album_id IN (1))'))
191
+ MODEL_DB.sqls[1..-1].should(include('SELECT genres.*, 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)))'))
192
+ a = a.first
193
+ a.band.should be_a_kind_of(EagerBand)
194
+ a.band.values.should == {:id => 2}
195
+ a.tracks.should be_a_kind_of(Array)
196
+ a.tracks.size.should == 1
197
+ a.tracks.first.should be_a_kind_of(EagerTrack)
198
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
199
+ a.genres.should be_a_kind_of(Array)
200
+ a.genres.size.should == 1
201
+ a.genres.first.should be_a_kind_of(EagerGenre)
202
+ a.genres.first.values.should == {:id => 4}
203
+ MODEL_DB.sqls.length.should == 4
204
+ end
205
+
177
206
  it "should allow cascading of eager loading for associations of associated models" do
178
207
  a = EagerTrack.eager(:album=>{:band=>:members}).all
179
208
  a.should be_a_kind_of(Array)
@@ -430,13 +459,13 @@ end
430
459
 
431
460
  describe Sequel::Model, "#eager_graph" do
432
461
  after(:all) do
433
- class MockDataset
462
+ class ::MockDataset
434
463
  alias clone orig_clone
435
464
  end
436
465
  end
437
466
 
438
467
  before(:all) do
439
- class MockDataset
468
+ class ::MockDataset
440
469
  alias orig_clone clone
441
470
  def clone(opts = {})
442
471
  c = super()
@@ -446,15 +475,16 @@ describe Sequel::Model, "#eager_graph" do
446
475
  end
447
476
  end
448
477
 
449
- class GraphAlbum < Sequel::Model(:albums)
478
+ class ::GraphAlbum < Sequel::Model(:albums)
450
479
  dataset.opts[:from] = [:albums]
451
480
  columns :id, :band_id
452
481
  many_to_one :band, :class=>'GraphBand', :key=>:band_id
453
482
  one_to_many :tracks, :class=>'GraphTrack', :key=>:album_id
454
483
  many_to_many :genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag
484
+ many_to_one :previous_album, :class=>'GraphAlbum'
455
485
  end
456
486
 
457
- class GraphBand < Sequel::Model(:bands)
487
+ class ::GraphBand < Sequel::Model(:bands)
458
488
  dataset.opts[:from] = [:bands]
459
489
  columns :id, :vocalist_id
460
490
  many_to_one :vocalist, :class=>'GraphBandMember', :key=>:vocalist_id
@@ -463,25 +493,29 @@ describe Sequel::Model, "#eager_graph" do
463
493
  many_to_many :genres, :class=>'GraphGenre', :left_key=>:band_id, :right_key=>:genre_id, :join_table=>:bg
464
494
  end
465
495
 
466
- class GraphTrack < Sequel::Model(:tracks)
496
+ class ::GraphTrack < Sequel::Model(:tracks)
467
497
  dataset.opts[:from] = [:tracks]
468
498
  columns :id, :album_id
469
499
  many_to_one :album, :class=>'GraphAlbum', :key=>:album_id
470
500
  end
471
501
 
472
- class GraphGenre < Sequel::Model(:genres)
502
+ class ::GraphGenre < Sequel::Model(:genres)
473
503
  dataset.opts[:from] = [:genres]
474
504
  columns :id
475
505
  many_to_many :albums, :class=>'GraphAlbum', :left_key=>:genre_id, :right_key=>:album_id, :join_table=>:ag
476
506
  end
477
507
 
478
- class GraphBandMember < Sequel::Model(:members)
508
+ class ::GraphBandMember < Sequel::Model(:members)
479
509
  dataset.opts[:from] = [:members]
480
510
  columns :id
481
511
  many_to_many :bands, :class=>'GraphBand', :left_key=>:member_id, :right_key=>:band_id, :join_table=>:bm
482
512
  end
483
513
  end
484
514
 
515
+ it "should raise an error if called without a symbol or hash" do
516
+ proc{GraphAlbum.eager_graph(Object.new)}.should raise_error(Sequel::Error)
517
+ end
518
+
485
519
  it "should eagerly load a single many_to_one association" do
486
520
  ds = GraphAlbum.eager_graph(:band)
487
521
  ds.sql.should == 'SELECT albums.id, albums.band_id, band.id AS band_id_0, band.vocalist_id FROM albums LEFT OUTER JOIN bands AS band ON (band.id = albums.band_id)'
@@ -534,7 +568,7 @@ describe Sequel::Model, "#eager_graph" do
534
568
  a.genres.first.values.should == {:id => 4}
535
569
  end
536
570
 
537
- it "should eagerly load multiple associations" do
571
+ it "should eagerly load multiple associations in a single call" do
538
572
  ds = GraphAlbum.eager_graph(:genres, :tracks, :band)
539
573
  ds.sql.should == 'SELECT albums.id, albums.band_id, genres.id AS genres_id, tracks.id AS tracks_id, tracks.album_id, band.id AS band_id_0, band.vocalist_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres ON (genres.id = ag.genre_id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id) LEFT OUTER JOIN bands AS band ON (band.id = albums.band_id)'
540
574
  def ds.fetch_rows(sql, &block)
@@ -558,6 +592,30 @@ describe Sequel::Model, "#eager_graph" do
558
592
  a.genres.first.values.should == {:id => 4}
559
593
  end
560
594
 
595
+ it "should eagerly load multiple associations in separate calls" do
596
+ ds = GraphAlbum.eager_graph(:genres).eager_graph(:tracks).eager_graph(:band)
597
+ ds.sql.should == 'SELECT albums.id, albums.band_id, genres.id AS genres_id, tracks.id AS tracks_id, tracks.album_id, band.id AS band_id_0, band.vocalist_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres ON (genres.id = ag.genre_id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id) LEFT OUTER JOIN bands AS band ON (band.id = albums.band_id)'
598
+ def ds.fetch_rows(sql, &block)
599
+ yield({:id=>1, :band_id=>2, :genres_id=>4, :tracks_id=>3, :album_id=>1, :band_id_0=>2, :vocalist_id=>6})
600
+ end
601
+ a = ds.all
602
+ a.should be_a_kind_of(Array)
603
+ a.size.should == 1
604
+ a.first.should be_a_kind_of(GraphAlbum)
605
+ a.first.values.should == {:id => 1, :band_id => 2}
606
+ a = a.first
607
+ a.band.should be_a_kind_of(GraphBand)
608
+ a.band.values.should == {:id => 2, :vocalist_id=>6}
609
+ a.tracks.should be_a_kind_of(Array)
610
+ a.tracks.size.should == 1
611
+ a.tracks.first.should be_a_kind_of(GraphTrack)
612
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
613
+ a.genres.should be_a_kind_of(Array)
614
+ a.genres.size.should == 1
615
+ a.genres.first.should be_a_kind_of(GraphGenre)
616
+ a.genres.first.values.should == {:id => 4}
617
+ end
618
+
561
619
  it "should allow cascading of eager loading for associations of associated models" do
562
620
  ds = GraphTrack.eager_graph(:album=>{:band=>:members})
563
621
  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 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)'
@@ -900,4 +958,7 @@ describe Sequel::Model, "#eager_graph" do
900
958
  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 ON (active) LEFT OUTER JOIN genres AS active_genres ON ((price + 2) > 100)"
901
959
  end
902
960
 
961
+ it "should create unique table aliases for all associations" do
962
+ 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
+ end
903
964
  end