viking-sequel 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. metadata +346 -0
@@ -0,0 +1,153 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe "Sequel::Plugins::LazyAttributes" do
4
+ before do
5
+ class ::LazyAttributesModel < Sequel::Model(:la)
6
+ plugin :lazy_attributes
7
+ set_columns([:id, :name])
8
+ meta_def(:columns){[:id, :name]}
9
+ lazy_attributes :name
10
+ meta_def(:columns){[:id]}
11
+ ds = dataset
12
+ def ds.fetch_rows(sql)
13
+ execute(sql)
14
+ select = @opts[:select]
15
+ where = @opts[:where]
16
+ block = @mod_block || proc{|s| s}
17
+ if !where
18
+ if select.include?(:name)
19
+ yield(block[:id=>1, :name=>'1'])
20
+ yield(block[:id=>2, :name=>'2'])
21
+ else
22
+ yield(:id=>1)
23
+ yield(:id=>2)
24
+ end
25
+ else
26
+ i = where.args.last
27
+ i = i.instance_variable_get(:@array) if i.is_a?(Sequel::SQL::SQLArray)
28
+ Array(i).each do |x|
29
+ if sql =~ /SELECT name FROM/
30
+ yield(block[:name=>x.to_s])
31
+ else
32
+ yield(block[:id=>x, :name=>x.to_s])
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ @c = ::LazyAttributesModel
39
+ @ds = LazyAttributesModel.dataset
40
+ MODEL_DB.reset
41
+ end
42
+ after do
43
+ Object.send(:remove_const, :LazyAttributesModel)
44
+ end
45
+
46
+ it "should allowing adding additional lazy attributes via plugin :lazy_attributes" do
47
+ @c.set_dataset(@ds.select(:id, :blah))
48
+ @c.dataset.sql.should == 'SELECT id, blah FROM la'
49
+ @c.plugin :lazy_attributes, :blah
50
+ @c.dataset.opts[:select].should == [:id]
51
+ @c.dataset.sql.should == 'SELECT id FROM la'
52
+ end
53
+
54
+ it "should allowing adding additional lazy attributes via lazy_attributes" do
55
+ @c.set_dataset(@ds.select(:id, :blah))
56
+ @c.dataset.sql.should == 'SELECT id, blah FROM la'
57
+ @c.lazy_attributes :blah
58
+ @c.dataset.opts[:select].should == [:id]
59
+ @c.dataset.sql.should == 'SELECT id FROM la'
60
+ end
61
+
62
+ it "should remove the attributes given from the SELECT columns of the model's dataset" do
63
+ @ds.opts[:select].should == [:id]
64
+ @ds.sql.should == 'SELECT id FROM la'
65
+ end
66
+
67
+ it "should lazily load the attribute for a single model object if there is an active identity map" do
68
+ @c.with_identity_map do
69
+ m = @c.first
70
+ m.values.should == {:id=>1}
71
+ m.name.should == '1'
72
+ m.values.should == {:id=>1, :name=>'1'}
73
+ MODEL_DB.sqls.should == ['SELECT id FROM la LIMIT 1', 'SELECT name FROM la WHERE (id = 1) LIMIT 1']
74
+ end
75
+ end
76
+
77
+ it "should lazily load the attribute for a single model object if there is no active identity map" do
78
+ m = @c.first
79
+ m.values.should == {:id=>1}
80
+ m.name.should == '1'
81
+ m.values.should == {:id=>1, :name=>'1'}
82
+ MODEL_DB.sqls.should == ['SELECT id FROM la LIMIT 1', 'SELECT name FROM la WHERE (id = 1) LIMIT 1']
83
+ end
84
+
85
+ it "should not lazily load the attribute for a single model object if the value already exists" do
86
+ @c.with_identity_map do
87
+ m = @c.first
88
+ m.values.should == {:id=>1}
89
+ m[:name] = '1'
90
+ m.name.should == '1'
91
+ m.values.should == {:id=>1, :name=>'1'}
92
+ MODEL_DB.sqls.should == ['SELECT id FROM la LIMIT 1']
93
+ end
94
+ end
95
+
96
+ it "should not lazily load the attribute for a single model object if it is a new record" do
97
+ @c.with_identity_map do
98
+ m = @c.new
99
+ m.values.should == {}
100
+ m.name.should == nil
101
+ MODEL_DB.sqls.should == []
102
+ end
103
+ end
104
+
105
+ it "should eagerly load the attribute for all model objects reteived with it" do
106
+ @c.with_identity_map do
107
+ ms = @c.all
108
+ ms.map{|m| m.values}.should == [{:id=>1}, {:id=>2}]
109
+ ms.map{|m| m.name}.should == %w'1 2'
110
+ ms.map{|m| m.values}.should == [{:id=>1, :name=>'1'}, {:id=>2, :name=>'2'}]
111
+ MODEL_DB.sqls.should == ['SELECT id FROM la', 'SELECT id, name FROM la WHERE (id IN (1, 2))']
112
+ end
113
+ end
114
+
115
+ it "should add the accessors to a module included in the class, so they can be easily overridden" do
116
+ @c.class_eval do
117
+ def name
118
+ "#{super}-blah"
119
+ end
120
+ end
121
+ @c.with_identity_map do
122
+ ms = @c.all
123
+ ms.map{|m| m.values}.should == [{:id=>1}, {:id=>2}]
124
+ ms.map{|m| m.name}.should == %w'1-blah 2-blah'
125
+ ms.map{|m| m.values}.should == [{:id=>1, :name=>'1'}, {:id=>2, :name=>'2'}]
126
+ MODEL_DB.sqls.should == ['SELECT id FROM la', 'SELECT id, name FROM la WHERE (id IN (1, 2))']
127
+ end
128
+ end
129
+
130
+ it "should work with the serialization plugin" do
131
+ @c.plugin :serialization, :yaml, :name
132
+ @ds.instance_variable_set(:@mod_block, proc{|s| s.merge(:name=>"--- #{s[:name].to_i*3}\n")})
133
+ @c.with_identity_map do
134
+ ms = @ds.all
135
+ ms.map{|m| m.values}.should == [{:id=>1}, {:id=>2}]
136
+ ms.map{|m| m.name}.should == [3,6]
137
+ ms.map{|m| m.values}.should == [{:id=>1, :name=>"--- 3\n"}, {:id=>2, :name=>"--- 6\n"}]
138
+ ms.map{|m| m.deserialized_values}.should == [{:name=>3}, {:name=>6}]
139
+ ms.map{|m| m.name}.should == [3,6]
140
+ MODEL_DB.sqls.should == ['SELECT id FROM la', 'SELECT id, name FROM la WHERE (id IN (1, 2))']
141
+ end
142
+ MODEL_DB.reset
143
+ @c.with_identity_map do
144
+ m = @ds.first
145
+ m.values.should == {:id=>1}
146
+ m.name.should == 3
147
+ m.values.should == {:id=>1, :name=>"--- 3\n"}
148
+ m.deserialized_values.should == {:name=>3}
149
+ m.name.should == 3
150
+ MODEL_DB.sqls.should == ["SELECT id FROM la LIMIT 1", "SELECT name FROM la WHERE (id = 1) LIMIT 1"]
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,39 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ context "LooserTypecasting Extension" do
4
+ before do
5
+ @db = Sequel::Database.new({})
6
+ def @db.schema(*args)
7
+ [[:id, {}], [:y, {:type=>:float}], [:b, {:type=>:integer}]]
8
+ end
9
+ @c = Class.new(Sequel::Model(@db[:items]))
10
+ @c.instance_eval do
11
+ @columns = [:id, :b, :y]
12
+ def columns; @columns; end
13
+ end
14
+ end
15
+
16
+ specify "Should use to_i instead of Integer() for typecasting integers" do
17
+ proc{@c.new(:b=>'a')}.should raise_error(Sequel::InvalidValue)
18
+ @db.extend(Sequel::LooserTypecasting)
19
+ @c.new(:b=>'a').b.should == 0
20
+
21
+ o = Object.new
22
+ def o.to_i
23
+ 1
24
+ end
25
+ @c.new(:b=>o).b.should == 1
26
+ end
27
+
28
+ specify "Should use to_f instead of Float() for typecasting floats" do
29
+ proc{@c.new(:y=>'a')}.should raise_error(Sequel::InvalidValue)
30
+ @db.extend(Sequel::LooserTypecasting)
31
+ @c.new(:y=>'a').y.should == 0.0
32
+
33
+ o = Object.new
34
+ def o.to_f
35
+ 1.0
36
+ end
37
+ @c.new(:y=>o).y.should == 1.0
38
+ end
39
+ end
@@ -0,0 +1,884 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe Sequel::Model, "many_through_many" do
4
+ before do
5
+ class ::Artist < Sequel::Model
6
+ attr_accessor :yyy
7
+ columns :id
8
+ plugin :many_through_many
9
+ end
10
+ class ::Tag < Sequel::Model
11
+ end
12
+ MODEL_DB.reset
13
+ @c1 = Artist
14
+ @c2 = Tag
15
+ @dataset = @c2.dataset
16
+ def @dataset.fetch_rows(sql)
17
+ @db << sql
18
+ yield({:id=>1})
19
+ end
20
+ end
21
+ after do
22
+ Object.send(:remove_const, :Artist)
23
+ Object.send(:remove_const, :Tag)
24
+ end
25
+
26
+ it "should default to associating to other models in the same scope" do
27
+ class ::AssociationModuleTest
28
+ class Artist < Sequel::Model
29
+ plugin :many_through_many
30
+ many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
31
+ end
32
+ class Tag < Sequel::Model
33
+ end
34
+ end
35
+
36
+ ::AssociationModuleTest::Artist.association_reflection(:tags).associated_class.should == ::AssociationModuleTest::Tag
37
+ end
38
+
39
+ it "should raise an error if in invalid form of through is used" do
40
+ proc{@c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id]]}.should raise_error(Sequel::Error)
41
+ proc{@c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], {:table=>:album_tags, :left=>:album_id}]}.should raise_error(Sequel::Error)
42
+ proc{@c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], :album_tags]}.should raise_error(Sequel::Error)
43
+ end
44
+
45
+ it "should use join tables given" do
46
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
47
+ n = @c1.load(:id => 1234)
48
+ a = n.tags_dataset
49
+ a.should be_a_kind_of(Sequel::Dataset)
50
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234))'
51
+ n.tags.should == [@c2.load(:id=>1)]
52
+ end
53
+
54
+ it "should handle multiple aliasing of tables" do
55
+ class ::Album < Sequel::Model
56
+ end
57
+ @c1.many_through_many :albums, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_artists, :album_id, :artist_id], [:artists, :id, :id], [:albums_artists, :artist_id, :album_id]]
58
+ n = @c1.load(:id => 1234)
59
+ a = n.albums_dataset
60
+ a.should be_a_kind_of(Sequel::Dataset)
61
+ a.sql.should == 'SELECT albums.* FROM albums INNER JOIN albums_artists ON (albums_artists.album_id = albums.id) INNER JOIN artists ON (artists.id = albums_artists.artist_id) INNER JOIN albums_artists AS albums_artists_0 ON (albums_artists_0.artist_id = artists.id) INNER JOIN albums AS albums_0 ON (albums_0.id = albums_artists_0.album_id) INNER JOIN albums_artists AS albums_artists_1 ON ((albums_artists_1.album_id = albums_0.id) AND (albums_artists_1.artist_id = 1234))'
62
+ n.albums.should == [Album.load(:id=>1, :x=>1)]
63
+ Object.send(:remove_const, :Album)
64
+ end
65
+
66
+ it "should use explicit class if given" do
67
+ @c1.many_through_many :albums_tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :class=>Tag
68
+ n = @c1.load(:id => 1234)
69
+ a = n.albums_tags_dataset
70
+ a.should be_a_kind_of(Sequel::Dataset)
71
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234))'
72
+ n.albums_tags.should == [@c2.load(:id=>1)]
73
+ end
74
+
75
+ it "should accept :left_primary_key and :right_primary_key option for primary keys to use in current and associated table" do
76
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :right_primary_key=>:tag_id, :left_primary_key=>:yyy
77
+ n = @c1.load(:id => 1234)
78
+ n.yyy = 85
79
+ a = n.tags_dataset
80
+ a.should be_a_kind_of(Sequel::Dataset)
81
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.tag_id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 85))'
82
+ n.tags.should == [@c2.load(:id=>1)]
83
+ end
84
+
85
+ it "should handle composite keys" do
86
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
87
+ n = @c1.load(:id => 1234)
88
+ n.yyy = 85
89
+ a = n.tags_dataset
90
+ a.should be_a_kind_of(Sequel::Dataset)
91
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.g1 = tags.h1) AND (albums_tags.g2 = tags.h2)) INNER JOIN albums ON ((albums.e1 = albums_tags.f1) AND (albums.e2 = albums_tags.f2)) INNER JOIN albums_artists ON ((albums_artists.c1 = albums.d1) AND (albums_artists.c2 = albums.d2) AND (albums_artists.b1 = 1234) AND (albums_artists.b2 = 85))'
92
+ n.tags.should == [@c2.load(:id=>1)]
93
+ end
94
+
95
+ it "should support a :conditions option" do
96
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :conditions=>{:a=>32}
97
+ n = @c1.load(:id => 1234)
98
+ a = n.tags_dataset
99
+ a.should be_a_kind_of(Sequel::Dataset)
100
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234)) WHERE (a = 32)'
101
+ n.tags.should == [@c2.load(:id=>1)]
102
+
103
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :conditions=>['a = ?', 42]
104
+ n = @c1.load(:id => 1234)
105
+ a = n.tags_dataset
106
+ a.should be_a_kind_of(Sequel::Dataset)
107
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234)) WHERE (a = 42)'
108
+ n.tags.should == [@c2.load(:id=>1)]
109
+ end
110
+
111
+ it "should support an :order option" do
112
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :order=>:blah
113
+ n = @c1.load(:id => 1234)
114
+ a = n.tags_dataset
115
+ a.should be_a_kind_of(Sequel::Dataset)
116
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234)) ORDER BY blah'
117
+ n.tags.should == [@c2.load(:id=>1)]
118
+ end
119
+
120
+ it "should support an array for the :order option" do
121
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :order=>[:blah1, :blah2]
122
+ n = @c1.load(:id => 1234)
123
+ a = n.tags_dataset
124
+ a.should be_a_kind_of(Sequel::Dataset)
125
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234)) ORDER BY blah1, blah2'
126
+ n.tags.should == [@c2.load(:id=>1)]
127
+ end
128
+
129
+ it "should support a select option" do
130
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :select=>:blah
131
+ n = @c1.load(:id => 1234)
132
+ a = n.tags_dataset
133
+ a.should be_a_kind_of(Sequel::Dataset)
134
+ a.sql.should == 'SELECT blah FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234))'
135
+ n.tags.should == [@c2.load(:id=>1)]
136
+ end
137
+
138
+ it "should support an array for the select option" do
139
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :select=>[:tags.*, :albums__name]
140
+ n = @c1.load(:id => 1234)
141
+ a = n.tags_dataset
142
+ a.should be_a_kind_of(Sequel::Dataset)
143
+ a.sql.should == 'SELECT tags.*, albums.name FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234))'
144
+ n.tags.should == [@c2.load(:id=>1)]
145
+ end
146
+
147
+ it "should accept a block" do
148
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]] do |ds| ds.filter(:yyy=>@yyy) end
149
+ n = @c1.load(:id => 1234)
150
+ n.yyy = 85
151
+ a = n.tags_dataset
152
+ a.should be_a_kind_of(Sequel::Dataset)
153
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234)) WHERE (yyy = 85)'
154
+ n.tags.should == [@c2.load(:id=>1)]
155
+ end
156
+
157
+ it "should allow the :order option while accepting a block" do
158
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :order=>:blah do |ds| ds.filter(:yyy=>@yyy) end
159
+ n = @c1.load(:id => 1234)
160
+ n.yyy = 85
161
+ a = n.tags_dataset
162
+ a.should be_a_kind_of(Sequel::Dataset)
163
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234)) WHERE (yyy = 85) ORDER BY blah'
164
+ n.tags.should == [@c2.load(:id=>1)]
165
+ end
166
+
167
+ it "should support a :dataset option that is used instead of the default" do
168
+ @c1.many_through_many :tags, [[:a, :b, :c]], :dataset=>proc{Tag.join(:albums_tags, [:tag_id]).join(:albums, [:album_id]).join(:albums_artists, [:album_id]).filter(:albums_artists__artist_id=>id)}
169
+ n = @c1.load(:id => 1234)
170
+ a = n.tags_dataset
171
+ a.should be_a_kind_of(Sequel::Dataset)
172
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags USING (tag_id) INNER JOIN albums USING (album_id) INNER JOIN albums_artists USING (album_id) WHERE (albums_artists.artist_id = 1234)'
173
+ n.tags.should == [@c2.load(:id=>1)]
174
+ end
175
+
176
+ it "should support a :limit option" do
177
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :limit=>10
178
+ n = @c1.load(:id => 1234)
179
+ a = n.tags_dataset
180
+ a.should be_a_kind_of(Sequel::Dataset)
181
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234)) LIMIT 10'
182
+ n.tags.should == [@c2.load(:id=>1)]
183
+
184
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :limit=>[10, 10]
185
+ n = @c1.load(:id => 1234)
186
+ a = n.tags_dataset
187
+ a.should be_a_kind_of(Sequel::Dataset)
188
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234)) LIMIT 10 OFFSET 10'
189
+ n.tags.should == [@c2.load(:id=>1)]
190
+ end
191
+
192
+ it "should have the :eager option affect the _dataset method" do
193
+ @c2.many_to_many :fans
194
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :eager=>:fans
195
+ @c1.load(:id => 1234).tags_dataset.opts[:eager].should == {:fans=>nil}
196
+ end
197
+
198
+ it "should provide an array with all members of the association" do
199
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
200
+ @c1.load(:id => 1234).tags.should == [@c2.load(:id=>1)]
201
+ MODEL_DB.sqls.should == ['SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234))']
202
+ end
203
+
204
+ it "should set cached instance variable when accessed" do
205
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
206
+ n = @c1.load(:id => 1234)
207
+ n.associations[:tags].should == nil
208
+ MODEL_DB.sqls.should == []
209
+ n.tags.should == [@c2.load(:id=>1)]
210
+ MODEL_DB.sqls.should == ['SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234))']
211
+ n.associations[:tags].should == n.tags
212
+ MODEL_DB.sqls.length.should == 1
213
+ end
214
+
215
+ it "should use cached instance variable if available" do
216
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
217
+ n = @c1.load(:id => 1234)
218
+ n.associations[:tags] = []
219
+ n.tags.should == []
220
+ MODEL_DB.sqls.should == []
221
+ end
222
+
223
+ it "should not use cached instance variable if asked to reload" do
224
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
225
+ n = @c1.load(:id => 1234)
226
+ n.associations[:tags] = []
227
+ MODEL_DB.sqls.should == []
228
+ n.tags(true).should == [@c2.load(:id=>1)]
229
+ MODEL_DB.sqls.should == ['SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234))']
230
+ n.associations[:tags].should == n.tags
231
+ MODEL_DB.sqls.length.should == 1
232
+ end
233
+
234
+ it "should not add associations methods directly to class" do
235
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
236
+ im = @c1.instance_methods.collect{|x| x.to_s}
237
+ im.should(include('tags'))
238
+ im.should(include('tags_dataset'))
239
+ im2 = @c1.instance_methods(false).collect{|x| x.to_s}
240
+ im2.should_not(include('tags'))
241
+ im2.should_not(include('tags_dataset'))
242
+ end
243
+
244
+ it "should support after_load association callback" do
245
+ h = []
246
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :after_load=>:al
247
+ @c1.class_eval do
248
+ @@blah = h
249
+ def al(v)
250
+ v.each{|x| @@blah << x.pk * 20}
251
+ end
252
+ end
253
+ @c2.class_eval do
254
+ def @dataset.fetch_rows(sql)
255
+ yield({:id=>20})
256
+ yield({:id=>30})
257
+ end
258
+ end
259
+ p = @c1.load(:id=>10, :parent_id=>20)
260
+ p.tags
261
+ h.should == [400, 600]
262
+ p.tags.collect{|a| a.pk}.should == [20, 30]
263
+ end
264
+
265
+ it "should support a :uniq option that removes duplicates from the association" do
266
+ h = []
267
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :uniq=>true
268
+ @c2.class_eval do
269
+ def @dataset.fetch_rows(sql)
270
+ yield({:id=>20})
271
+ yield({:id=>30})
272
+ yield({:id=>20})
273
+ yield({:id=>30})
274
+ end
275
+ end
276
+ @c1.load(:id=>10).tags.should == [@c2.load(:id=>20), @c2.load(:id=>30)]
277
+ end
278
+ end
279
+
280
+ describe 'Sequel::Plugins::ManyThroughMany::ManyThroughManyAssociationReflection' do
281
+ before do
282
+ class ::Artist < Sequel::Model
283
+ plugin :many_through_many
284
+ many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
285
+ end
286
+ class ::Tag < Sequel::Model
287
+ end
288
+ MODEL_DB.reset
289
+ @ar = Artist.association_reflection(:tags)
290
+ end
291
+ after do
292
+ Object.send(:remove_const, :Artist)
293
+ Object.send(:remove_const, :Tag)
294
+ end
295
+
296
+ it "#edges should be an array of joins to make when eager graphing" do
297
+ @ar.edges.should == [{:conditions=>[], :left=>:id, :right=>:artist_id, :table=>:albums_artists, :join_type=>:left_outer, :block=>nil}, {:conditions=>[], :left=>:album_id, :right=>:id, :table=>:albums, :join_type=>:left_outer, :block=>nil}, {:conditions=>[], :left=>:id, :right=>:album_id, :table=>:albums_tags, :join_type=>:left_outer, :block=>nil}]
298
+ end
299
+
300
+ it "#edges should handle composite keys" do
301
+ Artist.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
302
+ Artist.association_reflection(:tags).edges.should == [{:conditions=>[], :left=>[:id, :yyy], :right=>[:b1, :b2], :table=>:albums_artists, :join_type=>:left_outer, :block=>nil}, {:conditions=>[], :left=>[:c1, :c2], :right=>[:d1, :d2], :table=>:albums, :join_type=>:left_outer, :block=>nil}, {:conditions=>[], :left=>[:e1, :e2], :right=>[:f1, :f2], :table=>:albums_tags, :join_type=>:left_outer, :block=>nil}]
303
+ end
304
+
305
+ it "#reverse_edges should be an array of joins to make when lazy loading or eager loading" do
306
+ @ar.reverse_edges.should == [{:alias=>:albums_tags, :left=>:tag_id, :right=>:id, :table=>:albums_tags}, {:alias=>:albums, :left=>:id, :right=>:album_id, :table=>:albums}]
307
+ end
308
+
309
+ it "#reverse_edges should handle composite keys" do
310
+ Artist.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
311
+ Artist.association_reflection(:tags).reverse_edges.should == [{:alias=>:albums_tags, :left=>[:g1, :g2], :right=>[:h1, :h2], :table=>:albums_tags}, {:alias=>:albums, :left=>[:e1, :e2], :right=>[:f1, :f2], :table=>:albums}]
312
+ end
313
+
314
+ it "#reciprocal should be nil" do
315
+ @ar.reciprocal.should == nil
316
+ end
317
+ end
318
+
319
+ describe "Sequel::Plugins::ManyThroughMany eager loading methods" do
320
+ before do
321
+ class ::Artist < Sequel::Model
322
+ plugin :many_through_many
323
+ many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
324
+ many_through_many :other_tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :class=>:Tag
325
+ many_through_many :albums, [[:albums_artists, :artist_id, :album_id]]
326
+ many_through_many :artists, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_artists, :album_id, :artist_id]]
327
+ end
328
+ class ::Tag < Sequel::Model
329
+ plugin :many_through_many
330
+ many_through_many :tracks, [[:albums_tags, :tag_id, :album_id], [:albums, :id, :id]], :right_primary_key=>:album_id
331
+ end
332
+ class ::Album < Sequel::Model
333
+ end
334
+ class ::Track < Sequel::Model
335
+ end
336
+
337
+ Artist.dataset.extend(Module.new {
338
+ def columns
339
+ [:id]
340
+ end
341
+
342
+ def fetch_rows(sql)
343
+ @db << sql
344
+ h = {:id => 1}
345
+ if sql =~ /FROM artists LEFT OUTER JOIN albums_artists/
346
+ h[:tags_id] = 2
347
+ h[:albums_0_id] = 3 if sql =~ /LEFT OUTER JOIN albums AS albums_0/
348
+ h[:tracks_id] = 4 if sql =~ /LEFT OUTER JOIN tracks/
349
+ h[:other_tags_id] = 9 if sql =~ /other_tags\.id AS other_tags_id/
350
+ h[:artists_0_id] = 10 if sql =~ /artists_0\.id AS artists_0_id/
351
+ end
352
+ yield h
353
+ end
354
+ })
355
+
356
+ Tag.dataset.extend(Module.new {
357
+ def fetch_rows(sql)
358
+ @db << sql
359
+ h = {:id => 2}
360
+ if sql =~ /albums_artists.artist_id IN \(([18])\)/
361
+ h.merge!(:x_foreign_key_x=>$1.to_i)
362
+ elsif sql =~ /\(\(albums_artists.b1, albums_artists.b2\) IN \(\(1, 8\)\)\)/
363
+ h.merge!(:x_foreign_key_0_x=>1, :x_foreign_key_1_x=>8)
364
+ end
365
+ h[:tag_id] = h.delete(:id) if sql =~ /albums_artists.artist_id IN \(8\)/
366
+ yield h
367
+ end
368
+ })
369
+
370
+ Album.dataset.extend(Module.new {
371
+ def fetch_rows(sql)
372
+ @db << sql
373
+ h = {:id => 3}
374
+ h.merge!(:x_foreign_key_x=>1) if sql =~ /albums_artists.artist_id IN \(1\)/
375
+ yield h
376
+ end
377
+ })
378
+
379
+ Track.dataset.extend(Module.new {
380
+ def fetch_rows(sql)
381
+ @db << sql
382
+ h = {:id => 4}
383
+ h.merge!(:x_foreign_key_x=>2) if sql =~ /albums_tags.tag_id IN \(2\)/
384
+ yield h
385
+ end
386
+ })
387
+
388
+ @c1 = Artist
389
+ MODEL_DB.reset
390
+ end
391
+ after do
392
+ [:Artist, :Tag, :Album, :Track].each{|x| Object.send(:remove_const, x)}
393
+ end
394
+
395
+ it "should eagerly load a single many_through_many association" do
396
+ a = @c1.eager(:tags).all
397
+ a.should == [@c1.load(:id=>1)]
398
+ MODEL_DB.sqls.should == ['SELECT * FROM artists', 'SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))']
399
+ a.first.tags.should == [Tag.load(:id=>2)]
400
+ MODEL_DB.sqls.length.should == 2
401
+ end
402
+
403
+ it "should eagerly load multiple associations in a single call" do
404
+ a = @c1.eager(:tags, :albums).all
405
+ a.should == [@c1.load(:id=>1)]
406
+ MODEL_DB.sqls.length.should == 3
407
+ MODEL_DB.sqls[0].should == 'SELECT * FROM artists'
408
+ MODEL_DB.sqls[1..-1].should(include('SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))'))
409
+ MODEL_DB.sqls[1..-1].should(include('SELECT albums.*, albums_artists.artist_id AS x_foreign_key_x FROM albums INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))'))
410
+ a = a.first
411
+ a.tags.should == [Tag.load(:id=>2)]
412
+ a.albums.should == [Album.load(:id=>3)]
413
+ MODEL_DB.sqls.length.should == 3
414
+ end
415
+
416
+ it "should eagerly load multiple associations in separate" do
417
+ a = @c1.eager(:tags).eager(:albums).all
418
+ a.should == [@c1.load(:id=>1)]
419
+ MODEL_DB.sqls.length.should == 3
420
+ MODEL_DB.sqls[0].should == 'SELECT * FROM artists'
421
+ MODEL_DB.sqls[1..-1].should(include('SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))'))
422
+ MODEL_DB.sqls[1..-1].should(include('SELECT albums.*, albums_artists.artist_id AS x_foreign_key_x FROM albums INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))'))
423
+ a = a.first
424
+ a.tags.should == [Tag.load(:id=>2)]
425
+ a.albums.should == [Album.load(:id=>3)]
426
+ MODEL_DB.sqls.length.should == 3
427
+ end
428
+
429
+ it "should allow cascading of eager loading for associations of associated models" do
430
+ a = @c1.eager(:tags=>:tracks).all
431
+ a.should == [@c1.load(:id=>1)]
432
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
433
+ 'SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))',
434
+ 'SELECT tracks.*, albums_tags.tag_id AS x_foreign_key_x FROM tracks INNER JOIN albums ON (albums.id = tracks.album_id) INNER JOIN albums_tags ON ((albums_tags.album_id = albums.id) AND (albums_tags.tag_id IN (2)))']
435
+ a = a.first
436
+ a.tags.should == [Tag.load(:id=>2)]
437
+ a.tags.first.tracks.should == [Track.load(:id=>4)]
438
+ MODEL_DB.sqls.length.should == 3
439
+ end
440
+
441
+ it "should cascade eagerly loading when the :eager association option is used" do
442
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :eager=>:tracks
443
+ a = @c1.eager(:tags).all
444
+ a.should == [@c1.load(:id=>1)]
445
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
446
+ 'SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))',
447
+ 'SELECT tracks.*, albums_tags.tag_id AS x_foreign_key_x FROM tracks INNER JOIN albums ON (albums.id = tracks.album_id) INNER JOIN albums_tags ON ((albums_tags.album_id = albums.id) AND (albums_tags.tag_id IN (2)))']
448
+ a = a.first
449
+ a.tags.should == [Tag.load(:id=>2)]
450
+ a.tags.first.tracks.should == [Track.load(:id=>4)]
451
+ MODEL_DB.sqls.length.should == 3
452
+ end
453
+
454
+ it "should respect :eager when lazily loading an association" do
455
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :eager=>:tracks
456
+ a = @c1.load(:id=>1)
457
+ a.tags.should == [Tag.load(:id=>2)]
458
+ MODEL_DB.sqls.should == ['SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1))',
459
+ 'SELECT tracks.*, albums_tags.tag_id AS x_foreign_key_x FROM tracks INNER JOIN albums ON (albums.id = tracks.album_id) INNER JOIN albums_tags ON ((albums_tags.album_id = albums.id) AND (albums_tags.tag_id IN (2)))']
460
+ a.tags.first.tracks.should == [Track.load(:id=>4)]
461
+ MODEL_DB.sqls.length.should == 2
462
+ end
463
+
464
+ it "should cascade eagerly loading when the :eager_graph association option is used" do
465
+ Tag.dataset.extend(Module.new {
466
+ def columns
467
+ [:id]
468
+ end
469
+ def fetch_rows(sql)
470
+ @db << sql
471
+ yield({:id=>2, :tracks_id=>4, :x_foreign_key_x=>1})
472
+ end
473
+ })
474
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :eager_graph=>:tracks
475
+ a = @c1.eager(:tags).all
476
+ a.should == [@c1.load(:id=>1)]
477
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
478
+ 'SELECT tags.id, tracks.id AS tracks_id, albums_artists.artist_id AS x_foreign_key_x FROM (SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))) AS tags LEFT OUTER JOIN albums_tags AS albums_tags_0 ON (albums_tags_0.tag_id = tags.id) LEFT OUTER JOIN albums ON (albums.id = albums_tags_0.album_id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)']
479
+ a = a.first
480
+ a.tags.should == [Tag.load(:id=>2)]
481
+ a.tags.first.tracks.should == [Track.load(:id=>4)]
482
+ MODEL_DB.sqls.length.should == 2
483
+ end
484
+
485
+ it "should respect :eager_graph when lazily loading an association" do
486
+ Tag.dataset.extend(Module.new {
487
+ def columns
488
+ [:id]
489
+ end
490
+ def fetch_rows(sql)
491
+ @db << sql
492
+ yield({:id=>2, :tracks_id=>4})
493
+ end
494
+ })
495
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :eager_graph=>:tracks
496
+ a = @c1.load(:id=>1)
497
+ a.tags
498
+ MODEL_DB.sqls.should == [ 'SELECT tags.id, tracks.id AS tracks_id FROM (SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1))) AS tags LEFT OUTER JOIN albums_tags AS albums_tags_0 ON (albums_tags_0.tag_id = tags.id) LEFT OUTER JOIN albums ON (albums.id = albums_tags_0.album_id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)']
499
+ a.tags.should == [Tag.load(:id=>2)]
500
+ a.tags.first.tracks.should == [Track.load(:id=>4)]
501
+ MODEL_DB.sqls.length.should == 1
502
+ end
503
+
504
+ it "should respect :conditions when eagerly loading" do
505
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :conditions=>{:a=>32}
506
+ a = @c1.eager(:tags).all
507
+ a.should == [@c1.load(:id=>1)]
508
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
509
+ 'SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1))) WHERE (a = 32)']
510
+ a.first.tags.should == [Tag.load(:id=>2)]
511
+ MODEL_DB.sqls.length.should == 2
512
+ end
513
+
514
+ it "should respect :order when eagerly loading" do
515
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :order=>:blah
516
+ a = @c1.eager(:tags).all
517
+ a.should == [@c1.load(:id=>1)]
518
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
519
+ 'SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1))) ORDER BY blah']
520
+ a.first.tags.should == [Tag.load(:id=>2)]
521
+ MODEL_DB.sqls.length.should == 2
522
+ end
523
+
524
+ it "should use the association's block when eager loading by default" do
525
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]] do |ds| ds.filter(:a) end
526
+ a = @c1.eager(:tags).all
527
+ a.should == [@c1.load(:id=>1)]
528
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
529
+ 'SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1))) WHERE a']
530
+ a.first.tags.should == [Tag.load(:id=>2)]
531
+ MODEL_DB.sqls.length.should == 2
532
+ end
533
+
534
+ it "should use the :eager_block option when eager loading if given" do
535
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :eager_block=>proc{|ds| ds.filter(:b)} do |ds| ds.filter(:a) end
536
+ a = @c1.eager(:tags).all
537
+ a.should == [@c1.load(:id=>1)]
538
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
539
+ 'SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1))) WHERE b']
540
+ a.first.tags.should == [Tag.load(:id=>2)]
541
+ MODEL_DB.sqls.length.should == 2
542
+ end
543
+
544
+ it "should raise an error when attempting to eagerly load an association with the :allow_eager option set to false" do
545
+ proc{@c1.eager(:tags).all}.should_not raise_error
546
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :allow_eager=>false
547
+ proc{@c1.eager(:tags).all}.should raise_error(Sequel::Error)
548
+ end
549
+
550
+ it "should respect the association's :select option" do
551
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :select=>:tags__name
552
+ a = @c1.eager(:tags).all
553
+ a.should == [@c1.load(:id=>1)]
554
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
555
+ 'SELECT tags.name, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))']
556
+ a.first.tags.should == [Tag.load(:id=>2)]
557
+ MODEL_DB.sqls.length.should == 2
558
+ end
559
+
560
+ it "should respect many_to_many association's :left_primary_key and :right_primary_key options" do
561
+ @c1.send(:define_method, :yyy){values[:yyy]}
562
+ @c1.dataset.extend(Module.new {
563
+ def columns
564
+ [:id, :yyy]
565
+ end
566
+ def fetch_rows(sql)
567
+ @db << sql
568
+ yield({:id=>1, :yyy=>8})
569
+ end
570
+ })
571
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :left_primary_key=>:yyy, :right_primary_key=>:tag_id
572
+ a = @c1.eager(:tags).all
573
+ a.should == [@c1.load(:id=>1, :yyy=>8)]
574
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
575
+ 'SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.tag_id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (8)))']
576
+ a.first.tags.should == [Tag.load(:tag_id=>2)]
577
+ MODEL_DB.sqls.length.should == 2
578
+ end
579
+
580
+ it "should handle composite keys" do
581
+ @c1.send(:define_method, :yyy){values[:yyy]}
582
+ @c1.dataset.extend(Module.new {
583
+ def columns
584
+ [:id, :yyy]
585
+ end
586
+ def fetch_rows(sql)
587
+ @db << sql
588
+ yield({:id=>1, :yyy=>8})
589
+ end
590
+ })
591
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
592
+ a = @c1.eager(:tags).all
593
+ a.should == [@c1.load(:id=>1, :yyy=>8)]
594
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
595
+ 'SELECT tags.*, albums_artists.b1 AS x_foreign_key_0_x, albums_artists.b2 AS x_foreign_key_1_x FROM tags INNER JOIN albums_tags ON ((albums_tags.g1 = tags.h1) AND (albums_tags.g2 = tags.h2)) INNER JOIN albums ON ((albums.e1 = albums_tags.f1) AND (albums.e2 = albums_tags.f2)) INNER JOIN albums_artists ON ((albums_artists.c1 = albums.d1) AND (albums_artists.c2 = albums.d2) AND ((albums_artists.b1, albums_artists.b2) IN ((1, 8))))']
596
+ a.first.tags.should == [Tag.load(:id=>2)]
597
+ MODEL_DB.sqls.length.should == 2
598
+ end
599
+
600
+ it "should respect :after_load callbacks on associations when eager loading" do
601
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :after_load=>lambda{|o, as| o[:id] *= 2; as.each{|a| a[:id] *= 3}}
602
+ a = @c1.eager(:tags).all
603
+ a.should == [@c1.load(:id=>2)]
604
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
605
+ 'SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))']
606
+ a.first.tags.should == [Tag.load(:id=>6)]
607
+ MODEL_DB.sqls.length.should == 2
608
+ end
609
+
610
+ it "should raise an error if called without a symbol or hash" do
611
+ proc{@c1.eager_graph(Object.new)}.should raise_error(Sequel::Error)
612
+ end
613
+
614
+ it "should eagerly graph a single many_through_many association" do
615
+ a = @c1.eager_graph(:tags).all
616
+ a.should == [@c1.load(:id=>1)]
617
+ MODEL_DB.sqls.should == ['SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id)']
618
+ a.first.tags.should == [Tag.load(:id=>2)]
619
+ MODEL_DB.sqls.length.should == 1
620
+ end
621
+
622
+ it "should eagerly graph multiple associations in a single call" do
623
+ a = @c1.eager_graph(:tags, :albums).all
624
+ a.should == [@c1.load(:id=>1)]
625
+ MODEL_DB.sqls.should == ['SELECT artists.id, tags.id AS tags_id, albums_0.id AS albums_0_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id) LEFT OUTER JOIN albums_artists AS albums_artists_0 ON (albums_artists_0.artist_id = artists.id) LEFT OUTER JOIN albums AS albums_0 ON (albums_0.id = albums_artists_0.album_id)']
626
+ a = a.first
627
+ a.tags.should == [Tag.load(:id=>2)]
628
+ a.albums.should == [Album.load(:id=>3)]
629
+ MODEL_DB.sqls.length.should == 1
630
+ end
631
+
632
+ it "should eagerly graph multiple associations in separate calls" do
633
+ a = @c1.eager_graph(:tags).eager_graph(:albums).all
634
+ a.should == [@c1.load(:id=>1)]
635
+ MODEL_DB.sqls.should == ['SELECT artists.id, tags.id AS tags_id, albums_0.id AS albums_0_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id) LEFT OUTER JOIN albums_artists AS albums_artists_0 ON (albums_artists_0.artist_id = artists.id) LEFT OUTER JOIN albums AS albums_0 ON (albums_0.id = albums_artists_0.album_id)']
636
+ a = a.first
637
+ a.tags.should == [Tag.load(:id=>2)]
638
+ a.albums.should == [Album.load(:id=>3)]
639
+ MODEL_DB.sqls.length.should == 1
640
+ end
641
+
642
+ it "should allow cascading of eager graphing for associations of associated models" do
643
+ a = @c1.eager_graph(:tags=>:tracks).all
644
+ a.should == [@c1.load(:id=>1)]
645
+ MODEL_DB.sqls.should == ['SELECT artists.id, tags.id AS tags_id, tracks.id AS tracks_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id) LEFT OUTER JOIN albums_tags AS albums_tags_0 ON (albums_tags_0.tag_id = tags.id) LEFT OUTER JOIN albums AS albums_0 ON (albums_0.id = albums_tags_0.album_id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums_0.id)']
646
+ a = a.first
647
+ a.tags.should == [Tag.load(:id=>2)]
648
+ a.tags.first.tracks.should == [Track.load(:id=>4)]
649
+ MODEL_DB.sqls.length.should == 1
650
+ end
651
+
652
+ it "eager graphing should eliminate duplicates caused by cartesian products" do
653
+ ds = @c1.eager_graph(:tags)
654
+ def ds.fetch_rows(sql, &block)
655
+ @db << sql
656
+ # Assume artist has 2 albums each with 2 tags
657
+ yield({:id=>1, :tags_id=>2})
658
+ yield({:id=>1, :tags_id=>3})
659
+ yield({:id=>1, :tags_id=>2})
660
+ yield({:id=>1, :tags_id=>3})
661
+ end
662
+ a = ds.all
663
+ a.should == [@c1.load(:id=>1)]
664
+ MODEL_DB.sqls.should == ['SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id)']
665
+ a.first.tags.should == [Tag.load(:id=>2), Tag.load(:id=>3)]
666
+ MODEL_DB.sqls.length.should == 1
667
+ end
668
+
669
+ it "should eager graph multiple associations from the same table" do
670
+ a = @c1.eager_graph(:tags, :other_tags).all
671
+ a.should == [@c1.load(:id=>1)]
672
+ MODEL_DB.sqls.should == ['SELECT artists.id, tags.id AS tags_id, other_tags.id AS other_tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id) LEFT OUTER JOIN albums_artists AS albums_artists_0 ON (albums_artists_0.artist_id = artists.id) LEFT OUTER JOIN albums AS albums_0 ON (albums_0.id = albums_artists_0.album_id) LEFT OUTER JOIN albums_tags AS albums_tags_0 ON (albums_tags_0.album_id = albums_0.id) LEFT OUTER JOIN tags AS other_tags ON (other_tags.id = albums_tags_0.tag_id)']
673
+ a = a.first
674
+ a.tags.should == [Tag.load(:id=>2)]
675
+ a.other_tags.should == [Tag.load(:id=>9)]
676
+ MODEL_DB.sqls.length.should == 1
677
+ end
678
+
679
+ it "should eager graph a self_referential association" do
680
+ a = @c1.eager_graph(:tags, :artists).all
681
+ a.should == [@c1.load(:id=>1)]
682
+ MODEL_DB.sqls.should == ['SELECT artists.id, tags.id AS tags_id, artists_0.id AS artists_0_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id) LEFT OUTER JOIN albums_artists AS albums_artists_0 ON (albums_artists_0.artist_id = artists.id) LEFT OUTER JOIN albums AS albums_0 ON (albums_0.id = albums_artists_0.album_id) LEFT OUTER JOIN albums_artists AS albums_artists_1 ON (albums_artists_1.album_id = albums_0.id) LEFT OUTER JOIN artists AS artists_0 ON (artists_0.id = albums_artists_1.artist_id)']
683
+ a = a.first
684
+ a.tags.should == [Tag.load(:id=>2)]
685
+ a.artists.should == [@c1.load(:id=>10)]
686
+ MODEL_DB.sqls.length.should == 1
687
+ end
688
+
689
+ it "eager graphing should give you a graph of tables when called without .all" do
690
+ @c1.eager_graph(:tags, :artists).first.should == {:artists=>@c1.load(:id=>1), :artists_0=>@c1.load(:id=>10), :tags=>Tag.load(:id=>2)}
691
+ end
692
+
693
+ it "should be able to use eager and eager_graph together" do
694
+ a = @c1.eager_graph(:tags).eager(:albums).all
695
+ a.should == [@c1.load(:id=>1)]
696
+ MODEL_DB.sqls.should == ['SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id)',
697
+ 'SELECT albums.*, albums_artists.artist_id AS x_foreign_key_x FROM albums INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))']
698
+ a = a.first
699
+ a.tags.should == [Tag.load(:id=>2)]
700
+ a.albums.should == [Album.load(:id=>3)]
701
+ MODEL_DB.sqls.length.should == 2
702
+ end
703
+
704
+ it "should handle no associated records when eagerly graphing a single many_through_many association" do
705
+ ds = @c1.eager_graph(:tags)
706
+ def ds.fetch_rows(sql)
707
+ @db << sql
708
+ yield({:id=>1, :tags_id=>nil})
709
+ end
710
+ a = ds.all
711
+ a.should == [@c1.load(:id=>1)]
712
+ MODEL_DB.sqls.should == ['SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id)']
713
+ a.first.tags.should == []
714
+ MODEL_DB.sqls.length.should == 1
715
+ end
716
+
717
+ it "should handle no associated records when eagerly graphing multiple many_through_many associations" do
718
+ ds = @c1.eager_graph(:tags, :albums)
719
+ def ds.fetch_rows(sql)
720
+ @db << sql
721
+ yield({:id=>1, :tags_id=>nil, :albums_0_id=>3})
722
+ yield({:id=>1, :tags_id=>2, :albums_0_id=>nil})
723
+ yield({:id=>1, :tags_id=>5, :albums_0_id=>6})
724
+ yield({:id=>7, :tags_id=>nil, :albums_0_id=>nil})
725
+ end
726
+ a = ds.all
727
+ a.should == [@c1.load(:id=>1), @c1.load(:id=>7)]
728
+ MODEL_DB.sqls.should == ['SELECT artists.id, tags.id AS tags_id, albums_0.id AS albums_0_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id) LEFT OUTER JOIN albums_artists AS albums_artists_0 ON (albums_artists_0.artist_id = artists.id) LEFT OUTER JOIN albums AS albums_0 ON (albums_0.id = albums_artists_0.album_id)']
729
+ a.first.tags.should == [Tag.load(:id=>2), Tag.load(:id=>5)]
730
+ a.first.albums.should == [Album.load(:id=>3), Album.load(:id=>6)]
731
+ a.last.tags.should == []
732
+ a.last.albums.should == []
733
+ MODEL_DB.sqls.length.should == 1
734
+ end
735
+
736
+ it "should handle missing associated records when cascading eager graphing for associations of associated models" do
737
+ ds = @c1.eager_graph(:tags=>:tracks)
738
+ def ds.fetch_rows(sql)
739
+ @db << sql
740
+ yield({:id=>1, :tags_id=>2, :tracks_id=>4})
741
+ yield({:id=>1, :tags_id=>3, :tracks_id=>nil})
742
+ yield({:id=>2, :tags_id=>nil, :tracks_id=>nil})
743
+ end
744
+ a = ds.all
745
+ a.should == [@c1.load(:id=>1), @c1.load(:id=>2)]
746
+ MODEL_DB.sqls.should == ['SELECT artists.id, tags.id AS tags_id, tracks.id AS tracks_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id) LEFT OUTER JOIN albums_tags AS albums_tags_0 ON (albums_tags_0.tag_id = tags.id) LEFT OUTER JOIN albums AS albums_0 ON (albums_0.id = albums_tags_0.album_id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums_0.id)']
747
+ a.last.tags.should == []
748
+ a = a.first
749
+ a.tags.should == [Tag.load(:id=>2), Tag.load(:id=>3)]
750
+ a.tags.first.tracks.should == [Track.load(:id=>4)]
751
+ a.tags.last.tracks.should == []
752
+ MODEL_DB.sqls.length.should == 1
753
+ end
754
+
755
+ it "eager graphing should respect :left_primary_key and :right_primary_key options" do
756
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :left_primary_key=>:yyy, :right_primary_key=>:tag_id
757
+ @c1.dataset.meta_def(:columns){[:id, :yyy]}
758
+ Tag.dataset.meta_def(:columns){[:id, :tag_id]}
759
+ ds = @c1.eager_graph(:tags)
760
+ def ds.fetch_rows(sql)
761
+ @db << sql
762
+ yield({:id=>1, :yyy=>8, :tags_id=>2, :tag_id=>4})
763
+ end
764
+ a = ds.all
765
+ a.should == [@c1.load(:id=>1, :yyy=>8)]
766
+ MODEL_DB.sqls.should == ['SELECT artists.id, artists.yyy, tags.id AS tags_id, tags.tag_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.yyy) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.tag_id = albums_tags.tag_id)']
767
+ a.first.tags.should == [Tag.load(:id=>2, :tag_id=>4)]
768
+ MODEL_DB.sqls.length.should == 1
769
+ end
770
+
771
+ it "eager graphing should respect composite keys" do
772
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:id, :tag_id], :left_primary_key=>[:id, :yyy]
773
+ @c1.dataset.meta_def(:columns){[:id, :yyy]}
774
+ Tag.dataset.meta_def(:columns){[:id, :tag_id]}
775
+ ds = @c1.eager_graph(:tags)
776
+ def ds.fetch_rows(sql)
777
+ @db << sql
778
+ yield({:id=>1, :yyy=>8, :tags_id=>2, :tag_id=>4})
779
+ end
780
+ a = ds.all
781
+ a.should == [@c1.load(:id=>1, :yyy=>8)]
782
+ MODEL_DB.sqls.should == ['SELECT artists.id, artists.yyy, tags.id AS tags_id, tags.tag_id FROM artists LEFT OUTER JOIN albums_artists ON ((albums_artists.b1 = artists.id) AND (albums_artists.b2 = artists.yyy)) LEFT OUTER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) LEFT OUTER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) LEFT OUTER JOIN tags ON ((tags.id = albums_tags.g1) AND (tags.tag_id = albums_tags.g2))']
783
+ a.first.tags.should == [Tag.load(:id=>2, :tag_id=>4)]
784
+ MODEL_DB.sqls.length.should == 1
785
+ end
786
+
787
+ it "should respect the association's :graph_select option" do
788
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :graph_select=>:b
789
+ ds = @c1.eager_graph(:tags)
790
+ def ds.fetch_rows(sql)
791
+ @db << sql
792
+ yield({:id=>1, :b=>2})
793
+ end
794
+ a = ds.all
795
+ a.should == [@c1.load(:id=>1)]
796
+ MODEL_DB.sqls.should == ['SELECT artists.id, tags.b FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id)']
797
+ a.first.tags.should == [Tag.load(:b=>2)]
798
+ MODEL_DB.sqls.length.should == 1
799
+ end
800
+
801
+ it "should respect the association's :graph_join_type option" do
802
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :graph_join_type=>:inner
803
+ @c1.eager_graph(:tags).sql.should == 'SELECT artists.id, tags.id AS tags_id FROM artists INNER JOIN albums_artists ON (albums_artists.artist_id = artists.id) INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) INNER JOIN tags ON (tags.id = albums_tags.tag_id)'
804
+ end
805
+
806
+ it "should respect the association's :join_type option on through" do
807
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], {:table=>:albums, :left=>:id, :right=>:id, :join_type=>:natural}, [:albums_tags, :album_id, :tag_id]], :graph_join_type=>:inner
808
+ @c1.eager_graph(:tags).sql.should == 'SELECT artists.id, tags.id AS tags_id FROM artists INNER JOIN albums_artists ON (albums_artists.artist_id = artists.id) NATURAL JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) INNER JOIN tags ON (tags.id = albums_tags.tag_id)'
809
+ end
810
+
811
+ it "should respect the association's :conditions option" do
812
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], {:table=>:albums, :left=>:id, :right=>:id}, [:albums_tags, :album_id, :tag_id]], :conditions=>{:a=>32}
813
+ @c1.eager_graph(:tags).sql.should == 'SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON ((tags.id = albums_tags.tag_id) AND (tags.a = 32))'
814
+ end
815
+
816
+ it "should respect the association's :graph_conditions option" do
817
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], {:table=>:albums, :left=>:id, :right=>:id}, [:albums_tags, :album_id, :tag_id]], :graph_conditions=>{:a=>42}
818
+ @c1.eager_graph(:tags).sql.should == 'SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON ((tags.id = albums_tags.tag_id) AND (tags.a = 42))'
819
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], {:table=>:albums, :left=>:id, :right=>:id}, [:albums_tags, :album_id, :tag_id]], :graph_conditions=>{:a=>42}, :conditions=>{:a=>32}
820
+ @c1.eager_graph(:tags).sql.should == 'SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON ((tags.id = albums_tags.tag_id) AND (tags.a = 42))'
821
+ end
822
+
823
+ it "should respect the association's :conditions option on through" do
824
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], {:table=>:albums, :left=>:id, :right=>:id, :conditions=>{:a=>42}}, [:albums_tags, :album_id, :tag_id]]
825
+ @c1.eager_graph(:tags).sql.should == 'SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON ((albums.id = albums_artists.album_id) AND (albums.a = 42)) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id)'
826
+ end
827
+
828
+ it "should respect the association's :graph_block option" do
829
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], {:table=>:albums, :left=>:id, :right=>:id}, [:albums_tags, :album_id, :tag_id]], :graph_block=>proc{|ja,lja,js| {:active.qualify(ja)=>true}}
830
+ @c1.eager_graph(:tags).sql.should == 'SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON ((tags.id = albums_tags.tag_id) AND (tags.active IS TRUE))'
831
+ end
832
+
833
+ it "should respect the association's :block option on through" do
834
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], {:table=>:albums, :left=>:id, :right=>:id, :block=>proc{|ja,lja,js| {:active.qualify(ja)=>true}}}, [:albums_tags, :album_id, :tag_id]]
835
+ @c1.eager_graph(:tags).sql.should == 'SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON ((albums.id = albums_artists.album_id) AND (albums.active IS TRUE)) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id)'
836
+ end
837
+
838
+ it "should respect the association's :graph_only_conditions option" do
839
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], {:table=>:albums, :left=>:id, :right=>:id}, [:albums_tags, :album_id, :tag_id]], :graph_only_conditions=>{:a=>32}
840
+ @c1.eager_graph(:tags).sql.should == 'SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.a = 32)'
841
+ end
842
+
843
+ it "should respect the association's :only_conditions option on through" do
844
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], {:table=>:albums, :left=>:id, :right=>:id, :only_conditions=>{:a=>42}}, [:albums_tags, :album_id, :tag_id]]
845
+ @c1.eager_graph(:tags).sql.should == 'SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.a = 42) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id)'
846
+ end
847
+
848
+ it "should create unique table aliases for all associations" do
849
+ @c1.eager_graph(:artists=>{:artists=>:artists}).sql.should == "SELECT artists.id, artists_0.id AS artists_0_id, artists_1.id AS artists_1_id, artists_2.id AS artists_2_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_artists AS albums_artists_0 ON (albums_artists_0.album_id = albums.id) LEFT OUTER JOIN artists AS artists_0 ON (artists_0.id = albums_artists_0.artist_id) LEFT OUTER JOIN albums_artists AS albums_artists_1 ON (albums_artists_1.artist_id = artists_0.id) LEFT OUTER JOIN albums AS albums_0 ON (albums_0.id = albums_artists_1.album_id) LEFT OUTER JOIN albums_artists AS albums_artists_2 ON (albums_artists_2.album_id = albums_0.id) LEFT OUTER JOIN artists AS artists_1 ON (artists_1.id = albums_artists_2.artist_id) LEFT OUTER JOIN albums_artists AS albums_artists_3 ON (albums_artists_3.artist_id = artists_1.id) LEFT OUTER JOIN albums AS albums_1 ON (albums_1.id = albums_artists_3.album_id) LEFT OUTER JOIN albums_artists AS albums_artists_4 ON (albums_artists_4.album_id = albums_1.id) LEFT OUTER JOIN artists AS artists_2 ON (artists_2.id = albums_artists_4.artist_id)"
850
+ end
851
+
852
+ it "should respect the association's :order" do
853
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], {:table=>:albums, :left=>:id, :right=>:id}, [:albums_tags, :album_id, :tag_id]], :order=>[:blah1, :blah2]
854
+ @c1.order(:artists__blah2, :artists__blah3).eager_graph(:tags).sql.should == 'SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id) ORDER BY artists.blah2, artists.blah3, tags.blah1, tags.blah2'
855
+ end
856
+
857
+ it "should only qualify unqualified symbols, identifiers, or ordered versions in association's :order" do
858
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], {:table=>:albums, :left=>:id, :right=>:id}, [:albums_tags, :album_id, :tag_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)]
859
+ @c1.order(:artists__blah2, :artists__blah3).eager_graph(:tags).sql.should == 'SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id) ORDER BY artists.blah2, artists.blah3, tags.blah__id, tags.blah__id DESC, blah.id DESC, blah.id, tags.album_id, tags.album_id DESC, 1, RANDOM(), b.a'
860
+ end
861
+
862
+ it "should not respect the association's :order if :order_eager_graph is false" do
863
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], {:table=>:albums, :left=>:id, :right=>:id}, [:albums_tags, :album_id, :tag_id]], :order=>[:blah1, :blah2], :order_eager_graph=>false
864
+ @c1.order(:artists__blah2, :artists__blah3).eager_graph(:tags).sql.should == 'SELECT artists.id, tags.id AS tags_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id) ORDER BY artists.blah2, artists.blah3'
865
+ end
866
+
867
+ it "should add the associations :order for multiple associations" do
868
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], {:table=>:albums, :left=>:id, :right=>:id}, [:albums_tags, :album_id, :tag_id]], :order=>[:blah1, :blah2]
869
+ @c1.many_through_many :albums, [[:albums_artists, :artist_id, :album_id]], :order=>[:blah3, :blah4]
870
+ @c1.eager_graph(:tags, :albums).sql.should == 'SELECT artists.id, tags.id AS tags_id, albums_0.id AS albums_0_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id) LEFT OUTER JOIN albums_artists AS albums_artists_0 ON (albums_artists_0.artist_id = artists.id) LEFT OUTER JOIN albums AS albums_0 ON (albums_0.id = albums_artists_0.album_id) ORDER BY tags.blah1, tags.blah2, albums_0.blah3, albums_0.blah4'
871
+ end
872
+
873
+ it "should add the association's :order for cascading associations" do
874
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], {:table=>:albums, :left=>:id, :right=>:id}, [:albums_tags, :album_id, :tag_id]], :order=>[:blah1, :blah2]
875
+ Tag.many_through_many :tracks, [[:albums_tags, :tag_id, :album_id], [:albums, :id, :id]], :right_primary_key=>:album_id, :order=>[:blah3, :blah4]
876
+ @c1.eager_graph(:tags=>:tracks).sql.should == 'SELECT artists.id, tags.id AS tags_id, tracks.id AS tracks_id FROM artists LEFT OUTER JOIN albums_artists ON (albums_artists.artist_id = artists.id) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id) LEFT OUTER JOIN albums_tags AS albums_tags_0 ON (albums_tags_0.tag_id = tags.id) LEFT OUTER JOIN albums AS albums_0 ON (albums_0.id = albums_tags_0.album_id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums_0.id) ORDER BY tags.blah1, tags.blah2, tracks.blah3, tracks.blah4'
877
+ end
878
+
879
+ it "should use the correct qualifier when graphing multiple tables with extra conditions" do
880
+ @c1.many_through_many :tags, [{:table=>:albums_artists, :left=>:artist_id, :right=>:album_id, :conditions=>{:a=>:b}}, {:table=>:albums, :left=>:id, :right=>:id}, [:albums_tags, :album_id, :tag_id]]
881
+ @c1.many_through_many :albums, [{:table=>:albums_artists, :left=>:artist_id, :right=>:album_id, :conditions=>{:c=>:d}}]
882
+ @c1.eager_graph(:tags, :albums).sql.should == 'SELECT artists.id, tags.id AS tags_id, albums_0.id AS albums_0_id FROM artists LEFT OUTER JOIN albums_artists ON ((albums_artists.artist_id = artists.id) AND (albums_artists.a = artists.b)) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id) LEFT OUTER JOIN albums_artists AS albums_artists_0 ON ((albums_artists_0.artist_id = artists.id) AND (albums_artists_0.c = artists.d)) LEFT OUTER JOIN albums AS albums_0 ON (albums_0.id = albums_artists_0.album_id)'
883
+ end
884
+ end