sequel 3.21.0 → 3.32.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 (234) hide show
  1. data/CHANGELOG +539 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +20 -6
  4. data/Rakefile +37 -19
  5. data/bin/sequel +1 -5
  6. data/doc/advanced_associations.rdoc +17 -0
  7. data/doc/association_basics.rdoc +293 -43
  8. data/doc/dataset_basics.rdoc +4 -4
  9. data/doc/dataset_filtering.rdoc +8 -0
  10. data/doc/mass_assignment.rdoc +54 -0
  11. data/doc/migration.rdoc +15 -538
  12. data/doc/model_hooks.rdoc +72 -27
  13. data/doc/opening_databases.rdoc +89 -50
  14. data/doc/prepared_statements.rdoc +40 -13
  15. data/doc/reflection.rdoc +8 -2
  16. data/doc/release_notes/3.22.0.txt +39 -0
  17. data/doc/release_notes/3.23.0.txt +172 -0
  18. data/doc/release_notes/3.24.0.txt +420 -0
  19. data/doc/release_notes/3.25.0.txt +88 -0
  20. data/doc/release_notes/3.26.0.txt +88 -0
  21. data/doc/release_notes/3.27.0.txt +82 -0
  22. data/doc/release_notes/3.28.0.txt +304 -0
  23. data/doc/release_notes/3.29.0.txt +459 -0
  24. data/doc/release_notes/3.30.0.txt +135 -0
  25. data/doc/release_notes/3.31.0.txt +146 -0
  26. data/doc/release_notes/3.32.0.txt +202 -0
  27. data/doc/schema_modification.rdoc +547 -0
  28. data/doc/sharding.rdoc +7 -1
  29. data/doc/testing.rdoc +129 -0
  30. data/doc/transactions.rdoc +137 -0
  31. data/doc/virtual_rows.rdoc +2 -2
  32. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  33. data/lib/sequel/adapters/ado.rb +40 -18
  34. data/lib/sequel/adapters/amalgalite.rb +17 -9
  35. data/lib/sequel/adapters/db2.rb +175 -86
  36. data/lib/sequel/adapters/dbi.rb +15 -15
  37. data/lib/sequel/adapters/do/mysql.rb +5 -7
  38. data/lib/sequel/adapters/do/postgres.rb +0 -5
  39. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  40. data/lib/sequel/adapters/do.rb +17 -36
  41. data/lib/sequel/adapters/firebird.rb +27 -208
  42. data/lib/sequel/adapters/ibmdb.rb +448 -0
  43. data/lib/sequel/adapters/informix.rb +6 -23
  44. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  45. data/lib/sequel/adapters/jdbc/db2.rb +52 -0
  46. data/lib/sequel/adapters/jdbc/derby.rb +260 -0
  47. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  48. data/lib/sequel/adapters/jdbc/h2.rb +66 -30
  49. data/lib/sequel/adapters/jdbc/hsqldb.rb +177 -0
  50. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  51. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  52. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  53. data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
  54. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  55. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
  56. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
  57. data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
  58. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  59. data/lib/sequel/adapters/jdbc.rb +231 -78
  60. data/lib/sequel/adapters/mock.rb +339 -0
  61. data/lib/sequel/adapters/mysql.rb +85 -184
  62. data/lib/sequel/adapters/mysql2.rb +26 -25
  63. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  64. data/lib/sequel/adapters/odbc/mssql.rb +1 -6
  65. data/lib/sequel/adapters/odbc.rb +28 -9
  66. data/lib/sequel/adapters/openbase.rb +3 -5
  67. data/lib/sequel/adapters/oracle.rb +355 -53
  68. data/lib/sequel/adapters/postgres.rb +194 -33
  69. data/lib/sequel/adapters/shared/access.rb +32 -9
  70. data/lib/sequel/adapters/shared/db2.rb +328 -0
  71. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  72. data/lib/sequel/adapters/shared/informix.rb +53 -0
  73. data/lib/sequel/adapters/shared/mssql.rb +278 -107
  74. data/lib/sequel/adapters/shared/mysql.rb +203 -65
  75. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  76. data/lib/sequel/adapters/shared/oracle.rb +237 -46
  77. data/lib/sequel/adapters/shared/postgres.rb +214 -82
  78. data/lib/sequel/adapters/shared/progress.rb +5 -8
  79. data/lib/sequel/adapters/shared/sqlite.rb +200 -50
  80. data/lib/sequel/adapters/sqlite.rb +29 -21
  81. data/lib/sequel/adapters/swift/mysql.rb +12 -7
  82. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  83. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  84. data/lib/sequel/adapters/swift.rb +5 -5
  85. data/lib/sequel/adapters/tinytds.rb +160 -19
  86. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  87. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  88. data/lib/sequel/ast_transformer.rb +190 -0
  89. data/lib/sequel/connection_pool/threaded.rb +3 -2
  90. data/lib/sequel/connection_pool.rb +1 -1
  91. data/lib/sequel/core.rb +68 -5
  92. data/lib/sequel/database/connecting.rb +5 -6
  93. data/lib/sequel/database/dataset.rb +4 -4
  94. data/lib/sequel/database/dataset_defaults.rb +59 -1
  95. data/lib/sequel/database/logging.rb +1 -1
  96. data/lib/sequel/database/misc.rb +104 -19
  97. data/lib/sequel/database/query.rb +163 -59
  98. data/lib/sequel/database/schema_generator.rb +17 -7
  99. data/lib/sequel/database/schema_methods.rb +59 -29
  100. data/lib/sequel/dataset/actions.rb +240 -76
  101. data/lib/sequel/dataset/features.rb +91 -8
  102. data/lib/sequel/dataset/graph.rb +38 -11
  103. data/lib/sequel/dataset/misc.rb +50 -23
  104. data/lib/sequel/dataset/mutation.rb +12 -7
  105. data/lib/sequel/dataset/prepared_statements.rb +59 -27
  106. data/lib/sequel/dataset/query.rb +236 -43
  107. data/lib/sequel/dataset/sql.rb +676 -267
  108. data/lib/sequel/dataset.rb +8 -0
  109. data/lib/sequel/exceptions.rb +4 -0
  110. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  111. data/lib/sequel/extensions/migration.rb +6 -4
  112. data/lib/sequel/extensions/named_timezones.rb +5 -0
  113. data/lib/sequel/extensions/query.rb +1 -1
  114. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  115. data/lib/sequel/extensions/to_dot.rb +95 -83
  116. data/lib/sequel/model/associations.rb +1081 -344
  117. data/lib/sequel/model/base.rb +420 -133
  118. data/lib/sequel/model/errors.rb +1 -1
  119. data/lib/sequel/model/exceptions.rb +5 -1
  120. data/lib/sequel/model.rb +20 -10
  121. data/lib/sequel/plugins/active_model.rb +13 -1
  122. data/lib/sequel/plugins/association_pks.rb +22 -4
  123. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  124. data/lib/sequel/plugins/composition.rb +43 -10
  125. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  126. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  127. data/lib/sequel/plugins/force_encoding.rb +6 -6
  128. data/lib/sequel/plugins/identity_map.rb +114 -7
  129. data/lib/sequel/plugins/many_through_many.rb +68 -7
  130. data/lib/sequel/plugins/nested_attributes.rb +65 -10
  131. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  132. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  133. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  134. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  135. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  136. data/lib/sequel/plugins/serialization.rb +98 -49
  137. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  138. data/lib/sequel/plugins/sharding.rb +21 -54
  139. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  140. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  141. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  142. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  143. data/lib/sequel/sql.rb +109 -52
  144. data/lib/sequel/timezones.rb +57 -38
  145. data/lib/sequel/version.rb +1 -1
  146. data/spec/adapters/db2_spec.rb +146 -0
  147. data/spec/adapters/mssql_spec.rb +135 -30
  148. data/spec/adapters/mysql_spec.rb +244 -212
  149. data/spec/adapters/oracle_spec.rb +67 -77
  150. data/spec/adapters/postgres_spec.rb +267 -78
  151. data/spec/adapters/spec_helper.rb +6 -5
  152. data/spec/adapters/sqlite_spec.rb +35 -21
  153. data/spec/core/connection_pool_spec.rb +71 -92
  154. data/spec/core/core_sql_spec.rb +21 -30
  155. data/spec/core/database_spec.rb +789 -512
  156. data/spec/core/dataset_spec.rb +1176 -1061
  157. data/spec/core/expression_filters_spec.rb +159 -42
  158. data/spec/core/mock_adapter_spec.rb +419 -0
  159. data/spec/core/object_graph_spec.rb +86 -114
  160. data/spec/core/schema_generator_spec.rb +3 -3
  161. data/spec/core/schema_spec.rb +298 -38
  162. data/spec/core/spec_helper.rb +6 -48
  163. data/spec/extensions/active_model_spec.rb +13 -0
  164. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  165. data/spec/extensions/association_dependencies_spec.rb +2 -12
  166. data/spec/extensions/association_pks_spec.rb +66 -32
  167. data/spec/extensions/caching_spec.rb +23 -50
  168. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  169. data/spec/extensions/columns_introspection_spec.rb +91 -0
  170. data/spec/extensions/composition_spec.rb +18 -13
  171. data/spec/extensions/dataset_associations_spec.rb +199 -0
  172. data/spec/extensions/defaults_setter_spec.rb +64 -0
  173. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  174. data/spec/extensions/identity_map_spec.rb +114 -22
  175. data/spec/extensions/instance_filters_spec.rb +11 -21
  176. data/spec/extensions/instance_hooks_spec.rb +53 -0
  177. data/spec/extensions/json_serializer_spec.rb +4 -5
  178. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  179. data/spec/extensions/list_spec.rb +17 -39
  180. data/spec/extensions/many_through_many_spec.rb +283 -250
  181. data/spec/extensions/migration_spec.rb +35 -32
  182. data/spec/extensions/named_timezones_spec.rb +23 -3
  183. data/spec/extensions/nested_attributes_spec.rb +97 -88
  184. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  185. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  186. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  187. data/spec/extensions/prepared_statements_spec.rb +53 -0
  188. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  189. data/spec/extensions/pretty_table_spec.rb +1 -6
  190. data/spec/extensions/query_spec.rb +6 -0
  191. data/spec/extensions/rcte_tree_spec.rb +41 -43
  192. data/spec/extensions/schema_dumper_spec.rb +5 -8
  193. data/spec/extensions/schema_spec.rb +13 -21
  194. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  195. data/spec/extensions/serialization_spec.rb +81 -43
  196. data/spec/extensions/sharding_spec.rb +66 -140
  197. data/spec/extensions/single_table_inheritance_spec.rb +18 -29
  198. data/spec/extensions/spec_helper.rb +13 -61
  199. data/spec/extensions/sql_expr_spec.rb +20 -60
  200. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  201. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  202. data/spec/extensions/timestamps_spec.rb +6 -6
  203. data/spec/extensions/to_dot_spec.rb +4 -7
  204. data/spec/extensions/touch_spec.rb +13 -14
  205. data/spec/extensions/tree_spec.rb +11 -26
  206. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  207. data/spec/extensions/update_primary_key_spec.rb +30 -24
  208. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  209. data/spec/extensions/validation_helpers_spec.rb +16 -35
  210. data/spec/extensions/xml_serializer_spec.rb +16 -4
  211. data/spec/integration/associations_test.rb +661 -21
  212. data/spec/integration/database_test.rb +4 -2
  213. data/spec/integration/dataset_test.rb +500 -67
  214. data/spec/integration/eager_loader_test.rb +19 -21
  215. data/spec/integration/model_test.rb +80 -1
  216. data/spec/integration/plugin_test.rb +367 -119
  217. data/spec/integration/prepared_statement_test.rb +222 -125
  218. data/spec/integration/schema_test.rb +217 -39
  219. data/spec/integration/spec_helper.rb +39 -30
  220. data/spec/integration/timezone_test.rb +38 -12
  221. data/spec/integration/transaction_test.rb +182 -5
  222. data/spec/integration/type_test.rb +17 -3
  223. data/spec/model/association_reflection_spec.rb +201 -7
  224. data/spec/model/associations_spec.rb +955 -662
  225. data/spec/model/base_spec.rb +275 -95
  226. data/spec/model/dataset_methods_spec.rb +7 -27
  227. data/spec/model/eager_loading_spec.rb +677 -676
  228. data/spec/model/hooks_spec.rb +349 -67
  229. data/spec/model/model_spec.rb +222 -168
  230. data/spec/model/plugins_spec.rb +24 -13
  231. data/spec/model/record_spec.rb +381 -218
  232. data/spec/model/spec_helper.rb +13 -71
  233. data/spec/model/validations_spec.rb +11 -0
  234. metadata +117 -73
@@ -2,42 +2,47 @@ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  describe Sequel::Model, "associate" do
4
4
  it "should use explicit class if given a class, symbol, or string" do
5
- MODEL_DB.reset
6
- klass = Class.new(Sequel::Model(:nodes))
7
- class ::ParParent < Sequel::Model
5
+ begin
6
+ klass = Class.new(Sequel::Model(:nodes))
7
+ class ::ParParent < Sequel::Model; end
8
+
9
+ klass.associate :many_to_one, :par_parent0, :class=>ParParent
10
+ klass.associate :one_to_many, :par_parent1s, :class=>'ParParent'
11
+ klass.associate :many_to_many, :par_parent2s, :class=>:ParParent
12
+
13
+ klass.association_reflection(:"par_parent0").associated_class.should == ParParent
14
+ klass.association_reflection(:"par_parent1s").associated_class.should == ParParent
15
+ klass.association_reflection(:"par_parent2s").associated_class.should == ParParent
16
+ ensure
17
+ Object.send(:remove_const, :ParParent)
8
18
  end
9
-
10
- klass.associate :many_to_one, :par_parent0, :class=>ParParent
11
- klass.associate :one_to_many, :par_parent1s, :class=>'ParParent'
12
- klass.associate :many_to_many, :par_parent2s, :class=>:ParParent
13
-
14
- klass.association_reflection(:"par_parent0").associated_class.should == ParParent
15
- klass.association_reflection(:"par_parent1s").associated_class.should == ParParent
16
- klass.association_reflection(:"par_parent2s").associated_class.should == ParParent
17
19
  end
18
20
 
19
21
  it "should default to associating to other models in the same scope" do
20
- class ::AssociationModuleTest
21
- class Album < Sequel::Model
22
- many_to_one :artist
23
- many_to_many :tags
24
- end
25
- class Artist< Sequel::Model
26
- one_to_many :albums
27
- end
28
- class Tag < Sequel::Model
29
- many_to_many :albums
22
+ begin
23
+ class ::AssociationModuleTest
24
+ class Album < Sequel::Model
25
+ many_to_one :artist
26
+ many_to_many :tags
27
+ end
28
+ class Artist< Sequel::Model
29
+ one_to_many :albums
30
+ end
31
+ class Tag < Sequel::Model
32
+ many_to_many :albums
33
+ end
30
34
  end
35
+
36
+ ::AssociationModuleTest::Album.association_reflection(:artist).associated_class.should == ::AssociationModuleTest::Artist
37
+ ::AssociationModuleTest::Album.association_reflection(:tags).associated_class.should == ::AssociationModuleTest::Tag
38
+ ::AssociationModuleTest::Artist.association_reflection(:albums).associated_class.should == ::AssociationModuleTest::Album
39
+ ::AssociationModuleTest::Tag.association_reflection(:albums).associated_class.should == ::AssociationModuleTest::Album
40
+ ensure
41
+ Object.send(:remove_const, :AssociationModuleTest)
31
42
  end
32
-
33
- ::AssociationModuleTest::Album.association_reflection(:artist).associated_class.should == ::AssociationModuleTest::Artist
34
- ::AssociationModuleTest::Album.association_reflection(:tags).associated_class.should == ::AssociationModuleTest::Tag
35
- ::AssociationModuleTest::Artist.association_reflection(:albums).associated_class.should == ::AssociationModuleTest::Album
36
- ::AssociationModuleTest::Tag.association_reflection(:albums).associated_class.should == ::AssociationModuleTest::Album
37
43
  end
38
44
 
39
45
  it "should add a model_object and association_reflection accessors to the dataset, and return it with the current model object" do
40
- MODEL_DB.reset
41
46
  klass = Class.new(Sequel::Model(:nodes)) do
42
47
  columns :id, :a_id
43
48
  end
@@ -64,7 +69,6 @@ describe Sequel::Model, "associate" do
64
69
  end
65
70
 
66
71
  it "should allow extending the dataset with :extend option" do
67
- MODEL_DB.reset
68
72
  klass = Class.new(Sequel::Model(:nodes)) do
69
73
  columns :id, :a_id
70
74
  end
@@ -91,44 +95,46 @@ describe Sequel::Model, "associate" do
91
95
  end
92
96
 
93
97
  it "should clone an existing association with the :clone option" do
94
- MODEL_DB.reset
95
- klass = Class.new(Sequel::Model(:nodes))
96
-
97
- klass.many_to_one(:par_parent, :order=>:a){1}
98
- klass.one_to_many(:par_parent1s, :class=>'ParParent', :limit=>12){4}
99
- klass.many_to_many(:par_parent2s, :class=>:ParParent, :uniq=>true){2}
98
+ begin
99
+ class ::ParParent < Sequel::Model; end
100
+ klass = Class.new(Sequel::Model(:nodes))
101
+
102
+ klass.many_to_one(:par_parent, :order=>:a){1}
103
+ klass.one_to_many(:par_parent1s, :class=>'ParParent', :limit=>12){4}
104
+ klass.many_to_many(:par_parent2s, :class=>:ParParent, :uniq=>true){2}
100
105
 
101
- klass.many_to_one :par, :clone=>:par_parent, :select=>:b
102
- klass.one_to_many :par1s, :clone=>:par_parent1s, :order=>:b, :limit=>10, :block=>nil
103
- klass.many_to_many(:par2s, :clone=>:par_parent2s, :order=>:c){3}
104
-
105
- klass.association_reflection(:par).associated_class.should == ParParent
106
- klass.association_reflection(:par1s).associated_class.should == ParParent
107
- klass.association_reflection(:par2s).associated_class.should == ParParent
108
-
109
- klass.association_reflection(:par)[:order].should == :a
110
- klass.association_reflection(:par).select.should == :b
111
- klass.association_reflection(:par)[:block].call.should == 1
112
- klass.association_reflection(:par1s)[:limit].should == 10
113
- klass.association_reflection(:par1s)[:order].should == :b
114
- klass.association_reflection(:par1s)[:block].should == nil
115
- klass.association_reflection(:par2s)[:after_load].length.should == 1
116
- klass.association_reflection(:par2s)[:order].should == :c
117
- klass.association_reflection(:par2s)[:block].call.should == 3
106
+ klass.many_to_one :par, :clone=>:par_parent, :select=>:b
107
+ klass.one_to_many :par1s, :clone=>:par_parent1s, :order=>:b, :limit=>10, :block=>nil
108
+ klass.many_to_many(:par2s, :clone=>:par_parent2s, :order=>:c){3}
109
+
110
+ klass.association_reflection(:par).associated_class.should == ParParent
111
+ klass.association_reflection(:par1s).associated_class.should == ParParent
112
+ klass.association_reflection(:par2s).associated_class.should == ParParent
113
+
114
+ klass.association_reflection(:par)[:order].should == :a
115
+ klass.association_reflection(:par).select.should == :b
116
+ klass.association_reflection(:par)[:block].call.should == 1
117
+ klass.association_reflection(:par1s)[:limit].should == 10
118
+ klass.association_reflection(:par1s)[:order].should == :b
119
+ klass.association_reflection(:par1s)[:block].should == nil
120
+ klass.association_reflection(:par2s)[:after_load].length.should == 1
121
+ klass.association_reflection(:par2s)[:order].should == :c
122
+ klass.association_reflection(:par2s)[:block].call.should == 3
123
+ ensure
124
+ Object.send(:remove_const, :ParParent)
125
+ end
118
126
  end
119
127
 
120
128
  end
121
129
 
122
130
  describe Sequel::Model, "many_to_one" do
123
131
  before do
124
- MODEL_DB.reset
125
-
126
132
  @c2 = Class.new(Sequel::Model(:nodes)) do
127
133
  unrestrict_primary_key
128
134
  columns :id, :parent_id, :par_parent_id, :blah
129
135
  end
130
-
131
136
  @dataset = @c2.dataset
137
+ MODEL_DB.reset
132
138
  end
133
139
 
134
140
  it "should use implicit key if omitted" do
@@ -142,32 +148,43 @@ describe Sequel::Model, "many_to_one" do
142
148
  MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 234) LIMIT 1"]
143
149
  end
144
150
 
151
+ it "should allow association with the same name as the key if :key_alias is given" do
152
+ @c2.def_column_alias(:parent_id_id, :parent_id)
153
+ @c2.many_to_one :parent_id, :key_column=>:parent_id, :class => @c2
154
+ d = @c2.load(:id => 1, :parent_id => 234)
155
+ d.parent_id_dataset.sql.should == "SELECT * FROM nodes WHERE (nodes.id = 234) LIMIT 1"
156
+ d.parent_id.should == @c2.load(:x => 1, :id => 1)
157
+ d.parent_id_id.should == 234
158
+ d[:parent_id].should == 234
159
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 234) LIMIT 1"]
160
+
161
+ d.parent_id_id = 3
162
+ d.parent_id_id.should == 3
163
+ d[:parent_id].should == 3
164
+ end
165
+
145
166
  it "should use implicit class if omitted" do
146
- class ::ParParent < Sequel::Model
167
+ begin
168
+ class ::ParParent < Sequel::Model; end
169
+ @c2.many_to_one :par_parent
170
+ @c2.new(:id => 1, :par_parent_id => 234).par_parent.class.should == ParParent
171
+ MODEL_DB.sqls.should == ["SELECT * FROM par_parents WHERE (par_parents.id = 234) LIMIT 1"]
172
+ ensure
173
+ Object.send(:remove_const, :ParParent)
147
174
  end
148
-
149
- @c2.many_to_one :par_parent
150
-
151
- d = @c2.new(:id => 1, :par_parent_id => 234)
152
- p = d.par_parent
153
- p.class.should == ParParent
154
-
155
- MODEL_DB.sqls.should == ["SELECT * FROM par_parents WHERE (par_parents.id = 234) LIMIT 1"]
156
175
  end
157
176
 
158
177
  it "should use class inside module if given as a string" do
159
- module ::Par
160
- class Parent < Sequel::Model
178
+ begin
179
+ module ::Par
180
+ class Parent < Sequel::Model; end
161
181
  end
182
+ @c2.many_to_one :par_parent, :class=>"Par::Parent"
183
+ @c2.new(:id => 1, :par_parent_id => 234).par_parent.class.should == Par::Parent
184
+ MODEL_DB.sqls.should == ["SELECT * FROM parents WHERE (parents.id = 234) LIMIT 1"]
185
+ ensure
186
+ Object.send(:remove_const, :Par)
162
187
  end
163
-
164
- @c2.many_to_one :par_parent, :class=>"Par::Parent"
165
-
166
- d = @c2.new(:id => 1, :par_parent_id => 234)
167
- p = d.par_parent
168
- p.class.should == Par::Parent
169
-
170
- MODEL_DB.sqls.should == ["SELECT * FROM parents WHERE (parents.id = 234) LIMIT 1"]
171
188
  end
172
189
 
173
190
  it "should use explicit key if given" do
@@ -181,6 +198,12 @@ describe Sequel::Model, "many_to_one" do
181
198
  MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 567) LIMIT 1"]
182
199
  end
183
200
 
201
+ it "should respect :qualify => false option" do
202
+ @c2.many_to_one :parent, :class => @c2, :key => :blah, :qualify=>false
203
+ @c2.new(:id => 1, :blah => 567).parent
204
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (id = 567) LIMIT 1"]
205
+ end
206
+
184
207
  it "should use :primary_key option if given" do
185
208
  @c2.many_to_one :parent, :class => @c2, :key => :blah, :primary_key => :pk
186
209
  @c2.new(:id => 1, :blah => 567).parent
@@ -218,14 +241,12 @@ describe Sequel::Model, "many_to_one" do
218
241
  MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.id = 567) AND (a = 32)) LIMIT 1"]
219
242
 
220
243
  @c2.many_to_one :parent, :class => @c2, :key => :blah, :conditions=>:a
221
- MODEL_DB.sqls.clear
222
244
  @c2.new(:id => 1, :blah => 567).parent
223
245
  MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.id = 567) AND a) LIMIT 1"]
224
246
  end
225
247
 
226
248
  it "should support :order, :limit (only for offset), and :dataset options, as well as a block" do
227
- c2 = @c2
228
- @c2.many_to_one :child_20, :class => @c2, :key=>:id, :dataset=>proc{c2.filter(:parent_id=>pk)}, :limit=>[10,20], :order=>:name do |ds|
249
+ @c2.many_to_one :child_20, :class => @c2, :key=>:id, :dataset=>proc{model.filter(:parent_id=>pk)}, :limit=>[10,20], :order=>:name do |ds|
229
250
  ds.filter(:x.sql_number > 1)
230
251
  end
231
252
  @c2.load(:id => 100).child_20
@@ -234,24 +255,19 @@ describe Sequel::Model, "many_to_one" do
234
255
 
235
256
  it "should return nil if key value is nil" do
236
257
  @c2.many_to_one :parent, :class => @c2
237
-
238
- d = @c2.new(:id => 1)
239
- d.parent.should == nil
258
+ @c2.new(:id => 1).parent.should == nil
259
+ MODEL_DB.sqls.should == []
240
260
  end
241
261
 
242
262
  it "should cache negative lookup" do
243
263
  @c2.many_to_one :parent, :class => @c2
244
- ds = @c2.dataset
245
- def ds.fetch_rows(sql, &block)
246
- MODEL_DB.sqls << sql
247
- end
248
-
264
+ @c2.dataset._fetch = []
249
265
  d = @c2.new(:id => 1, :parent_id=>555)
250
266
  MODEL_DB.sqls.should == []
251
267
  d.parent.should == nil
252
268
  MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.id = 555) LIMIT 1']
253
269
  d.parent.should == nil
254
- MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.id = 555) LIMIT 1']
270
+ MODEL_DB.sqls.should == []
255
271
  end
256
272
 
257
273
  it "should define a setter method" do
@@ -314,11 +330,9 @@ describe Sequel::Model, "many_to_one" do
314
330
  @c2.many_to_one :parent, :class => @c2
315
331
 
316
332
  d = @c2.load(:id => 1)
317
- MODEL_DB.reset
318
333
  d.parent_id = 234
319
334
  d.associations[:parent].should == nil
320
- ds = @c2.dataset
321
- def ds.fetch_rows(sql, &block); MODEL_DB.sqls << sql; yield({:id=>234}) end
335
+ @c2.dataset._fetch = {:id=>234}
322
336
  e = d.parent
323
337
  MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 234) LIMIT 1"]
324
338
  d.associations[:parent].should == e
@@ -357,13 +371,32 @@ describe Sequel::Model, "many_to_one" do
357
371
  MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 234) LIMIT 1"]
358
372
  end
359
373
 
374
+ it "should use a callback if given one as the argument" do
375
+ @c2.many_to_one :parent, :class => @c2
376
+
377
+ d = @c2.create(:id => 1)
378
+ MODEL_DB.reset
379
+ d.parent_id = 234
380
+ d.associations[:parent] = 42
381
+ d.parent(proc{|ds| ds.filter{name > 'M'}}).should_not == 42
382
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.id = 234) AND (name > 'M')) LIMIT 1"]
383
+ end
384
+
385
+ it "should use a block given to the association method as a callback on ruby 1.8.7+" do
386
+ @c2.many_to_one :parent, :class => @c2
387
+
388
+ d = @c2.create(:id => 1)
389
+ MODEL_DB.reset
390
+ d.parent_id = 234
391
+ d.associations[:parent] = 42
392
+ d.parent{|ds| ds.filter{name > 'M'}}.should_not == 42
393
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.id = 234) AND (name > 'M')) LIMIT 1"]
394
+ end if RUBY_VERSION >= '1.8.7'
395
+
360
396
  it "should have the setter add to the reciprocal one_to_many cached association list if it exists" do
361
397
  @c2.many_to_one :parent, :class => @c2
362
398
  @c2.one_to_many :children, :class => @c2, :key=>:parent_id
363
- ds = @c2.dataset
364
- def ds.fetch_rows(sql, &block)
365
- MODEL_DB.sqls << sql
366
- end
399
+ @c2.dataset._fetch = []
367
400
 
368
401
  d = @c2.new(:id => 1)
369
402
  e = @c2.new(:id => 2)
@@ -372,14 +405,13 @@ describe Sequel::Model, "many_to_one" do
372
405
  e.children.should_not(include(d))
373
406
  MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.parent_id = 2)']
374
407
 
375
- MODEL_DB.reset
376
408
  d = @c2.new(:id => 1)
377
409
  e = @c2.new(:id => 2)
378
410
  e.children.should_not(include(d))
379
411
  MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.parent_id = 2)']
380
412
  d.parent = e
381
413
  e.children.should(include(d))
382
- MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.parent_id = 2)']
414
+ MODEL_DB.sqls.should == []
383
415
  end
384
416
 
385
417
  it "should have many_to_one setter deal with a one_to_one reciprocal" do
@@ -407,10 +439,7 @@ describe Sequel::Model, "many_to_one" do
407
439
  it "should have the setter remove the object from the previous associated object's reciprocal one_to_many cached association list if it exists" do
408
440
  @c2.many_to_one :parent, :class => @c2
409
441
  @c2.one_to_many :children, :class => @c2, :key=>:parent_id
410
- ds = @c2.dataset
411
- def ds.fetch_rows(sql, &block)
412
- MODEL_DB.sqls << sql
413
- end
442
+ @c2.dataset._fetch = []
414
443
 
415
444
  d = @c2.new(:id => 1)
416
445
  e = @c2.new(:id => 2)
@@ -429,17 +458,9 @@ describe Sequel::Model, "many_to_one" do
429
458
  end
430
459
 
431
460
  it "should get all matching records and only return the first if :key option is set to nil" do
432
- c2 = @c2
433
461
  @c2.one_to_many :children, :class => @c2, :key=>:parent_id
434
- @c2.many_to_one :first_grand_parent, :class => @c2, :key=>nil, :eager_graph=>:children, :dataset=>proc{c2.filter(:children_id=>parent_id)}
435
- ds = @c2.dataset
436
- def ds.columns
437
- [:id, :parent_id, :par_parent_id, :blah]
438
- end
439
- def ds.fetch_rows(sql, &block)
440
- MODEL_DB.sqls << sql
441
- 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})
442
- end
462
+ @c2.many_to_one :first_grand_parent, :class => @c2, :key=>nil, :eager_graph=>:children, :dataset=>proc{model.filter(:children_id=>parent_id)}
463
+ @c2.dataset.columns(:id, :parent_id, :par_parent_id, :blah)._fetch = [{: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}, {}]
443
464
  p = @c2.new(:parent_id=>2)
444
465
  fgp = p.first_grand_parent
445
466
  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)"]
@@ -500,15 +521,15 @@ describe Sequel::Model, "many_to_one" do
500
521
  h = []
501
522
  @c2.many_to_one :parent, :class => @c2, :before_set=>[proc{|x,y| h << x.pk; h << (y ? -y.pk : :y)}, :blah], :after_set=>proc{h << 3}
502
523
  @c2.class_eval do
503
- class_variable_set(:@@blah, h)
524
+ self::Foo = h
504
525
  def []=(a, v)
505
- a == :parent_id ? (self.class.send(:class_variable_get, :@@blah) << (v ? 4 : 5)) : super
526
+ a == :parent_id ? (model::Foo << (v ? 4 : 5)) : super
506
527
  end
507
528
  def blah(x)
508
- self.class.send(:class_variable_get, :@@blah) << (x ? x.pk : :x)
529
+ model::Foo << (x ? x.pk : :x)
509
530
  end
510
531
  def blahr(x)
511
- self.class.send(:class_variable_get, :@@blah) << 6
532
+ model::Foo << 6
512
533
  end
513
534
  end
514
535
  p = @c2.load(:id=>10)
@@ -524,13 +545,11 @@ describe Sequel::Model, "many_to_one" do
524
545
  h = []
525
546
  @c2.many_to_one :parent, :class => @c2, :after_load=>[proc{|x,y| h << [x.pk, y.pk]}, :al]
526
547
  @c2.class_eval do
527
- class_variable_set(:@@blah, h)
548
+ self::Foo = h
528
549
  def al(v)
529
- self.class.send(:class_variable_get, :@@blah) << v.pk
530
- end
531
- def @dataset.fetch_rows(sql)
532
- yield({:id=>20})
550
+ model::Foo << v.pk
533
551
  end
552
+ dataset._fetch = {:id=>20}
534
553
  end
535
554
  p = @c2.load(:id=>10, :parent_id=>20)
536
555
  parent = p.parent
@@ -538,6 +557,18 @@ describe Sequel::Model, "many_to_one" do
538
557
  parent.pk.should == 20
539
558
  end
540
559
 
560
+ it "should support after_load association callback that changes the cached object" do
561
+ @c2.many_to_one :parent, :class => @c2, :after_load=>:al
562
+ @c2.class_eval do
563
+ def al(v)
564
+ associations[:parent] = :foo
565
+ end
566
+ end
567
+ p = @c2.load(:id=>10, :parent_id=>20)
568
+ p.parent.should == :foo
569
+ p.associations[:parent].should == :foo
570
+ end
571
+
541
572
  it "should raise error and not call internal add or remove method if before callback returns false, even if raise_on_save_failure is false" do
542
573
  # The reason for this is that assignment in ruby always returns the argument instead of the result
543
574
  # of the method, so we can't return nil to signal that the association callback prevented the modification
@@ -565,35 +596,32 @@ describe Sequel::Model, "many_to_one" do
565
596
  d = @c2.load(:id=>321)
566
597
  p = @c2.new
567
598
  p.associations[:parent] = d
568
- h = []
569
599
  @c2.many_to_one :parent, :class => @c2, :before_set=>:bs, :after_set=>:as
570
600
  @c2.class_eval do
571
- class_variable_set(:@@blah, h)
601
+ self::Foo = []
572
602
  def []=(a, v)
573
- a == :parent_id ? (self.class.send(:class_variable_get, :@@blah) << 5) : super
603
+ a == :parent_id ? (model::Foo << 5) : super
574
604
  end
575
605
  def bs(x)
576
- self.class.send(:class_variable_get, :@@blah) << x.pk
606
+ model::Foo << x.pk
577
607
  end
578
608
  def as(x)
579
- self.class.send(:class_variable_get, :@@blah) << x.pk * 2
609
+ model::Foo << x.pk * 2
580
610
  end
581
611
  end
582
612
  p.parent = c
583
- h.should == [123, 5, 246]
613
+ @c2::Foo.should == [123, 5, 246]
584
614
  end
585
615
  end
586
616
 
587
617
  describe Sequel::Model, "one_to_one" do
588
618
  before do
589
619
  @c1 = Class.new(Sequel::Model(:attributes)) do
590
- def _refresh(ds); end
591
620
  unrestrict_primary_key
592
621
  columns :id, :node_id, :y
593
622
  end
594
623
 
595
624
  @c2 = Class.new(Sequel::Model(:nodes)) do
596
- def _refresh(ds); end
597
625
  unrestrict_primary_key
598
626
  attr_accessor :xxx
599
627
 
@@ -602,24 +630,8 @@ describe Sequel::Model, "one_to_one" do
602
630
  columns :id, :x, :parent_id, :par_parent_id, :blah, :node_id
603
631
  end
604
632
  @dataset = @c2.dataset
605
-
606
- @c2.dataset.extend(Module.new {
607
- def empty?; false; end
608
- def fetch_rows(sql)
609
- @db << sql
610
- yield Hash.new
611
- end
612
- })
613
-
614
- @c1.dataset.extend(Module.new {
615
- def empty?; opts.has_key?(:empty) ? (super; true) : false; end
616
- def fetch_rows(sql)
617
- @db << sql
618
- yield Hash.new
619
- end
620
- })
621
-
622
- @dataset = @c2.dataset
633
+ @dataset._fetch = {}
634
+ @c1.dataset._fetch = {}
623
635
  MODEL_DB.reset
624
636
  end
625
637
 
@@ -641,26 +653,24 @@ describe Sequel::Model, "one_to_one" do
641
653
  it "should add a setter method" do
642
654
  @c2.one_to_one :attribute, :class => @c1
643
655
  attrib = @c1.new(:id=>3)
644
- d = @c1.dataset
645
- @c1.class_eval{remove_method :_refresh}
646
- def d.fetch_rows(s); yield({:id=>3}) end
656
+ @c1.dataset._fetch = {:id=>3}
647
657
  @c2.new(:id => 1234).attribute = attrib
658
+ sqls = MODEL_DB.sqls
648
659
  ['INSERT INTO attributes (node_id, id) VALUES (1234, 3)',
649
- 'INSERT INTO attributes (id, node_id) VALUES (3, 1234)'].should(include(MODEL_DB.sqls.last))
650
- MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))'
651
- MODEL_DB.sqls.length.should == 2
660
+ 'INSERT INTO attributes (id, node_id) VALUES (3, 1234)'].should(include(sqls.slice! 1))
661
+ sqls.should == ['UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))', "SELECT * FROM attributes WHERE (id = 3) LIMIT 1"]
662
+
652
663
  @c2.new(:id => 1234).attribute.should == attrib
653
- MODEL_DB.sqls.clear
654
664
  attrib = @c1.load(:id=>3)
655
665
  @c2.new(:id => 1234).attribute = attrib
656
- MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))',
666
+ MODEL_DB.sqls.should == ["SELECT * FROM attributes WHERE (attributes.node_id = 1234) LIMIT 1",
667
+ 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))',
657
668
  "UPDATE attributes SET node_id = 1234 WHERE (id = 3)"]
658
669
  end
659
670
 
660
671
  it "should use a transaction in the setter method" do
661
672
  @c2.one_to_one :attribute, :class => @c1
662
673
  @c2.use_transactions = true
663
- MODEL_DB.sqls.clear
664
674
  attrib = @c1.load(:id=>3)
665
675
  @c2.new(:id => 1234).attribute = attrib
666
676
  MODEL_DB.sqls.should == ['BEGIN',
@@ -673,7 +683,6 @@ describe Sequel::Model, "one_to_one" do
673
683
  @c2.one_to_one :attribute, :class => @c1, :conditions=>{:a=>1} do |ds|
674
684
  ds.filter(:b=>2)
675
685
  end
676
- MODEL_DB.sqls.clear
677
686
  attrib = @c1.load(:id=>3)
678
687
  @c2.new(:id => 1234).attribute = attrib
679
688
  MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (a = 1) AND (b = 2) AND (id != 3))',
@@ -683,30 +692,30 @@ describe Sequel::Model, "one_to_one" do
683
692
  it "should have the setter method respect the :primary_key option" do
684
693
  @c2.one_to_one :attribute, :class => @c1, :primary_key=>:xxx
685
694
  attrib = @c1.new(:id=>3)
686
- d = @c1.dataset
687
- @c1.class_eval{remove_method :_refresh}
688
- def d.fetch_rows(s); yield({:id=>3}) end
695
+ @c1.dataset._fetch = {:id=>3}
689
696
  @c2.new(:id => 1234, :xxx=>5).attribute = attrib
697
+ sqls = MODEL_DB.sqls
690
698
  ['INSERT INTO attributes (node_id, id) VALUES (5, 3)',
691
- 'INSERT INTO attributes (id, node_id) VALUES (3, 5)'].should(include(MODEL_DB.sqls.last))
692
- MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))'
693
- MODEL_DB.sqls.length.should == 2
699
+ 'INSERT INTO attributes (id, node_id) VALUES (3, 5)'].should(include(sqls.slice! 1))
700
+ sqls.should == ['UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))', "SELECT * FROM attributes WHERE (id = 3) LIMIT 1"]
701
+
694
702
  @c2.new(:id => 321, :xxx=>5).attribute.should == attrib
695
- MODEL_DB.sqls.clear
696
703
  attrib = @c1.load(:id=>3)
697
704
  @c2.new(:id => 621, :xxx=>5).attribute = attrib
698
- MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))',
705
+ MODEL_DB.sqls.should == ["SELECT * FROM attributes WHERE (attributes.node_id = 5) LIMIT 1",
706
+ 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))',
699
707
  'UPDATE attributes SET node_id = 5 WHERE (id = 3)']
700
708
  end
701
709
 
702
710
  it "should have the setter method respect composite keys" do
703
711
  @c2.one_to_one :attribute, :class => @c1, :key=>[:node_id, :y], :primary_key=>[:id, :x]
704
712
  attrib = @c1.load(:id=>3, :y=>6)
705
- d = @c1.dataset
706
- def d.fetch_rows(s); yield({:id=>3, :y=>6}) end
713
+ @c1.dataset._fetch = {:id=>3, :y=>6}
707
714
  @c2.load(:id => 1234, :x=>5).attribute = attrib
708
- MODEL_DB.sqls.last.should =~ /UPDATE attributes SET (node_id = 1234|y = 5), (node_id = 1234|y = 5) WHERE \(id = 3\)/
709
- MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id|y) = NULL, (node_id|y) = NULL WHERE \(\(node_id = 1234\) AND \(y = 5\) AND \(id != 3\)\)/
715
+ sqls = MODEL_DB.sqls
716
+ sqls.last.should =~ /UPDATE attributes SET (node_id = 1234|y = 5), (node_id = 1234|y = 5) WHERE \(id = 3\)/
717
+ sqls.first.should =~ /UPDATE attributes SET (node_id|y) = NULL, (node_id|y) = NULL WHERE \(\(node_id = 1234\) AND \(y = 5\) AND \(id != 3\)\)/
718
+ sqls.length.should == 2
710
719
  end
711
720
 
712
721
  it "should use implicit key if omitted" do
@@ -721,31 +730,27 @@ describe Sequel::Model, "one_to_one" do
721
730
  end
722
731
 
723
732
  it "should use implicit class if omitted" do
724
- class ::ParParent < Sequel::Model
733
+ begin
734
+ class ::ParParent < Sequel::Model; end
735
+ @c2.one_to_one :par_parent
736
+ @c2.new(:id => 234).par_parent.class.should == ParParent
737
+ MODEL_DB.sqls.should == ["SELECT * FROM par_parents WHERE (par_parents.node_id = 234) LIMIT 1"]
738
+ ensure
739
+ Object.send(:remove_const, :ParParent)
725
740
  end
726
-
727
- @c2.one_to_one :par_parent
728
-
729
- d = @c2.new(:id => 234)
730
- p = d.par_parent
731
- p.class.should == ParParent
732
-
733
- MODEL_DB.sqls.should == ["SELECT * FROM par_parents WHERE (par_parents.node_id = 234) LIMIT 1"]
734
741
  end
735
742
 
736
743
  it "should use class inside module if given as a string" do
737
- module ::Par
738
- class Parent < Sequel::Model
744
+ begin
745
+ module ::Par
746
+ class Parent < Sequel::Model; end
739
747
  end
748
+ @c2.one_to_one :par_parent, :class=>"Par::Parent"
749
+ @c2.new(:id => 234).par_parent.class.should == Par::Parent
750
+ MODEL_DB.sqls.should == ["SELECT * FROM parents WHERE (parents.node_id = 234) LIMIT 1"]
751
+ ensure
752
+ Object.send(:remove_const, :Par)
740
753
  end
741
-
742
- @c2.one_to_one :par_parent, :class=>"Par::Parent"
743
-
744
- d = @c2.new(:id => 234)
745
- p = d.par_parent
746
- p.class.should == Par::Parent
747
-
748
- MODEL_DB.sqls.should == ["SELECT * FROM parents WHERE (parents.node_id = 234) LIMIT 1"]
749
754
  end
750
755
 
751
756
  it "should use explicit key if given" do
@@ -796,14 +801,12 @@ describe Sequel::Model, "one_to_one" do
796
801
  MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.node_id = 567) AND (a = 32)) LIMIT 1"]
797
802
 
798
803
  @c2.one_to_one :parent, :class => @c2, :conditions=>:a
799
- MODEL_DB.sqls.clear
800
804
  @c2.new(:id => 567).parent
801
805
  MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.node_id = 567) AND a) LIMIT 1"]
802
806
  end
803
807
 
804
808
  it "should support :order, :limit (only for offset), and :dataset options, as well as a block" do
805
- c2 = @c2
806
- @c2.one_to_one :child_20, :class => @c2, :key=>:id, :dataset=>proc{c2.filter(:parent_id=>pk)}, :limit=>[10,20], :order=>:name do |ds|
809
+ @c2.one_to_one :child_20, :class => @c2, :key=>:id, :dataset=>proc{model.filter(:parent_id=>pk)}, :limit=>[10,20], :order=>:name do |ds|
807
810
  ds.filter(:x.sql_number > 1)
808
811
  end
809
812
  @c2.load(:id => 100).child_20
@@ -813,24 +816,19 @@ describe Sequel::Model, "one_to_one" do
813
816
  it "should return nil if primary_key value is nil" do
814
817
  @c2.one_to_one :parent, :class => @c2, :primary_key=>:node_id
815
818
 
816
- d = @c2.new(:id => 1)
817
- d.parent.should == nil
819
+ @c2.new(:id => 1).parent.should be_nil
818
820
  MODEL_DB.sqls.should == []
819
821
  end
820
822
 
821
823
  it "should cache negative lookup" do
822
824
  @c2.one_to_one :parent, :class => @c2
823
- ds = @c2.dataset
824
- def ds.fetch_rows(sql, &block)
825
- MODEL_DB.sqls << sql
826
- end
827
-
825
+ @c2.dataset._fetch = []
828
826
  d = @c2.new(:id => 555)
829
827
  MODEL_DB.sqls.should == []
830
828
  d.parent.should == nil
831
829
  MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.node_id = 555) LIMIT 1']
832
830
  d.parent.should == nil
833
- MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.node_id = 555) LIMIT 1']
831
+ MODEL_DB.sqls.should == []
834
832
  end
835
833
 
836
834
  it "should define a setter method" do
@@ -838,28 +836,44 @@ describe Sequel::Model, "one_to_one" do
838
836
 
839
837
  d = @c2.new(:id => 1)
840
838
  f = @c2.new(:id => 3, :node_id=> 4321)
839
+ @c2.dataset._fetch = {:id => 3, :node_id=>1}
841
840
  d.parent = f
842
841
  f.values.should == {:id => 3, :node_id=>1}
843
842
  d.parent.should == f
843
+ sqls = MODEL_DB.sqls
844
+ ["INSERT INTO nodes (node_id, id) VALUES (1, 3)",
845
+ "INSERT INTO nodes (id, node_id) VALUES (3, 1)"].should include(sqls.slice! 1)
846
+ sqls.should == ["UPDATE nodes SET node_id = NULL WHERE ((node_id = 1) AND (id != 3))", "SELECT * FROM nodes WHERE (id = 3) LIMIT 1"]
844
847
 
845
848
  d.parent = nil
846
849
  d.parent.should == nil
850
+ MODEL_DB.sqls.should == ["UPDATE nodes SET node_id = NULL WHERE (node_id = 1)"]
847
851
  end
848
852
 
849
853
  it "should have the setter method respect the :primary_key option" do
850
854
  @c2.one_to_one :parent, :class => @c2, :primary_key=>:blah
851
855
  d = @c2.new(:id => 1, :blah => 3)
852
856
  e = @c2.new(:id => 4321, :node_id=>444)
857
+ @c2.dataset._fetch = {:id => 4321, :node_id => 3}
853
858
  d.parent = e
854
859
  e.values.should == {:id => 4321, :node_id => 3}
860
+ sqls = MODEL_DB.sqls
861
+ ["INSERT INTO nodes (node_id, id) VALUES (3, 4321)",
862
+ "INSERT INTO nodes (id, node_id) VALUES (4321, 3)"].should include(sqls.slice! 1)
863
+ sqls.should == ["UPDATE nodes SET node_id = NULL WHERE ((node_id = 3) AND (id != 4321))", "SELECT * FROM nodes WHERE (id = 4321) LIMIT 1"]
855
864
  end
856
865
 
857
866
  it "should have the setter method respect the :key option" do
858
867
  @c2.one_to_one :parent, :class => @c2, :key=>:blah
859
868
  d = @c2.new(:id => 3)
860
869
  e = @c2.new(:id => 4321, :blah=>444)
870
+ @c2.dataset._fetch = {:id => 4321, :blah => 3}
861
871
  d.parent = e
862
872
  e.values.should == {:id => 4321, :blah => 3}
873
+ sqls = MODEL_DB.sqls
874
+ ["INSERT INTO nodes (blah, id) VALUES (3, 4321)",
875
+ "INSERT INTO nodes (id, blah) VALUES (4321, 3)"].should include(sqls.slice! 1)
876
+ sqls.should == ["UPDATE nodes SET blah = NULL WHERE ((blah = 3) AND (id != 4321))", "SELECT * FROM nodes WHERE (id = 4321) LIMIT 1"]
863
877
  end
864
878
 
865
879
  it "should persist changes to associated object when the setter is called" do
@@ -875,12 +889,11 @@ describe Sequel::Model, "one_to_one" do
875
889
 
876
890
  d = @c2.load(:id => 1)
877
891
  d.associations[:parent].should == nil
878
- ds = @c2.dataset
879
- def ds.fetch_rows(sql, &block); MODEL_DB.sqls << sql; yield({:id=>234}) end
892
+ @c2.dataset._fetch = {:id=>234}
880
893
  e = d.parent
881
894
  MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.node_id = 1) LIMIT 1"]
882
895
  d.parent
883
- MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.node_id = 1) LIMIT 1"]
896
+ MODEL_DB.sqls.should == []
884
897
  d.associations[:parent].should == e
885
898
  end
886
899
 
@@ -922,7 +935,6 @@ describe Sequel::Model, "one_to_one" do
922
935
  e.child.should == d
923
936
  MODEL_DB.sqls.should == ["UPDATE nodes SET parent_id = NULL WHERE ((parent_id = 1) AND (id != 2))",
924
937
  "UPDATE nodes SET parent_id = 1 WHERE (id = 2)"]
925
- MODEL_DB.reset
926
938
  d.parent = nil
927
939
  e.child.should == nil
928
940
  MODEL_DB.sqls.should == ["UPDATE nodes SET parent_id = NULL WHERE (parent_id = 1)"]
@@ -931,17 +943,13 @@ describe Sequel::Model, "one_to_one" do
931
943
  it "should have the setter remove the object from the previous associated object's reciprocal many_to_one cached association list if it exists" do
932
944
  @c2.one_to_one :parent, :class => @c2, :key=>:parent_id
933
945
  @c2.many_to_one :child, :class => @c2, :key=>:parent_id
934
- ds = @c2.dataset
935
- def ds.fetch_rows(sql, &block)
936
- MODEL_DB.sqls << sql
937
- end
946
+ @c2.dataset._fetch = []
938
947
 
939
948
  d = @c2.load(:id => 1)
940
949
  e = @c2.load(:id => 2)
941
950
  f = @c2.load(:id => 3)
942
951
  e.child.should == nil
943
952
  f.child.should == nil
944
- MODEL_DB.reset
945
953
  d.parent = e
946
954
  e.child.should == d
947
955
  d.parent = f
@@ -983,12 +991,12 @@ describe Sequel::Model, "one_to_one" do
983
991
  h = []
984
992
  @c2.one_to_one :parent, :class => @c2, :before_set=>[proc{|x,y| h << x.pk; h << (y ? -y.pk : :y)}, :blah], :after_set=>proc{h << 3}
985
993
  @c2.class_eval do
986
- class_variable_set(:@@blah, h)
994
+ self::Foo = h
987
995
  def blah(x)
988
- self.class.send(:class_variable_get, :@@blah) << (x ? x.pk : :x)
996
+ model::Foo << (x ? x.pk : :x)
989
997
  end
990
998
  def blahr(x)
991
- self.class.send(:class_variable_get, :@@blah) << 6
999
+ model::Foo << 6
992
1000
  end
993
1001
  end
994
1002
  p = @c2.load(:id=>10)
@@ -1004,13 +1012,11 @@ describe Sequel::Model, "one_to_one" do
1004
1012
  h = []
1005
1013
  @c2.one_to_one :parent, :class => @c2, :after_load=>[proc{|x,y| h << [x.pk, y.pk]}, :al]
1006
1014
  @c2.class_eval do
1007
- class_variable_set(:@@blah, h)
1015
+ self::Foo = h
1008
1016
  def al(v)
1009
- self.class.send(:class_variable_get, :@@blah) << v.pk
1010
- end
1011
- def @dataset.fetch_rows(sql)
1012
- yield({:id=>20})
1017
+ model::Foo << v.pk
1013
1018
  end
1019
+ @dataset._fetch = {:id=>20}
1014
1020
  end
1015
1021
  p = @c2.load(:id=>10)
1016
1022
  parent = p.parent
@@ -1048,15 +1054,15 @@ describe Sequel::Model, "one_to_one" do
1048
1054
  h = []
1049
1055
  @c2.one_to_one :parent, :class => @c2, :before_set=>:bs, :after_set=>:as
1050
1056
  @c2.class_eval do
1051
- class_variable_set(:@@blah, h)
1057
+ self::Foo = h
1052
1058
  def []=(a, v)
1053
- a == :node_id ? (self.class.send(:class_variable_get, :@@blah) << 5) : super
1059
+ a == :node_id ? (model::Foo << 5) : super
1054
1060
  end
1055
1061
  def bs(x)
1056
- self.class.send(:class_variable_get, :@@blah) << x.pk
1062
+ model::Foo << x.pk
1057
1063
  end
1058
1064
  def as(x)
1059
- self.class.send(:class_variable_get, :@@blah) << x.pk * 2
1065
+ model::Foo << x.pk * 2
1060
1066
  end
1061
1067
  end
1062
1068
  p.parent = c
@@ -1072,16 +1078,9 @@ end
1072
1078
 
1073
1079
  describe Sequel::Model, "one_to_many" do
1074
1080
  before do
1075
- MODEL_DB.reset
1076
-
1077
1081
  @c1 = Class.new(Sequel::Model(:attributes)) do
1078
- def _refresh(ds); end
1079
1082
  unrestrict_primary_key
1080
1083
  columns :id, :node_id, :y, :z
1081
-
1082
- def self.[](id)
1083
- load(id.is_a?(Array) ? {:id => id[0], :z => id[1]} : {:id => id})
1084
- end
1085
1084
  end
1086
1085
 
1087
1086
  @c2 = Class.new(Sequel::Model(:nodes)) do
@@ -1094,68 +1093,58 @@ describe Sequel::Model, "one_to_many" do
1094
1093
  columns :id, :x
1095
1094
  end
1096
1095
  @dataset = @c2.dataset
1097
-
1098
- @c2.dataset.extend(Module.new {
1099
- def empty?; false; end
1100
- def fetch_rows(sql)
1101
- @db << sql
1102
- yield Hash.new
1103
- end
1104
- })
1105
-
1106
- @c1.dataset.extend(Module.new {
1107
- def empty?; opts.has_key?(:empty) ? (super; true) : false; end
1108
- def fetch_rows(sql)
1109
- @db << sql
1110
- yield Hash.new
1111
- end
1112
- })
1096
+ @dataset._fetch = {}
1097
+ @c1.dataset._fetch = proc{|sql| sql =~ /SELECT 1/ ? {:a=>1} : {}}
1098
+ MODEL_DB.reset
1113
1099
  end
1114
1100
 
1115
1101
  it "should use implicit key if omitted" do
1116
1102
  @c2.one_to_many :attributes, :class => @c1
1117
-
1118
- n = @c2.new(:id => 1234)
1119
- a = n.attributes_dataset
1120
- a.should be_a_kind_of(Sequel::Dataset)
1121
- a.sql.should == 'SELECT * FROM attributes WHERE (attributes.node_id = 1234)'
1103
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE (attributes.node_id = 1234)'
1122
1104
  end
1123
1105
 
1124
1106
  it "should use implicit class if omitted" do
1125
- class ::HistoricalValue < Sequel::Model
1107
+ begin
1108
+ class ::HistoricalValue < Sequel::Model; end
1109
+ @c2.one_to_many :historical_values
1110
+
1111
+ v = @c2.new(:id => 1234).historical_values_dataset
1112
+ v.should be_a_kind_of(Sequel::Dataset)
1113
+ v.sql.should == 'SELECT * FROM historical_values WHERE (historical_values.node_id = 1234)'
1114
+ v.model.should == HistoricalValue
1115
+ ensure
1116
+ Object.send(:remove_const, :HistoricalValue)
1126
1117
  end
1127
-
1128
- @c2.one_to_many :historical_values
1129
-
1130
- n = @c2.new(:id => 1234)
1131
- v = n.historical_values_dataset
1132
- v.should be_a_kind_of(Sequel::Dataset)
1133
- v.sql.should == 'SELECT * FROM historical_values WHERE (historical_values.node_id = 1234)'
1134
- v.model.should == HistoricalValue
1135
1118
  end
1136
1119
 
1137
1120
  it "should use class inside a module if given as a string" do
1138
- module ::Historical
1139
- class Value < Sequel::Model
1121
+ begin
1122
+ module ::Historical
1123
+ class Value < Sequel::Model; end
1140
1124
  end
1125
+ @c2.one_to_many :historical_values, :class=>'Historical::Value'
1126
+
1127
+ v = @c2.new(:id => 1234).historical_values_dataset
1128
+ v.should be_a_kind_of(Sequel::Dataset)
1129
+ v.sql.should == 'SELECT * FROM values WHERE (values.node_id = 1234)'
1130
+ v.model.should == Historical::Value
1131
+ ensure
1132
+ Object.send(:remove_const, :Historical)
1141
1133
  end
1142
-
1143
- @c2.one_to_many :historical_values, :class=>'Historical::Value'
1144
-
1145
- n = @c2.new(:id => 1234)
1146
- v = n.historical_values_dataset
1147
- v.should be_a_kind_of(Sequel::Dataset)
1148
- v.sql.should == 'SELECT * FROM values WHERE (values.node_id = 1234)'
1149
- v.model.should == Historical::Value
1150
1134
  end
1151
1135
 
1152
- it "should use explicit key if given" do
1136
+ it "should use a callback if given one as the argument" do
1153
1137
  @c2.one_to_many :attributes, :class => @c1, :key => :nodeid
1154
1138
 
1155
- n = @c2.new(:id => 1234)
1156
- a = n.attributes_dataset
1157
- a.should be_a_kind_of(Sequel::Dataset)
1158
- a.sql.should == 'SELECT * FROM attributes WHERE (attributes.nodeid = 1234)'
1139
+ d = @c2.load(:id => 1234)
1140
+ d.associations[:attributes] = []
1141
+ d.attributes(proc{|ds| ds.filter{name > 'M'}}).should_not == []
1142
+ MODEL_DB.sqls.should == ["SELECT * FROM attributes WHERE ((attributes.nodeid = 1234) AND (name > 'M'))"]
1143
+ end
1144
+
1145
+ it "should use explicit key if given" do
1146
+ @c2.one_to_many :attributes, :class => @c1, :key => :nodeid
1147
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE (attributes.nodeid = 1234)'
1159
1148
  end
1160
1149
 
1161
1150
  it "should support_composite keys" do
@@ -1180,10 +1169,8 @@ describe Sequel::Model, "one_to_many" do
1180
1169
  it "should define an add_ method that works on existing records" do
1181
1170
  @c2.one_to_many :attributes, :class => @c1
1182
1171
 
1183
- n = @c2.new(:id => 1234)
1184
- a = @c1.new(:id => 2345)
1185
- a.save
1186
- MODEL_DB.reset
1172
+ n = @c2.load(:id => 1234)
1173
+ a = @c1.load(:id => 2345)
1187
1174
  a.should == n.add_attribute(a)
1188
1175
  a.values.should == {:node_id => 1234, :id => 2345}
1189
1176
  MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 1234 WHERE (id = 2345)']
@@ -1192,52 +1179,53 @@ describe Sequel::Model, "one_to_many" do
1192
1179
  it "should define an add_ method that works on new records" do
1193
1180
  @c2.one_to_many :attributes, :class => @c1
1194
1181
 
1195
- n = @c2.new(:id => 1234)
1182
+ n = @c2.load(:id => 1234)
1196
1183
  a = @c1.new(:id => 234)
1197
- # do not save
1198
- MODEL_DB.reset
1184
+ @c1.dataset._fetch = {:node_id => 1234, :id => 234}
1199
1185
  a.should == n.add_attribute(a)
1200
- MODEL_DB.sqls.first.should =~ /INSERT INTO attributes \((node_)?id, (node_)?id\) VALUES \(1?234, 1?234\)/
1186
+ sqls = MODEL_DB.sqls
1187
+ sqls.shift.should =~ /INSERT INTO attributes \((node_)?id, (node_)?id\) VALUES \(1?234, 1?234\)/
1188
+ sqls.should == ["SELECT * FROM attributes WHERE (id = 234) LIMIT 1"]
1201
1189
  a.values.should == {:node_id => 1234, :id => 234}
1202
1190
  end
1203
1191
 
1204
1192
  it "should define a remove_ method that works on existing records" do
1205
1193
  @c2.one_to_many :attributes, :class => @c1
1206
1194
 
1207
- n = @c2.new(:id => 1234)
1208
- a = @c1.new(:id => 2345, :node_id => 1234)
1209
- a.save
1210
- MODEL_DB.reset
1195
+ n = @c2.load(:id => 1234)
1196
+ a = @c1.load(:id => 2345, :node_id => 1234)
1211
1197
  a.should == n.remove_attribute(a)
1212
1198
  a.values.should == {:node_id => nil, :id => 2345}
1213
- MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE (id = 2345)']
1199
+ MODEL_DB.sqls.should == ["SELECT 1 AS one FROM attributes WHERE ((attributes.node_id = 1234) AND (id = 2345)) LIMIT 1", 'UPDATE attributes SET node_id = NULL WHERE (id = 2345)']
1214
1200
  end
1215
1201
 
1216
1202
  it "should have the remove_ method raise an error if the passed object is not already associated" do
1217
1203
  @c2.one_to_many :attributes, :class => @c1
1218
- @c1.dataset.opts[:empty] = true
1219
1204
 
1220
1205
  n = @c2.new(:id => 1234)
1221
1206
  a = @c1.load(:id => 2345, :node_id => 1234)
1222
- MODEL_DB.reset
1207
+ @c1.dataset._fetch = []
1223
1208
  proc{n.remove_attribute(a)}.should raise_error(Sequel::Error)
1224
- MODEL_DB.sqls.should == ["SELECT 1 FROM attributes WHERE ((attributes.node_id = 1234) AND (id = 2345)) LIMIT 1"]
1209
+ MODEL_DB.sqls.should == ["SELECT 1 AS one FROM attributes WHERE ((attributes.node_id = 1234) AND (id = 2345)) LIMIT 1"]
1225
1210
  end
1226
1211
 
1227
1212
  it "should accept a hash for the add_ method and create a new record" do
1228
1213
  @c2.one_to_many :attributes, :class => @c1
1229
1214
  n = @c2.new(:id => 1234)
1230
1215
  MODEL_DB.reset
1231
- @c1.load(:node_id => 1234, :id => 234).should == n.add_attribute(:id => 234)
1232
- MODEL_DB.sqls.first.should =~ /INSERT INTO attributes \((node_)?id, (node_)?id\) VALUES \(1?234, 1?234\)/
1216
+ @c1.dataset._fetch = {:node_id => 1234, :id => 234}
1217
+ n.add_attribute(:id => 234).should == @c1.load(:node_id => 1234, :id => 234)
1218
+ sqls = MODEL_DB.sqls
1219
+ sqls.shift.should =~ /INSERT INTO attributes \((node_)?id, (node_)?id\) VALUES \(1?234, 1?234\)/
1220
+ sqls.should == ["SELECT * FROM attributes WHERE (id = 234) LIMIT 1"]
1233
1221
  end
1234
1222
 
1235
1223
  it "should accept a primary key for the add_ method" do
1236
1224
  @c2.one_to_many :attributes, :class => @c1
1237
1225
  n = @c2.new(:id => 1234)
1238
- MODEL_DB.reset
1239
- @c1.load(:node_id => 1234, :id => 234).should == n.add_attribute(234)
1240
- MODEL_DB.sqls.first.should == "UPDATE attributes SET node_id = 1234 WHERE (id = 234)"
1226
+ @c1.dataset._fetch = {:id=>234, :node_id=>nil}
1227
+ n.add_attribute(234).should == @c1.load(:node_id => 1234, :id => 234)
1228
+ MODEL_DB.sqls.should == ["SELECT * FROM attributes WHERE id = 234", "UPDATE attributes SET node_id = 1234 WHERE (id = 234)"]
1241
1229
  end
1242
1230
 
1243
1231
  it "should raise an error in the add_ method if the passed associated object is not of the correct type" do
@@ -1248,13 +1236,8 @@ describe Sequel::Model, "one_to_many" do
1248
1236
  it "should accept a primary key for the remove_ method and remove an existing record" do
1249
1237
  @c2.one_to_many :attributes, :class => @c1
1250
1238
  n = @c2.new(:id => 1234)
1251
- ds = @c1.dataset
1252
- def ds.fetch_rows(sql)
1253
- db << sql
1254
- yield({:id=>234, :node_id=>1234})
1255
- end
1256
- MODEL_DB.reset
1257
- @c1.load(:node_id => nil, :id => 234).should == n.remove_attribute(234)
1239
+ @c1.dataset._fetch = {:id=>234, :node_id=>1234}
1240
+ n.remove_attribute(234).should == @c1.load(:node_id => nil, :id => 234)
1258
1241
  MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (attributes.id = 234)) LIMIT 1',
1259
1242
  'UPDATE attributes SET node_id = NULL WHERE (id = 234)']
1260
1243
  end
@@ -1268,10 +1251,8 @@ describe Sequel::Model, "one_to_many" do
1268
1251
  @c2.one_to_many :attributes, :class => @c1, :primary_key=>:xxx
1269
1252
 
1270
1253
  n = @c2.new(:id => 1234, :xxx=>5)
1271
- a = @c1.new(:id => 2345)
1272
- a.save
1273
- MODEL_DB.reset
1274
- a.should == n.add_attribute(a)
1254
+ a = @c1.load(:id => 2345)
1255
+ n.add_attribute(a).should == a
1275
1256
  MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 5 WHERE (id = 2345)']
1276
1257
  end
1277
1258
 
@@ -1279,15 +1260,13 @@ describe Sequel::Model, "one_to_many" do
1279
1260
  @c2.one_to_many :attributes, :class => @c1
1280
1261
 
1281
1262
  n = @c2.new(:id => 1234)
1282
- a = @c1.new(:id => 2345)
1283
- a.save
1284
- MODEL_DB.reset
1263
+ a = @c1.load(:id => 2345)
1285
1264
  n.associations[:attributes] = []
1286
1265
  a.should == n.add_attribute(a)
1287
1266
  a.should == n.add_attribute(a)
1288
1267
  a.values.should == {:node_id => 1234, :id => 2345}
1289
- MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 1234 WHERE (id = 2345)'] * 2
1290
1268
  n.attributes.should == [a]
1269
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 1234 WHERE (id = 2345)'] * 2
1291
1270
  end
1292
1271
 
1293
1272
  it "should have add_ method respect composite keys" do
@@ -1295,18 +1274,24 @@ describe Sequel::Model, "one_to_many" do
1295
1274
 
1296
1275
  n = @c2.load(:id => 1234, :x=>5)
1297
1276
  a = @c1.load(:id => 2345)
1298
- a.should == n.add_attribute(a)
1299
- MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id = 1234|y = 5), (node_id = 1234|y = 5) WHERE \(id = 2345\)/
1277
+ n.add_attribute(a).should == a
1278
+ sqls = MODEL_DB.sqls
1279
+ sqls.shift.should =~ /UPDATE attributes SET (node_id = 1234|y = 5), (node_id = 1234|y = 5) WHERE \(id = 2345\)/
1280
+ sqls.should == []
1300
1281
  end
1301
1282
 
1302
1283
  it "should have add_ method accept a composite key" do
1303
1284
  @c1.set_primary_key :id, :z
1304
1285
  @c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :y], :primary_key=>[:id, :x]
1286
+ @c1.dataset._fetch = {:id => 2345, :z => 8, :node_id => 1234, :y=>5}
1305
1287
 
1306
1288
  n = @c2.load(:id => 1234, :x=>5)
1307
1289
  a = @c1.load(:id => 2345, :z => 8, :node_id => 1234, :y=>5)
1308
- a.should == n.add_attribute([2345, 8])
1309
- MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id|y) = (1234|5), (node_id|y) = (1234|5) WHERE \(\((id|z) = (2345|8)\) AND \((id|z) = (2345|8)\)\)/
1290
+ n.add_attribute([2345, 8]).should == a
1291
+ sqls = MODEL_DB.sqls
1292
+ sqls.shift.should =~ /SELECT \* FROM attributes WHERE \(\((id|z) = (2345|8)\) AND \((id|z) = (2345|8)\)\) LIMIT 1/
1293
+ sqls.shift.should =~ /UPDATE attributes SET (node_id|y) = (1234|5), (node_id|y) = (1234|5) WHERE \(\((id|z) = (2345|8)\) AND \((id|z) = (2345|8)\)\)/
1294
+ sqls.should == []
1310
1295
  end
1311
1296
 
1312
1297
  it "should have remove_ method respect composite keys" do
@@ -1314,31 +1299,29 @@ describe Sequel::Model, "one_to_many" do
1314
1299
 
1315
1300
  n = @c2.load(:id => 1234, :x=>5)
1316
1301
  a = @c1.load(:id => 2345, :node_id=>1234, :y=>5)
1317
- a.should == n.remove_attribute(a)
1318
- MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id|y) = NULL, (node_id|y) = NULL WHERE \(id = 2345\)/
1302
+ n.remove_attribute(a).should == a
1303
+ sqls = MODEL_DB.sqls
1304
+ sqls.pop.should =~ /UPDATE attributes SET (node_id|y) = NULL, (node_id|y) = NULL WHERE \(id = 2345\)/
1305
+ sqls.should == ["SELECT 1 AS one FROM attributes WHERE ((attributes.node_id = 1234) AND (attributes.y = 5) AND (id = 2345)) LIMIT 1"]
1319
1306
  end
1320
1307
 
1321
1308
  it "should accept a array of composite primary key values for the remove_ method and remove an existing record" do
1322
1309
  @c1.set_primary_key :id, :y
1323
1310
  @c2.one_to_many :attributes, :class => @c1, :key=>:node_id, :primary_key=>:id
1324
1311
  n = @c2.new(:id => 123)
1325
- ds = @c1.dataset
1326
- def ds.fetch_rows(sql)
1327
- db << sql
1328
- yield({:id=>234, :node_id=>123, :y=>5})
1329
- end
1330
- MODEL_DB.reset
1331
- @c1.load(:node_id => nil, :y => 5, :id => 234).should == n.remove_attribute([234, 5])
1332
- MODEL_DB.sqls.length.should == 2
1333
- MODEL_DB.sqls.first.should =~ /SELECT \* FROM attributes WHERE \(\(attributes.node_id = 123\) AND \(attributes\.(id|y) = (234|5)\) AND \(attributes\.(id|y) = (234|5)\)\) LIMIT 1/
1334
- MODEL_DB.sqls.last.should =~ /UPDATE attributes SET node_id = NULL WHERE \(\((id|y) = (234|5)\) AND \((id|y) = (234|5)\)\)/
1312
+ @c1.dataset._fetch = {:id=>234, :node_id=>123, :y=>5}
1313
+ n.remove_attribute([234, 5]).should == @c1.load(:node_id => nil, :y => 5, :id => 234)
1314
+ sqls = MODEL_DB.sqls
1315
+ sqls.length.should == 2
1316
+ sqls.first.should =~ /SELECT \* FROM attributes WHERE \(\(attributes.node_id = 123\) AND \(attributes\.(id|y) = (234|5)\) AND \(attributes\.(id|y) = (234|5)\)\) LIMIT 1/
1317
+ sqls.last.should =~ /UPDATE attributes SET node_id = NULL WHERE \(\((id|y) = (234|5)\) AND \((id|y) = (234|5)\)\)/
1335
1318
  end
1336
1319
 
1337
1320
  it "should raise an error in add_ and remove_ if the passed object returns false to save (is not valid)" do
1338
1321
  @c2.one_to_many :attributes, :class => @c1
1339
1322
  n = @c2.new(:id => 1234)
1340
1323
  a = @c1.new(:id => 2345)
1341
- def a.valid?(opts); false; end
1324
+ def a.validate() errors.add(:id, 'foo') end
1342
1325
  proc{n.add_attribute(a)}.should raise_error(Sequel::Error)
1343
1326
  proc{n.remove_attribute(a)}.should raise_error(Sequel::Error)
1344
1327
  end
@@ -1347,7 +1330,7 @@ describe Sequel::Model, "one_to_many" do
1347
1330
  @c2.one_to_many :attributes, :class => @c1, :validate=>false
1348
1331
  n = @c2.new(:id => 1234)
1349
1332
  a = @c1.new(:id => 2345)
1350
- def a.valid?(opts); false; end
1333
+ def a.validate() errors.add(:id, 'foo') end
1351
1334
  n.add_attribute(a).should == a
1352
1335
  n.remove_attribute(a).should == a
1353
1336
  end
@@ -1364,81 +1347,48 @@ describe Sequel::Model, "one_to_many" do
1364
1347
 
1365
1348
  it "should use :primary_key option if given" do
1366
1349
  @c1.one_to_many :nodes, :class => @c2, :primary_key => :node_id, :key=>:id
1367
- n = @c1.load(:id => 1234, :node_id=>4321)
1368
- n.nodes_dataset.sql.should == "SELECT * FROM nodes WHERE (nodes.id = 4321)"
1350
+ @c1.load(:id => 1234, :node_id=>4321).nodes_dataset.sql.should == "SELECT * FROM nodes WHERE (nodes.id = 4321)"
1369
1351
  end
1370
1352
 
1371
1353
  it "should support a select option" do
1372
1354
  @c2.one_to_many :attributes, :class => @c1, :select => [:id, :name]
1373
-
1374
- n = @c2.new(:id => 1234)
1375
- n.attributes_dataset.sql.should == "SELECT id, name FROM attributes WHERE (attributes.node_id = 1234)"
1355
+ @c2.new(:id => 1234).attributes_dataset.sql.should == "SELECT id, name FROM attributes WHERE (attributes.node_id = 1234)"
1376
1356
  end
1377
1357
 
1378
1358
  it "should support a conditions option" do
1379
1359
  @c2.one_to_many :attributes, :class => @c1, :conditions => {:a=>32}
1380
- n = @c2.new(:id => 1234)
1381
- n.attributes_dataset.sql.should == "SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (a = 32))"
1360
+ @c2.new(:id => 1234).attributes_dataset.sql.should == "SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (a = 32))"
1382
1361
  @c2.one_to_many :attributes, :class => @c1, :conditions => ~:a
1383
- n = @c2.new(:id => 1234)
1384
- n.attributes_dataset.sql.should == "SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND NOT a)"
1362
+ @c2.new(:id => 1234).attributes_dataset.sql.should == "SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND NOT a)"
1385
1363
  end
1386
1364
 
1387
1365
  it "should support an order option" do
1388
1366
  @c2.one_to_many :attributes, :class => @c1, :order => :kind
1389
-
1390
- n = @c2.new(:id => 1234)
1391
- n.attributes_dataset.sql.should == "SELECT * FROM attributes WHERE (attributes.node_id = 1234) ORDER BY kind"
1367
+ @c2.new(:id => 1234).attributes_dataset.sql.should == "SELECT * FROM attributes WHERE (attributes.node_id = 1234) ORDER BY kind"
1392
1368
  end
1393
1369
 
1394
1370
  it "should support an array for the order option" do
1395
1371
  @c2.one_to_many :attributes, :class => @c1, :order => [:kind1, :kind2]
1396
-
1397
- n = @c2.new(:id => 1234)
1398
- n.attributes_dataset.sql.should == "SELECT * FROM attributes WHERE (attributes.node_id = 1234) ORDER BY kind1, kind2"
1372
+ @c2.new(:id => 1234).attributes_dataset.sql.should == "SELECT * FROM attributes WHERE (attributes.node_id = 1234) ORDER BY kind1, kind2"
1399
1373
  end
1400
1374
 
1401
1375
  it "should return array with all members of the association" do
1402
1376
  @c2.one_to_many :attributes, :class => @c1
1403
-
1404
- n = @c2.new(:id => 1234)
1405
- atts = n.attributes
1406
- atts.should be_a_kind_of(Array)
1407
- atts.size.should == 1
1408
- atts.first.should be_a_kind_of(@c1)
1409
- atts.first.values.should == {}
1410
-
1411
- MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
1377
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE (attributes.node_id = 1234)'
1412
1378
  end
1413
1379
 
1414
1380
  it "should accept a block" do
1415
1381
  @c2.one_to_many :attributes, :class => @c1 do |ds|
1416
1382
  ds.filter(:xxx => @xxx)
1417
1383
  end
1418
-
1419
- n = @c2.new(:id => 1234)
1420
- atts = n.attributes
1421
- atts.should be_a_kind_of(Array)
1422
- atts.size.should == 1
1423
- atts.first.should be_a_kind_of(@c1)
1424
- atts.first.values.should == {}
1425
-
1426
- MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (xxx IS NULL))']
1384
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (xxx IS NULL))'
1427
1385
  end
1428
1386
 
1429
1387
  it "should support :order option with block" do
1430
1388
  @c2.one_to_many :attributes, :class => @c1, :order => :kind do |ds|
1431
1389
  ds.filter(:xxx => @xxx)
1432
1390
  end
1433
-
1434
- n = @c2.new(:id => 1234)
1435
- atts = n.attributes
1436
- atts.should be_a_kind_of(Array)
1437
- atts.size.should == 1
1438
- atts.first.should be_a_kind_of(@c1)
1439
- atts.first.values.should == {}
1440
-
1441
- MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (xxx IS NULL)) ORDER BY kind']
1391
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (xxx IS NULL)) ORDER BY kind'
1442
1392
  end
1443
1393
 
1444
1394
  it "should have the block argument affect the _dataset method" do
@@ -1450,18 +1400,11 @@ describe Sequel::Model, "one_to_many" do
1450
1400
 
1451
1401
  it "should support a :dataset option that is used instead of the default" do
1452
1402
  c1 = @c1
1453
- @c2.one_to_many :all_other_attributes, :class => @c1, :dataset=>proc{c1.filter(:nodeid=>pk).invert}, :order=>:a, :limit=>10 do |ds|
1403
+ @c2.one_to_many :all_other_attributes, :class => @c1, :dataset=>proc{c1.exclude(:nodeid=>pk)}, :order=>:a, :limit=>10 do |ds|
1454
1404
  ds.filter(:xxx => 5)
1455
1405
  end
1456
-
1457
1406
  @c2.new(:id => 1234).all_other_attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE ((nodeid != 1234) AND (xxx = 5)) ORDER BY a LIMIT 10'
1458
- n = @c2.new(:id => 1234)
1459
- atts = n.all_other_attributes
1460
- atts.should be_a_kind_of(Array)
1461
- atts.size.should == 1
1462
- atts.first.should be_a_kind_of(@c1)
1463
- atts.first.values.should == {}
1464
-
1407
+ @c2.new(:id => 1234).all_other_attributes.should == [@c1.load({})]
1465
1408
  MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE ((nodeid != 1234) AND (xxx = 5)) ORDER BY a LIMIT 10']
1466
1409
  end
1467
1410
 
@@ -1479,9 +1422,7 @@ describe Sequel::Model, "one_to_many" do
1479
1422
 
1480
1423
  it "should set cached instance variable when accessed" do
1481
1424
  @c2.one_to_many :attributes, :class => @c1
1482
-
1483
1425
  n = @c2.new(:id => 1234)
1484
- MODEL_DB.reset
1485
1426
  n.associations.include?(:attributes).should == false
1486
1427
  atts = n.attributes
1487
1428
  atts.should == n.associations[:attributes]
@@ -1490,9 +1431,7 @@ describe Sequel::Model, "one_to_many" do
1490
1431
 
1491
1432
  it "should use cached instance variable if available" do
1492
1433
  @c2.one_to_many :attributes, :class => @c1
1493
-
1494
1434
  n = @c2.new(:id => 1234)
1495
- MODEL_DB.reset
1496
1435
  n.associations[:attributes] = 42
1497
1436
  n.attributes.should == 42
1498
1437
  MODEL_DB.sqls.should == []
@@ -1500,9 +1439,7 @@ describe Sequel::Model, "one_to_many" do
1500
1439
 
1501
1440
  it "should not use cached instance variable if asked to reload" do
1502
1441
  @c2.one_to_many :attributes, :class => @c1
1503
-
1504
1442
  n = @c2.new(:id => 1234)
1505
- MODEL_DB.reset
1506
1443
  n.associations[:attributes] = 42
1507
1444
  n.attributes(true).should_not == 42
1508
1445
  MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
@@ -1510,10 +1447,8 @@ describe Sequel::Model, "one_to_many" do
1510
1447
 
1511
1448
  it "should add item to cached instance variable if it exists when calling add_" do
1512
1449
  @c2.one_to_many :attributes, :class => @c1
1513
-
1514
1450
  n = @c2.new(:id => 1234)
1515
- att = @c1.new(:id => 345)
1516
- MODEL_DB.reset
1451
+ att = @c1.load(:id => 345)
1517
1452
  a = []
1518
1453
  n.associations[:attributes] = a
1519
1454
  n.add_attribute(att)
@@ -1535,7 +1470,6 @@ describe Sequel::Model, "one_to_many" do
1535
1470
 
1536
1471
  n = @c2.load(:id => 1234)
1537
1472
  att = @c1.load(:id => 345)
1538
- MODEL_DB.reset
1539
1473
  a = [att]
1540
1474
  n.associations[:attributes] = a
1541
1475
  n.remove_attribute(att)
@@ -1587,13 +1521,9 @@ describe Sequel::Model, "one_to_many" do
1587
1521
  n = @c2.new(:id => 1234)
1588
1522
  atts = n.attributes
1589
1523
  MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
1590
- atts.should be_a_kind_of(Array)
1591
- atts.size.should == 1
1592
- atts.first.should be_a_kind_of(@c1)
1593
- atts.first.values.should == {}
1594
- atts.first.node.should == n
1595
-
1596
- MODEL_DB.sqls.length.should == 1
1524
+ atts.should == [@c1.load({})]
1525
+ atts.map{|a| a.node}.should == [n]
1526
+ MODEL_DB.sqls.should == []
1597
1527
  end
1598
1528
 
1599
1529
  it "should use an explicit reciprocal instance variable if given" do
@@ -1602,19 +1532,15 @@ describe Sequel::Model, "one_to_many" do
1602
1532
  n = @c2.new(:id => 1234)
1603
1533
  atts = n.attributes
1604
1534
  MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
1605
- atts.should be_a_kind_of(Array)
1606
- atts.size.should == 1
1607
- atts.first.should be_a_kind_of(@c1)
1608
- atts.first.values.should == {}
1609
- atts.first.associations[:wxyz].should == n
1610
-
1611
- MODEL_DB.sqls.length.should == 1
1535
+ atts.should == [@c1.load({})]
1536
+ atts.map{|a| a.associations[:wxyz]}.should == [n]
1537
+ MODEL_DB.sqls.should == []
1612
1538
  end
1613
1539
 
1614
1540
  it "should have an remove_all_ method that removes all associations" do
1615
1541
  @c2.one_to_many :attributes, :class => @c1
1616
1542
  @c2.new(:id => 1234).remove_all_attributes
1617
- MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE (node_id = 1234)'
1543
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE (node_id = 1234)']
1618
1544
  end
1619
1545
 
1620
1546
  it "should have remove_all method respect association filters" do
@@ -1628,13 +1554,15 @@ describe Sequel::Model, "one_to_many" do
1628
1554
  it "should have the remove_all_ method respect the :primary_key option" do
1629
1555
  @c2.one_to_many :attributes, :class => @c1, :primary_key=>:xxx
1630
1556
  @c2.new(:id => 1234, :xxx=>5).remove_all_attributes
1631
- MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE (node_id = 5)'
1557
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE (node_id = 5)']
1632
1558
  end
1633
1559
 
1634
1560
  it "should have the remove_all_ method respect composite keys" do
1635
1561
  @c2.one_to_many :attributes, :class => @c1, :key=>[:node_id, :y], :primary_key=>[:id, :x]
1636
1562
  @c2.new(:id => 1234, :x=>5).remove_all_attributes
1637
- MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id|y) = NULL, (node_id|y) = NULL WHERE \(\(node_id = 1234\) AND \(y = 5\)\)/
1563
+ sqls = MODEL_DB.sqls
1564
+ sqls.pop.should =~ /UPDATE attributes SET (node_id|y) = NULL, (node_id|y) = NULL WHERE \(\(node_id = 1234\) AND \(y = 5\)\)/
1565
+ sqls.should == []
1638
1566
  end
1639
1567
 
1640
1568
  it "remove_all should set the cached instance variable to []" do
@@ -1648,10 +1576,8 @@ describe Sequel::Model, "one_to_many" do
1648
1576
  @c2.one_to_many :attributes, :class => @c1
1649
1577
  attrib = @c1.new(:id=>3)
1650
1578
  node = @c2.new(:id => 1234)
1651
- d = @c1.dataset
1652
- def d.fetch_rows(s); end
1579
+ @c1.dataset._fetch = [[], [{:id=>3, :node_id=>1234}]]
1653
1580
  node.attributes.should == []
1654
- def attrib.save(*); self end
1655
1581
  node.add_attribute(attrib)
1656
1582
  node.associations[:attributes].should == [attrib]
1657
1583
  node.remove_all_attributes.should == [attrib]
@@ -1665,15 +1591,12 @@ describe Sequel::Model, "one_to_many" do
1665
1591
  it "remove_all should remove the current item from all reciprocal instance varaibles if it cached instance variable exists" do
1666
1592
  @c2.one_to_many :attributes, :class => @c1
1667
1593
  @c1.many_to_one :node, :class => @c2
1668
- d = @c1.dataset
1669
- def d.fetch_rows(s); end
1670
- d = @c2.dataset
1671
- def d.fetch_rows(s); end
1594
+ @c2.dataset._fetch = []
1595
+ @c1.dataset._fetch = [[], [{:id=>3, :node_id=>1234}]]
1672
1596
  attrib = @c1.new(:id=>3)
1673
- node = @c2.new(:id => 1234)
1597
+ node = @c2.load(:id => 1234)
1674
1598
  node.attributes.should == []
1675
1599
  attrib.node.should == nil
1676
- def attrib.save(*); self end
1677
1600
  node.add_attribute(attrib)
1678
1601
  attrib.associations[:node].should == node
1679
1602
  node.remove_all_attributes
@@ -1761,18 +1684,18 @@ describe Sequel::Model, "one_to_many" do
1761
1684
  h = []
1762
1685
  @c2.one_to_many :attributes, :class => @c1, :before_add=>[proc{|x,y| h << x.pk; h << -y.pk}, :blah], :after_add=>proc{h << 3}, :before_remove=>:blah, :after_remove=>[:blahr]
1763
1686
  @c2.class_eval do
1764
- class_variable_set(:@@blah, h)
1687
+ self::Foo = h
1765
1688
  def _add_attribute(v)
1766
- self.class.send(:class_variable_get, :@@blah) << 4
1689
+ model::Foo << 4
1767
1690
  end
1768
1691
  def _remove_attribute(v)
1769
- self.class.send(:class_variable_get, :@@blah) << 5
1692
+ model::Foo << 5
1770
1693
  end
1771
1694
  def blah(x)
1772
- self.class.send(:class_variable_get, :@@blah) << x.pk
1695
+ model::Foo << x.pk
1773
1696
  end
1774
1697
  def blahr(x)
1775
- self.class.send(:class_variable_get, :@@blah) << 6
1698
+ model::Foo << 6
1776
1699
  end
1777
1700
  end
1778
1701
  p = @c2.load(:id=>10)
@@ -1788,17 +1711,12 @@ describe Sequel::Model, "one_to_many" do
1788
1711
  h = []
1789
1712
  @c2.one_to_many :attributes, :class => @c1, :after_load=>[proc{|x,y| h << [x.pk, y.collect{|z|z.pk}]}, :al]
1790
1713
  @c2.class_eval do
1791
- class_variable_set(:@@blah, h)
1714
+ self::Foo = h
1792
1715
  def al(v)
1793
- v.each{|x| self.class.send(:class_variable_get, :@@blah) << x.pk}
1794
- end
1795
- end
1796
- @c1.class_eval do
1797
- def @dataset.fetch_rows(sql)
1798
- yield({:id=>20})
1799
- yield({:id=>30})
1716
+ v.each{|x| model::Foo << x.pk}
1800
1717
  end
1801
1718
  end
1719
+ @c1.dataset._fetch = [{:id=>20}, {:id=>30}]
1802
1720
  p = @c2.load(:id=>10, :parent_id=>20)
1803
1721
  attributes = p.attributes
1804
1722
  h.should == [[10, [20, 30]], 20, 30]
@@ -1845,23 +1763,13 @@ describe Sequel::Model, "one_to_many" do
1845
1763
  end
1846
1764
 
1847
1765
  describe Sequel::Model, "many_to_many" do
1848
-
1849
1766
  before do
1850
- MODEL_DB.reset
1851
-
1852
1767
  @c1 = Class.new(Sequel::Model(:attributes)) do
1853
1768
  unrestrict_primary_key
1854
1769
  attr_accessor :yyy
1855
1770
  def self.name; 'Attribute'; end
1856
1771
  def self.to_s; 'Attribute'; end
1857
1772
  columns :id, :y
1858
- def _refresh(ds)
1859
- self
1860
- end
1861
-
1862
- def self.[](id)
1863
- load(id.is_a?(Array) ? {:id => id[0], :y => id[1]} : {:id => id})
1864
- end
1865
1773
  end
1866
1774
 
1867
1775
  @c2 = Class.new(Sequel::Model(:nodes)) do
@@ -1873,98 +1781,61 @@ describe Sequel::Model, "many_to_many" do
1873
1781
  columns :id, :x
1874
1782
  end
1875
1783
  @dataset = @c2.dataset
1784
+ @c1.dataset.autoid = 1
1876
1785
 
1877
- [@c1, @c2].each do |c|
1878
- c.dataset.extend(Module.new {
1879
- def fetch_rows(sql)
1880
- @db << sql
1881
- yield Hash.new
1882
- end
1883
- })
1884
- end
1786
+ [@c1, @c2].each{|c| c.dataset._fetch = {}}
1787
+ MODEL_DB.reset
1885
1788
  end
1886
1789
 
1887
1790
  it "should use implicit key values and join table if omitted" do
1888
1791
  @c2.many_to_many :attributes, :class => @c1
1889
-
1890
- n = @c2.new(:id => 1234)
1891
- a = n.attributes_dataset
1892
- a.should be_a_kind_of(Sequel::Dataset)
1893
- a.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))'
1792
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))'
1894
1793
  end
1895
1794
 
1896
1795
  it "should use implicit class if omitted" do
1897
- class ::Tag < Sequel::Model
1796
+ begin
1797
+ class ::Tag < Sequel::Model; end
1798
+ @c2.many_to_many :tags
1799
+ @c2.new(:id => 1234).tags_dataset.sql.should == 'SELECT tags.* FROM tags INNER JOIN nodes_tags ON ((nodes_tags.tag_id = tags.id) AND (nodes_tags.node_id = 1234))'
1800
+ ensure
1801
+ Object.send(:remove_const, :Tag)
1898
1802
  end
1899
-
1900
- @c2.many_to_many :tags
1901
-
1902
- n = @c2.new(:id => 1234)
1903
- a = n.tags_dataset
1904
- a.should be_a_kind_of(Sequel::Dataset)
1905
- a.sql.should == 'SELECT tags.* FROM tags INNER JOIN nodes_tags ON ((nodes_tags.tag_id = tags.id) AND (nodes_tags.node_id = 1234))'
1906
1803
  end
1907
1804
 
1908
1805
  it "should use class inside module if given as a string" do
1909
- module ::Historical
1910
- class Tag < Sequel::Model
1806
+ begin
1807
+ module ::Historical
1808
+ class Tag < Sequel::Model; end
1911
1809
  end
1810
+ @c2.many_to_many :tags, :class=>'::Historical::Tag'
1811
+ @c2.new(:id => 1234).tags_dataset.sql.should == 'SELECT tags.* FROM tags INNER JOIN nodes_tags ON ((nodes_tags.tag_id = tags.id) AND (nodes_tags.node_id = 1234))'
1812
+ ensure
1813
+ Object.send(:remove_const, :Historical)
1912
1814
  end
1913
-
1914
- @c2.many_to_many :tags, :class=>'::Historical::Tag'
1915
-
1916
- n = @c2.new(:id => 1234)
1917
- a = n.tags_dataset
1918
- a.should be_a_kind_of(Sequel::Dataset)
1919
- a.sql.should == 'SELECT tags.* FROM tags INNER JOIN nodes_tags ON ((nodes_tags.tag_id = tags.id) AND (nodes_tags.node_id = 1234))'
1920
1815
  end
1921
1816
 
1922
1817
  it "should use explicit key values and join table if given" do
1923
1818
  @c2.many_to_many :attributes, :class => @c1, :left_key => :nodeid, :right_key => :attributeid, :join_table => :attribute2node
1924
-
1925
- n = @c2.new(:id => 1234)
1926
- a = n.attributes_dataset
1927
- a.should be_a_kind_of(Sequel::Dataset)
1928
- a.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attribute2node ON ((attribute2node.attributeid = attributes.id) AND (attribute2node.nodeid = 1234))'
1929
- end
1930
-
1931
- it "should handle an aliased join table when eager loading" do
1932
- r = @c2.many_to_many(:attributes, :class => @c1, :join_table => :attribute2node___attributes_nodes)
1933
-
1934
- a = @c2.eager_loading_dataset(r, @c2.dataset, [:id], nil)
1935
- a.sql.should == 'SELECT id, attributes_nodes.node_id AS x_foreign_key_x FROM nodes'
1819
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attribute2node ON ((attribute2node.attributeid = attributes.id) AND (attribute2node.nodeid = 1234))'
1936
1820
  end
1937
1821
 
1938
1822
  it "should support a conditions option" do
1939
1823
  @c2.many_to_many :attributes, :class => @c1, :conditions => {:a=>32}
1940
- n = @c2.new(:id => 1234)
1941
- a = n.attributes_dataset
1942
- a.should be_a_kind_of(Sequel::Dataset)
1943
- a.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE (a = 32)'
1824
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE (a = 32)'
1825
+
1944
1826
  @c2.many_to_many :attributes, :class => @c1, :conditions => ['a = ?', 32]
1945
- n = @c2.new(:id => 1234)
1946
- a = n.attributes_dataset
1947
- a.should be_a_kind_of(Sequel::Dataset)
1948
- a.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE (a = 32)'
1949
- n.attributes.should == [@c1.load({})]
1827
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE (a = 32)'
1828
+ @c2.new(:id => 1234).attributes.should == [@c1.load({})]
1950
1829
  end
1951
1830
 
1952
1831
  it "should support an order option" do
1953
1832
  @c2.many_to_many :attributes, :class => @c1, :order => :blah
1954
-
1955
- n = @c2.new(:id => 1234)
1956
- a = n.attributes_dataset
1957
- a.should be_a_kind_of(Sequel::Dataset)
1958
- a.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) ORDER BY blah'
1833
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) ORDER BY blah'
1959
1834
  end
1960
1835
 
1961
1836
  it "should support an array for the order option" do
1962
1837
  @c2.many_to_many :attributes, :class => @c1, :order => [:blah1, :blah2]
1963
-
1964
- n = @c2.new(:id => 1234)
1965
- a = n.attributes_dataset
1966
- a.should be_a_kind_of(Sequel::Dataset)
1967
- a.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) ORDER BY blah1, blah2'
1838
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) ORDER BY blah1, blah2'
1968
1839
  end
1969
1840
 
1970
1841
  it "should support :left_primary_key and :right_primary_key options" do
@@ -1999,19 +1870,13 @@ describe Sequel::Model, "many_to_many" do
1999
1870
  it "should support a select option" do
2000
1871
  @c2.many_to_many :attributes, :class => @c1, :select => :blah
2001
1872
 
2002
- n = @c2.new(:id => 1234)
2003
- a = n.attributes_dataset
2004
- a.should be_a_kind_of(Sequel::Dataset)
2005
- a.sql.should == 'SELECT blah FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))'
1873
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT blah FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))'
2006
1874
  end
2007
1875
 
2008
1876
  it "should support an array for the select option" do
2009
1877
  @c2.many_to_many :attributes, :class => @c1, :select => [:attributes.*, :attribute_nodes__blah2]
2010
1878
 
2011
- n = @c2.new(:id => 1234)
2012
- a = n.attributes_dataset
2013
- a.should be_a_kind_of(Sequel::Dataset)
2014
- a.sql.should == 'SELECT attributes.*, attribute_nodes.blah2 FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))'
1879
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.*, attribute_nodes.blah2 FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))'
2015
1880
  end
2016
1881
 
2017
1882
  it "should accept a block" do
@@ -2021,11 +1886,7 @@ describe Sequel::Model, "many_to_many" do
2021
1886
 
2022
1887
  n = @c2.new(:id => 1234)
2023
1888
  n.xxx = 555
2024
- a = n.attributes
2025
- a.should be_a_kind_of(Array)
2026
- a.size.should == 1
2027
- a.first.should be_a_kind_of(@c1)
2028
- MODEL_DB.sqls.first.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE (xxx = 555)'
1889
+ n.attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE (xxx = 555)'
2029
1890
  end
2030
1891
 
2031
1892
  it "should allow the :order option while accepting a block" do
@@ -2035,20 +1896,9 @@ describe Sequel::Model, "many_to_many" do
2035
1896
 
2036
1897
  n = @c2.new(:id => 1234)
2037
1898
  n.xxx = 555
2038
- a = n.attributes
2039
- a.should be_a_kind_of(Array)
2040
- a.size.should == 1
2041
- a.first.should be_a_kind_of(@c1)
2042
- MODEL_DB.sqls.first.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE (xxx = 555) ORDER BY blah1, blah2'
1899
+ n.attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE (xxx = 555) ORDER BY blah1, blah2'
2043
1900
  end
2044
1901
 
2045
- it "should have the block argument affect the _dataset method" do
2046
- @c2.many_to_many :attributes, :class => @c1 do |ds|
2047
- ds.filter(:xxx => 456)
2048
- end
2049
- @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE (xxx = 456)'
2050
- end
2051
-
2052
1902
  it "should support a :dataset option that is used instead of the default" do
2053
1903
  c1 = @c1
2054
1904
  @c2.many_to_many :attributes, :class => @c1, :dataset=>proc{c1.join_table(:natural, :an).filter(:an__nodeid=>pk)}, :order=> :a, :limit=>10, :select=>nil do |ds|
@@ -2058,11 +1908,8 @@ describe Sequel::Model, "many_to_many" do
2058
1908
  n = @c2.new(:id => 1234)
2059
1909
  n.xxx = 555
2060
1910
  n.attributes_dataset.sql.should == 'SELECT * FROM attributes NATURAL JOIN an WHERE ((an.nodeid = 1234) AND (xxx = 555)) ORDER BY a LIMIT 10'
2061
- a = n.attributes
2062
- a.should be_a_kind_of(Array)
2063
- a.size.should == 1
2064
- a.first.should be_a_kind_of(@c1)
2065
- MODEL_DB.sqls.first.should == 'SELECT * FROM attributes NATURAL JOIN an WHERE ((an.nodeid = 1234) AND (xxx = 555)) ORDER BY a LIMIT 10'
1911
+ n.attributes.should == [@c1.load({})]
1912
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes NATURAL JOIN an WHERE ((an.nodeid = 1234) AND (xxx = 555)) ORDER BY a LIMIT 10']
2066
1913
  end
2067
1914
 
2068
1915
  it "should support a :limit option" do
@@ -2077,15 +1924,32 @@ describe Sequel::Model, "many_to_many" do
2077
1924
  @c2.new(:id => 1234).attributes_dataset.opts[:eager].should == {:attributes=>nil}
2078
1925
  end
2079
1926
 
1927
+ it "should handle an aliased join table" do
1928
+ @c2.many_to_many :attributes, :class => @c1, :join_table => :attribute2node___attributes_nodes
1929
+ n = @c2.load(:id => 1234)
1930
+ a = @c1.load(:id => 2345)
1931
+ n.attributes_dataset.sql.should == "SELECT attributes.* FROM attributes INNER JOIN attribute2node AS attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))"
1932
+ a.should == n.add_attribute(a)
1933
+ a.should == n.remove_attribute(a)
1934
+ n.remove_all_attributes
1935
+ sqls = MODEL_DB.sqls
1936
+ ['INSERT INTO attribute2node (node_id, attribute_id) VALUES (1234, 2345)',
1937
+ 'INSERT INTO attribute2node (attribute_id, node_id) VALUES (2345, 1234)'].should(include(sqls.shift))
1938
+ ["DELETE FROM attribute2node WHERE ((node_id = 1234) AND (attribute_id = 2345))",
1939
+ "DELETE FROM attribute2node WHERE ((attribute_id = 2345) AND (node_id = 1234))"].should(include(sqls.shift))
1940
+ sqls.should == ["DELETE FROM attribute2node WHERE (node_id = 1234)"]
1941
+ end
1942
+
2080
1943
  it "should define an add_ method that works on existing records" do
2081
1944
  @c2.many_to_many :attributes, :class => @c1
2082
1945
 
2083
1946
  n = @c2.load(:id => 1234)
2084
1947
  a = @c1.load(:id => 2345)
2085
- a.should == n.add_attribute(a)
1948
+ n.add_attribute(a).should == a
1949
+ sqls = MODEL_DB.sqls
2086
1950
  ['INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 2345)',
2087
- 'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (2345, 1234)'
2088
- ].should(include(MODEL_DB.sqls.first))
1951
+ 'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (2345, 1234)'].should(include(sqls.shift))
1952
+ sqls.should == []
2089
1953
  end
2090
1954
 
2091
1955
  it "should define an add_ method that works with a primary key" do
@@ -2093,21 +1957,25 @@ describe Sequel::Model, "many_to_many" do
2093
1957
 
2094
1958
  n = @c2.load(:id => 1234)
2095
1959
  a = @c1.load(:id => 2345)
2096
- a.should == n.add_attribute(2345)
1960
+ @c1.dataset._fetch = {:id=>2345}
1961
+ n.add_attribute(2345).should == a
1962
+ sqls = MODEL_DB.sqls
2097
1963
  ['INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 2345)',
2098
- 'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (2345, 1234)'
2099
- ].should(include(MODEL_DB.sqls.first))
1964
+ 'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (2345, 1234)'].should(include(sqls.pop))
1965
+ sqls.should == ["SELECT * FROM attributes WHERE id = 2345"]
2100
1966
  end
2101
1967
 
2102
1968
  it "should allow passing a hash to the add_ method which creates a new record" do
2103
1969
  @c2.many_to_many :attributes, :class => @c1
2104
1970
 
2105
1971
  n = @c2.load(:id => 1234)
2106
- @c1.load(:id => 1).should == n.add_attribute(:id => 1)
2107
- MODEL_DB.sqls.first.should == 'INSERT INTO attributes (id) VALUES (1)'
1972
+ @c1.dataset._fetch = {:id=>1}
1973
+ n.add_attribute(:id => 1).should == @c1.load(:id => 1)
1974
+ sqls = MODEL_DB.sqls
2108
1975
  ['INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 1)',
2109
1976
  'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (1, 1234)'
2110
- ].should(include(MODEL_DB.sqls.last))
1977
+ ].should(include(sqls.pop))
1978
+ sqls.should == ['INSERT INTO attributes (id) VALUES (1)', "SELECT * FROM attributes WHERE (id = 1) LIMIT 1"]
2111
1979
  end
2112
1980
 
2113
1981
  it "should define a remove_ method that works on existing records" do
@@ -2115,8 +1983,8 @@ describe Sequel::Model, "many_to_many" do
2115
1983
 
2116
1984
  n = @c2.new(:id => 1234)
2117
1985
  a = @c1.new(:id => 2345)
2118
- a.should == n.remove_attribute(a)
2119
- MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 2345))'
1986
+ n.remove_attribute(a).should == a
1987
+ MODEL_DB.sqls.should == ['DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 2345))']
2120
1988
  end
2121
1989
 
2122
1990
  it "should raise an error in the add_ method if the passed associated object is not of the correct type" do
@@ -2127,13 +1995,8 @@ describe Sequel::Model, "many_to_many" do
2127
1995
  it "should accept a primary key for the remove_ method and remove an existing record" do
2128
1996
  @c2.many_to_many :attributes, :class => @c1
2129
1997
  n = @c2.new(:id => 1234)
2130
- ds = @c1.dataset
2131
- def ds.fetch_rows(sql)
2132
- db << sql
2133
- yield({:id=>234})
2134
- end
2135
- MODEL_DB.reset
2136
- @c1.load(:id => 234).should == n.remove_attribute(234)
1998
+ @c1.dataset._fetch = {:id=>234}
1999
+ n.remove_attribute(234).should == @c1.load(:id => 234)
2137
2000
  MODEL_DB.sqls.should == ["SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE (attributes.id = 234) LIMIT 1",
2138
2001
  "DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 234))"]
2139
2002
  end
@@ -2148,10 +2011,12 @@ describe Sequel::Model, "many_to_many" do
2148
2011
 
2149
2012
  n = @c2.load(:id => 1234).set(:xxx=>5)
2150
2013
  a = @c1.load(:id => 2345).set(:yyy=>8)
2151
- a.should == n.add_attribute(a)
2014
+ n.add_attribute(a).should == a
2015
+ sqls = MODEL_DB.sqls
2152
2016
  ['INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (5, 8)',
2153
2017
  'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (8, 5)'
2154
- ].should(include(MODEL_DB.sqls.first))
2018
+ ].should(include(sqls.pop))
2019
+ sqls.should == []
2155
2020
  end
2156
2021
 
2157
2022
  it "should have add_ method not add the same object to the cached association array if the object is already in the array" do
@@ -2170,7 +2035,9 @@ describe Sequel::Model, "many_to_many" do
2170
2035
  n = @c2.load(:id => 1234, :x=>5)
2171
2036
  a = @c1.load(:id => 2345, :y=>8)
2172
2037
  a.should == n.add_attribute(a)
2173
- m = /INSERT INTO attributes_nodes \((\w+), (\w+), (\w+), (\w+)\) VALUES \((\d+), (\d+), (\d+), (\d+)\)/.match(MODEL_DB.sqls.first)
2038
+ sqls = MODEL_DB.sqls
2039
+ m = /INSERT INTO attributes_nodes \((\w+), (\w+), (\w+), (\w+)\) VALUES \((\d+), (\d+), (\d+), (\d+)\)/.match(sqls.pop)
2040
+ sqls.should == []
2174
2041
  m.should_not == nil
2175
2042
  map = {'l1'=>1234, 'l2'=>5, 'r1'=>2345, 'r2'=>8}
2176
2043
  %w[l1 l2 r1 r2].each do |x|
@@ -2187,10 +2054,15 @@ describe Sequel::Model, "many_to_many" do
2187
2054
 
2188
2055
  it "should have the add_ method respect composite keys" do
2189
2056
  @c2.many_to_many :attributes, :class => @c1, :left_key=>[:l1, :l2], :right_key=>[:r1, :r2], :left_primary_key=>[:id, :x], :right_primary_key=>[:id, :y]
2057
+ @c1.set_primary_key [:id, :y]
2190
2058
  n = @c2.load(:id => 1234, :x=>5)
2191
2059
  a = @c1.load(:id => 2345, :y=>8)
2192
- a.should == n.add_attribute([2345, 8])
2193
- MODEL_DB.sqls.first.should =~ /INSERT INTO attributes_nodes \([lr][12], [lr][12], [lr][12], [lr][12]\) VALUES \((1234|5|2345|8), (1234|5|2345|8), (1234|5|2345|8), (1234|5|2345|8)\)/
2060
+ @c1.dataset._fetch = {:id => 2345, :y=>8}
2061
+ n.add_attribute([2345, 8]).should == a
2062
+ sqls = MODEL_DB.sqls
2063
+ sqls.shift.should =~ /SELECT \* FROM attributes WHERE \(\((id|y) = (8|2345)\) AND \((id|y) = (8|2345)\)\) LIMIT 1/
2064
+ sqls.pop.should =~ /INSERT INTO attributes_nodes \([lr][12], [lr][12], [lr][12], [lr][12]\) VALUES \((1234|5|2345|8), (1234|5|2345|8), (1234|5|2345|8), (1234|5|2345|8)\)/
2065
+ sqls.should == []
2194
2066
  end
2195
2067
 
2196
2068
  it "should have the remove_ method respect the :left_primary_key and :right_primary_key options" do
@@ -2198,8 +2070,8 @@ describe Sequel::Model, "many_to_many" do
2198
2070
 
2199
2071
  n = @c2.new(:id => 1234, :xxx=>5)
2200
2072
  a = @c1.new(:id => 2345, :yyy=>8)
2201
- a.should == n.remove_attribute(a)
2202
- MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 5) AND (attribute_id = 8))'
2073
+ n.remove_attribute(a).should == a
2074
+ MODEL_DB.sqls.should == ['DELETE FROM attributes_nodes WHERE ((node_id = 5) AND (attribute_id = 8))']
2203
2075
  end
2204
2076
 
2205
2077
  it "should have the remove_ method respect composite keys" do
@@ -2214,17 +2086,12 @@ describe Sequel::Model, "many_to_many" do
2214
2086
  @c1.set_primary_key [:id, :y]
2215
2087
  @c2.many_to_many :attributes, :class => @c1
2216
2088
  n = @c2.new(:id => 1234)
2217
- ds = @c1.dataset
2218
- def ds.fetch_rows(sql)
2219
- db << sql
2220
- yield({:id=>234, :y=>8})
2221
- end
2222
- MODEL_DB.reset
2089
+ @c1.dataset._fetch = {:id=>234, :y=>8}
2223
2090
  @c1.load(:id => 234, :y=>8).should == n.remove_attribute([234, 8])
2091
+ sqls = MODEL_DB.sqls
2224
2092
  ["SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE ((attributes.id = 234) AND (attributes.y = 8)) LIMIT 1",
2225
- "SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE ((attributes.y = 8) AND (attributes.id = 234)) LIMIT 1"].should include(MODEL_DB.sqls.first)
2226
- MODEL_DB.sqls.last.should == "DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 234))"
2227
- MODEL_DB.sqls.length.should == 2
2093
+ "SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE ((attributes.y = 8) AND (attributes.id = 234)) LIMIT 1"].should include(sqls.shift)
2094
+ sqls.should == ["DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 234))"]
2228
2095
  end
2229
2096
 
2230
2097
  it "should raise an error if the model object doesn't have a valid primary key" do
@@ -2242,6 +2109,7 @@ describe Sequel::Model, "many_to_many" do
2242
2109
  n = @c1.new
2243
2110
  a = @c2.load(:id=>123)
2244
2111
  n.new?.should == true
2112
+ @c1.dataset._fetch = {:id=>1}
2245
2113
  a.add_attribute(n)
2246
2114
  n.new?.should == false
2247
2115
  end
@@ -2250,7 +2118,7 @@ describe Sequel::Model, "many_to_many" do
2250
2118
  @c2.many_to_many :attributes, :class => @c1
2251
2119
  n = @c1.new
2252
2120
  a = @c2.load(:id=>123)
2253
- def n.valid?(opts); false; end
2121
+ def n.validate() errors.add(:id, 'foo') end
2254
2122
  proc{a.add_attribute(n)}.should raise_error(Sequel::ValidationFailed)
2255
2123
  end
2256
2124
 
@@ -2259,7 +2127,7 @@ describe Sequel::Model, "many_to_many" do
2259
2127
  n = @c1.new
2260
2128
  n.raise_on_save_failure = false
2261
2129
  a = @c2.load(:id=>123)
2262
- def n.valid?(opts); false; end
2130
+ def n.validate() errors.add(:id, 'foo') end
2263
2131
  proc{a.add_attribute(n)}.should raise_error(Sequel::Error)
2264
2132
  end
2265
2133
 
@@ -2267,7 +2135,8 @@ describe Sequel::Model, "many_to_many" do
2267
2135
  @c2.many_to_many :attributes, :class => @c1, :validate=>false
2268
2136
  n = @c1.new
2269
2137
  a = @c2.load(:id=>123)
2270
- def n.valid?(opts); false; end
2138
+ def n.validate() errors.add(:id, 'foo') end
2139
+ @c1.dataset._fetch = {:id=>1}
2271
2140
  a.add_attribute(n)
2272
2141
  n.new?.should == false
2273
2142
  end
@@ -2282,31 +2151,23 @@ describe Sequel::Model, "many_to_many" do
2282
2151
  it "should provide an array with all members of the association" do
2283
2152
  @c2.many_to_many :attributes, :class => @c1
2284
2153
 
2285
- n = @c2.new(:id => 1234)
2286
- atts = n.attributes
2287
- atts.should be_a_kind_of(Array)
2288
- atts.size.should == 1
2289
- atts.first.should be_a_kind_of(@c1)
2290
-
2291
- MODEL_DB.sqls.first.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))'
2154
+ @c2.new(:id => 1234).attributes.should == [@c1.load({})]
2155
+ MODEL_DB.sqls.should == ['SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))']
2292
2156
  end
2293
2157
 
2294
2158
  it "should set cached instance variable when accessed" do
2295
2159
  @c2.many_to_many :attributes, :class => @c1
2296
2160
 
2297
2161
  n = @c2.new(:id => 1234)
2298
- MODEL_DB.reset
2299
2162
  n.associations.include?(:attributes).should == false
2300
2163
  atts = n.attributes
2301
2164
  atts.should == n.associations[:attributes]
2302
- MODEL_DB.sqls.length.should == 1
2303
2165
  end
2304
2166
 
2305
2167
  it "should use cached instance variable if available" do
2306
2168
  @c2.many_to_many :attributes, :class => @c1
2307
2169
 
2308
2170
  n = @c2.new(:id => 1234)
2309
- MODEL_DB.reset
2310
2171
  n.associations[:attributes] = 42
2311
2172
  n.attributes.should == 42
2312
2173
  MODEL_DB.sqls.should == []
@@ -2316,18 +2177,16 @@ describe Sequel::Model, "many_to_many" do
2316
2177
  @c2.many_to_many :attributes, :class => @c1
2317
2178
 
2318
2179
  n = @c2.new(:id => 1234)
2319
- MODEL_DB.reset
2320
2180
  n.associations[:attributes] = 42
2321
2181
  n.attributes(true).should_not == 42
2322
- MODEL_DB.sqls.length.should == 1
2182
+ MODEL_DB.sqls.should == ["SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))"]
2323
2183
  end
2324
2184
 
2325
2185
  it "should add item to cached instance variable if it exists when calling add_" do
2326
2186
  @c2.many_to_many :attributes, :class => @c1
2327
2187
 
2328
2188
  n = @c2.new(:id => 1234)
2329
- att = @c1.new(:id => 345)
2330
- MODEL_DB.reset
2189
+ att = @c1.load(:id => 345)
2331
2190
  a = []
2332
2191
  n.associations[:attributes] = a
2333
2192
  n.add_attribute(att)
@@ -2339,7 +2198,7 @@ describe Sequel::Model, "many_to_many" do
2339
2198
  @c1.many_to_many :nodes, :class => @c2
2340
2199
 
2341
2200
  n = @c2.new(:id => 1234)
2342
- att = @c1.new(:id => 345)
2201
+ att = @c1.load(:id => 345)
2343
2202
  att.associations[:nodes] = []
2344
2203
  n.add_attribute(att)
2345
2204
  att.nodes.should == [n]
@@ -2349,8 +2208,7 @@ describe Sequel::Model, "many_to_many" do
2349
2208
  @c2.many_to_many :attributes, :class => @c1
2350
2209
 
2351
2210
  n = @c2.new(:id => 1234)
2352
- att = @c1.new(:id => 345)
2353
- MODEL_DB.reset
2211
+ att = @c1.load(:id => 345)
2354
2212
  a = [att]
2355
2213
  n.associations[:attributes] = a
2356
2214
  n.remove_attribute(att)
@@ -2397,13 +2255,13 @@ describe Sequel::Model, "many_to_many" do
2397
2255
  it "should have an remove_all_ method that removes all associations" do
2398
2256
  @c2.many_to_many :attributes, :class => @c1
2399
2257
  @c2.new(:id => 1234).remove_all_attributes
2400
- MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE (node_id = 1234)'
2258
+ MODEL_DB.sqls.should == ['DELETE FROM attributes_nodes WHERE (node_id = 1234)']
2401
2259
  end
2402
2260
 
2403
2261
  it "should have the remove_all_ method respect the :left_primary_key option" do
2404
2262
  @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx
2405
2263
  @c2.new(:id => 1234, :xxx=>5).remove_all_attributes
2406
- MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE (node_id = 5)'
2264
+ MODEL_DB.sqls.should == ['DELETE FROM attributes_nodes WHERE (node_id = 5)']
2407
2265
  end
2408
2266
 
2409
2267
  it "should have the remove_all_ method respect composite keys" do
@@ -2421,10 +2279,9 @@ describe Sequel::Model, "many_to_many" do
2421
2279
 
2422
2280
  it "remove_all should return the array of previously associated items if the cached instance variable exists" do
2423
2281
  @c2.many_to_many :attributes, :class => @c1
2424
- attrib = @c1.new(:id=>3)
2425
- node = @c2.new(:id => 1234)
2426
- d = @c1.dataset
2427
- def d.fetch_rows(s); end
2282
+ attrib = @c1.load(:id=>3)
2283
+ node = @c2.load(:id => 1234)
2284
+ @c1.dataset._fetch = []
2428
2285
  node.attributes.should == []
2429
2286
  node.add_attribute(attrib)
2430
2287
  node.associations[:attributes].should == [attrib]
@@ -2439,11 +2296,9 @@ describe Sequel::Model, "many_to_many" do
2439
2296
  it "remove_all should remove the current item from all reciprocal instance varaibles if it cached instance variable exists" do
2440
2297
  @c2.many_to_many :attributes, :class => @c1
2441
2298
  @c1.many_to_many :nodes, :class => @c2
2442
- d = @c1.dataset
2443
- def d.fetch_rows(s); end
2444
- d = @c2.dataset
2445
- def d.fetch_rows(s); end
2446
- attrib = @c1.new(:id=>3)
2299
+ @c1.dataset._fetch = []
2300
+ @c2.dataset._fetch = []
2301
+ attrib = @c1.load(:id=>3)
2447
2302
  node = @c2.new(:id => 1234)
2448
2303
  node.attributes.should == []
2449
2304
  attrib.nodes.should == []
@@ -2459,8 +2314,9 @@ describe Sequel::Model, "many_to_many" do
2459
2314
  o.add_attribute(@c1.load(:id=>44))
2460
2315
  o.remove_attribute(@c1.load(:id=>45))
2461
2316
  o.remove_all_attributes
2462
- MODEL_DB.sqls.first =~ /INSERT INTO attributes_nodes \((node_id|attribute_id|x), (node_id|attribute_id|x), (node_id|attribute_id|x)\) VALUES \((1234|123|44), (1234|123|44), (1234|123|44)\)/
2463
- MODEL_DB.sqls[1..-1].should == ["DELETE FROM attributes_nodes WHERE ((x = 123) AND (node_id = 1234) AND (attribute_id = 45))",
2317
+ sqls = MODEL_DB.sqls
2318
+ sqls.shift =~ /INSERT INTO attributes_nodes \((node_id|attribute_id|x), (node_id|attribute_id|x), (node_id|attribute_id|x)\) VALUES \((1234|123|44), (1234|123|44), (1234|123|44)\)/
2319
+ sqls.should == ["DELETE FROM attributes_nodes WHERE ((x = 123) AND (node_id = 1234) AND (attribute_id = 45))",
2464
2320
  "DELETE FROM attributes_nodes WHERE ((x = 123) AND (node_id = 1234))"]
2465
2321
  end
2466
2322
 
@@ -2543,18 +2399,18 @@ describe Sequel::Model, "many_to_many" do
2543
2399
  h = []
2544
2400
  @c2.many_to_many :attributes, :class => @c1, :before_add=>[proc{|x,y| h << x.pk; h << -y.pk}, :blah], :after_add=>proc{h << 3}, :before_remove=>:blah, :after_remove=>[:blahr]
2545
2401
  @c2.class_eval do
2546
- class_variable_set(:@@blah, h)
2402
+ self::Foo = h
2547
2403
  def _add_attribute(v)
2548
- self.class.send(:class_variable_get, :@@blah) << 4
2404
+ model::Foo << 4
2549
2405
  end
2550
2406
  def _remove_attribute(v)
2551
- self.class.send(:class_variable_get, :@@blah) << 5
2407
+ model::Foo << 5
2552
2408
  end
2553
2409
  def blah(x)
2554
- self.class.send(:class_variable_get, :@@blah) << x.pk
2410
+ model::Foo << x.pk
2555
2411
  end
2556
2412
  def blahr(x)
2557
- self.class.send(:class_variable_get, :@@blah) << 6
2413
+ model::Foo << 6
2558
2414
  end
2559
2415
  end
2560
2416
  p = @c2.load(:id=>10)
@@ -2570,17 +2426,12 @@ describe Sequel::Model, "many_to_many" do
2570
2426
  h = []
2571
2427
  @c2.many_to_many :attributes, :class => @c1, :after_load=>[proc{|x,y| h << [x.pk, y.collect{|z|z.pk}]}, :al]
2572
2428
  @c2.class_eval do
2573
- class_variable_set(:@@blah, h)
2429
+ self::Foo = h
2574
2430
  def al(v)
2575
- v.each{|x| self.class.send(:class_variable_get, :@@blah) << x.pk}
2576
- end
2577
- end
2578
- @c1.class_eval do
2579
- def @dataset.fetch_rows(sql)
2580
- yield({:id=>20})
2581
- yield({:id=>30})
2431
+ v.each{|x| model::Foo << x.pk}
2582
2432
  end
2583
2433
  end
2434
+ @c1.dataset._fetch = [{:id=>20}, {:id=>30}]
2584
2435
  p = @c2.load(:id=>10, :parent_id=>20)
2585
2436
  attributes = p.attributes
2586
2437
  h.should == [[10, [20, 30]], 20, 30]
@@ -2623,14 +2474,7 @@ describe Sequel::Model, "many_to_many" do
2623
2474
 
2624
2475
  it "should support a :uniq option that removes duplicates from the association" do
2625
2476
  @c2.many_to_many :attributes, :class => @c1, :uniq=>true
2626
- @c1.class_eval do
2627
- def @dataset.fetch_rows(sql)
2628
- yield({:id=>20})
2629
- yield({:id=>30})
2630
- yield({:id=>20})
2631
- yield({:id=>30})
2632
- end
2633
- end
2477
+ @c1.dataset._fetch = [{:id=>20}, {:id=>30}, {:id=>20}, {:id=>30}]
2634
2478
  @c2.load(:id=>10, :parent_id=>20).attributes.should == [@c1.load(:id=>20), @c1.load(:id=>30)]
2635
2479
  end
2636
2480
 
@@ -2651,76 +2495,525 @@ describe Sequel::Model, "many_to_many" do
2651
2495
  @c2.many_to_many :attributes, :class => @c1 do |ds|
2652
2496
  ds.filter(:join_table_att=>3)
2653
2497
  end
2654
- @c1.dataset.instance_eval do
2655
- def fetch_rows(sql, &block)
2656
- db.sqls << sql
2657
- yield(:id=>2)
2658
- end
2659
- end
2498
+ @c1.dataset._fetch = {:id=>2}
2660
2499
  @c2.load(:id=>1).remove_attribute(2)
2661
2500
  MODEL_DB.sqls.should == ["SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1)) WHERE ((join_table_att = 3) AND (attributes.id = 2)) LIMIT 1",
2662
2501
  "DELETE FROM attributes_nodes WHERE ((node_id = 1) AND (attribute_id = 2))"]
2663
2502
  end
2664
2503
  end
2665
2504
 
2666
- describe Sequel::Model, " association reflection methods" do
2505
+ describe "Filtering by associations" do
2667
2506
  before do
2668
- MODEL_DB.reset
2669
- @c1 = Class.new(Sequel::Model(:nodes)) do
2670
- def self.name; 'Node'; end
2671
- def self.to_s; 'Node'; end
2507
+ @Album = Class.new(Sequel::Model(:albums))
2508
+ artist = @Artist = Class.new(Sequel::Model(:artists))
2509
+ tag = @Tag = Class.new(Sequel::Model(:tags))
2510
+ track = @Track = Class.new(Sequel::Model(:tracks))
2511
+ album_info = @AlbumInfo = Class.new(Sequel::Model(:album_infos))
2512
+ @Artist.columns :id, :id1, :id2
2513
+ @Tag.columns :id, :tid1, :tid2
2514
+ @Track.columns :id, :album_id, :album_id1, :album_id2
2515
+ @AlbumInfo.columns :id, :album_id, :album_id1, :album_id2
2516
+ @Album.class_eval do
2517
+ columns :id, :id1, :id2, :artist_id, :artist_id1, :artist_id2
2518
+ many_to_one :artist, :class=>artist
2519
+ one_to_many :tracks, :class=>track, :key=>:album_id
2520
+ one_to_one :album_info, :class=>album_info, :key=>:album_id
2521
+ many_to_many :tags, :class=>tag, :left_key=>:album_id, :join_table=>:albums_tags
2522
+
2523
+ many_to_one :cartist, :class=>artist, :key=>[:artist_id1, :artist_id2], :primary_key=>[:id1, :id2]
2524
+ one_to_many :ctracks, :class=>track, :key=>[:album_id1, :album_id2], :primary_key=>[:id1, :id2]
2525
+ one_to_one :calbum_info, :class=>album_info, :key=>[:album_id1, :album_id2], :primary_key=>[:id1, :id2]
2526
+ many_to_many :ctags, :class=>tag, :left_key=>[:album_id1, :album_id2], :left_primary_key=>[:id1, :id2], :right_key=>[:tag_id1, :tag_id2], :right_primary_key=>[:tid1, :tid2], :join_table=>:albums_tags
2672
2527
  end
2673
2528
  end
2674
-
2675
- it "#all_association_reflections should include all association reflection hashes" do
2676
- @c1.all_association_reflections.should == []
2677
2529
 
2678
- @c1.associate :many_to_one, :parent, :class => @c1
2679
- @c1.all_association_reflections.collect{|v| v[:name]}.should == [:parent]
2680
- @c1.all_association_reflections.collect{|v| v[:type]}.should == [:many_to_one]
2681
- @c1.all_association_reflections.collect{|v| v[:class]}.should == [@c1]
2530
+ it "should be able to filter on many_to_one associations" do
2531
+ @Album.filter(:artist=>@Artist.load(:id=>3)).sql.should == 'SELECT * FROM albums WHERE (albums.artist_id = 3)'
2532
+ end
2533
+
2534
+ it "should be able to filter on one_to_many associations" do
2535
+ @Album.filter(:tracks=>@Track.load(:album_id=>3)).sql.should == 'SELECT * FROM albums WHERE (albums.id = 3)'
2536
+ end
2537
+
2538
+ it "should be able to filter on one_to_one associations" do
2539
+ @Album.filter(:album_info=>@AlbumInfo.load(:album_id=>3)).sql.should == 'SELECT * FROM albums WHERE (albums.id = 3)'
2540
+ end
2541
+
2542
+ it "should be able to filter on many_to_many associations" do
2543
+ @Album.filter(:tags=>@Tag.load(:id=>3)).sql.should == 'SELECT * FROM albums WHERE (albums.id IN (SELECT albums_tags.album_id FROM albums_tags WHERE ((albums_tags.tag_id = 3) AND (albums_tags.album_id IS NOT NULL))))'
2544
+ end
2545
+
2546
+ it "should be able to filter on many_to_one associations with composite keys" do
2547
+ @Album.filter(:cartist=>@Artist.load(:id1=>3, :id2=>4)).sql.should == 'SELECT * FROM albums WHERE ((albums.artist_id1 = 3) AND (albums.artist_id2 = 4))'
2548
+ end
2549
+
2550
+ it "should be able to filter on one_to_many associations with composite keys" do
2551
+ @Album.filter(:ctracks=>@Track.load(:album_id1=>3, :album_id2=>4)).sql.should == 'SELECT * FROM albums WHERE ((albums.id1 = 3) AND (albums.id2 = 4))'
2552
+ end
2553
+
2554
+ it "should be able to filter on one_to_one associations with composite keys" do
2555
+ @Album.filter(:calbum_info=>@AlbumInfo.load(:album_id1=>3, :album_id2=>4)).sql.should == 'SELECT * FROM albums WHERE ((albums.id1 = 3) AND (albums.id2 = 4))'
2556
+ end
2557
+
2558
+ it "should be able to filter on many_to_many associations with composite keys" do
2559
+ @Album.filter(:ctags=>@Tag.load(:tid1=>3, :tid2=>4)).sql.should == 'SELECT * FROM albums WHERE ((albums.id1, albums.id2) IN (SELECT albums_tags.album_id1, albums_tags.album_id2 FROM albums_tags WHERE ((albums_tags.tag_id1 = 3) AND (albums_tags.tag_id2 = 4) AND (albums_tags.album_id1 IS NOT NULL) AND (albums_tags.album_id2 IS NOT NULL))))'
2560
+ end
2561
+
2562
+ it "should work inside a complex filter" do
2563
+ artist = @Artist.load(:id=>3)
2564
+ @Album.filter{foo & {:artist=>artist}}.sql.should == 'SELECT * FROM albums WHERE (foo AND (albums.artist_id = 3))'
2565
+ track = @Track.load(:album_id=>4)
2566
+ @Album.filter{foo & [[:artist, artist], [:tracks, track]]}.sql.should == 'SELECT * FROM albums WHERE (foo AND (albums.artist_id = 3) AND (albums.id = 4))'
2567
+ end
2682
2568
 
2683
- @c1.associate :one_to_many, :children, :class => @c1
2684
- @c1.all_association_reflections.sort_by{|x|x[:name].to_s}
2685
- @c1.all_association_reflections.sort_by{|x|x[:name].to_s}.collect{|v| v[:name]}.should == [:children, :parent]
2686
- @c1.all_association_reflections.sort_by{|x|x[:name].to_s}.collect{|v| v[:type]}.should == [:one_to_many, :many_to_one]
2687
- @c1.all_association_reflections.sort_by{|x|x[:name].to_s}.collect{|v| v[:class]}.should == [@c1, @c1]
2569
+ it "should raise for an invalid association name" do
2570
+ proc{@Album.filter(:foo=>@Artist.load(:id=>3)).sql}.should raise_error(Sequel::Error)
2688
2571
  end
2689
2572
 
2690
- it "#association_reflection should return nil for nonexistent association" do
2691
- @c1.association_reflection(:blah).should == nil
2573
+ it "should raise for an invalid association type" do
2574
+ @Album.many_to_many :iatags, :clone=>:tags
2575
+ @Album.association_reflection(:iatags)[:type] = :foo
2576
+ proc{@Album.filter(:iatags=>@Tag.load(:id=>3)).sql}.should raise_error(Sequel::Error)
2692
2577
  end
2693
2578
 
2694
- it "#association_reflection should return association reflection hash if association exists" do
2695
- @c1.associate :many_to_one, :parent, :class => @c1
2696
- @c1.association_reflection(:parent).should be_a_kind_of(Sequel::Model::Associations::AssociationReflection)
2697
- @c1.association_reflection(:parent)[:name].should == :parent
2698
- @c1.association_reflection(:parent)[:type].should == :many_to_one
2699
- @c1.association_reflection(:parent)[:class].should == @c1
2579
+ it "should raise for an invalid associated object class " do
2580
+ proc{@Album.filter(:tags=>@Artist.load(:id=>3)).sql}.should raise_error(Sequel::Error)
2581
+ end
2582
+
2583
+ it "should raise for an invalid associated object class when multiple objects are used" do
2584
+ proc{@Album.filter(:tags=>[@Tag.load(:id=>3), @Artist.load(:id=>3)]).sql}.should raise_error(Sequel::Error)
2585
+ end
2586
+
2587
+ it "should correctly handle case when a multiple value association is used" do
2588
+ proc{@Album.filter(:tags=>[@Tag.load(:id=>3), @Artist.load(:id=>3)]).sql}.should raise_error(Sequel::Error)
2589
+ end
2590
+
2591
+ it "should not affect non-association IN/NOT IN filtering with an empty array" do
2592
+ @Album.filter(:tag_id=>[]).sql.should == 'SELECT * FROM albums WHERE (tag_id != tag_id)'
2593
+ @Album.exclude(:tag_id=>[]).sql.should == 'SELECT * FROM albums WHERE (tag_id = tag_id)'
2594
+ end
2700
2595
 
2701
- @c1.associate :one_to_many, :children, :class => @c1
2702
- @c1.association_reflection(:children).should be_a_kind_of(Sequel::Model::Associations::AssociationReflection)
2703
- @c1.association_reflection(:children)[:name].should == :children
2704
- @c1.association_reflection(:children)[:type].should == :one_to_many
2705
- @c1.association_reflection(:children)[:class].should == @c1
2596
+ it "should work correctly in subclasses" do
2597
+ c = Class.new(@Album)
2598
+ c.many_to_one :sartist, :class=>@Artist
2599
+ c.filter(:sartist=>@Artist.load(:id=>3)).sql.should == 'SELECT * FROM albums WHERE (albums.sartist_id = 3)'
2706
2600
  end
2707
2601
 
2708
- it "#associations should include all association names" do
2709
- @c1.associations.should == []
2710
- @c1.associate :many_to_one, :parent, :class => @c1
2711
- @c1.associations.should == [:parent]
2712
- @c1.associate :one_to_many, :children, :class => @c1
2713
- @c1.associations.sort_by{|x|x.to_s}.should == [:children, :parent]
2602
+ it "should be able to exclude on many_to_one associations" do
2603
+ @Album.exclude(:artist=>@Artist.load(:id=>3)).sql.should == 'SELECT * FROM albums WHERE ((albums.artist_id != 3) OR (albums.artist_id IS NULL))'
2714
2604
  end
2715
2605
 
2716
- it "association reflections should be copied upon subclasing" do
2717
- @c1.associate :many_to_one, :parent, :class => @c1
2718
- c = Class.new(@c1)
2719
- @c1.associations.should == [:parent]
2720
- c.associations.should == [:parent]
2721
- c.associate :many_to_one, :parent2, :class => @c1
2722
- @c1.associations.should == [:parent]
2723
- c.associations.sort_by{|x| x.to_s}.should == [:parent, :parent2]
2724
- c.instance_methods.map{|x| x.to_s}.should include('parent')
2606
+ it "should be able to exclude on one_to_many associations" do
2607
+ @Album.exclude(:tracks=>@Track.load(:album_id=>3)).sql.should == 'SELECT * FROM albums WHERE ((albums.id != 3) OR (albums.id IS NULL))'
2608
+ end
2609
+
2610
+ it "should be able to exclude on one_to_one associations" do
2611
+ @Album.exclude(:album_info=>@AlbumInfo.load(:album_id=>3)).sql.should == 'SELECT * FROM albums WHERE ((albums.id != 3) OR (albums.id IS NULL))'
2612
+ end
2613
+
2614
+ it "should be able to exclude on many_to_many associations" do
2615
+ @Album.exclude(:tags=>@Tag.load(:id=>3)).sql.should == 'SELECT * FROM albums WHERE ((albums.id NOT IN (SELECT albums_tags.album_id FROM albums_tags WHERE ((albums_tags.tag_id = 3) AND (albums_tags.album_id IS NOT NULL)))) OR (albums.id IS NULL))'
2616
+ end
2617
+
2618
+ it "should be able to exclude on many_to_one associations with composite keys" do
2619
+ @Album.exclude(:cartist=>@Artist.load(:id1=>3, :id2=>4)).sql.should == 'SELECT * FROM albums WHERE ((albums.artist_id1 != 3) OR (albums.artist_id2 != 4) OR (albums.artist_id1 IS NULL) OR (albums.artist_id2 IS NULL))'
2620
+ end
2621
+
2622
+ it "should be able to exclude on one_to_many associations with composite keys" do
2623
+ @Album.exclude(:ctracks=>@Track.load(:album_id1=>3, :album_id2=>4)).sql.should == 'SELECT * FROM albums WHERE ((albums.id1 != 3) OR (albums.id2 != 4) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2624
+ end
2625
+
2626
+ it "should be able to exclude on one_to_one associations with composite keys" do
2627
+ @Album.exclude(:calbum_info=>@AlbumInfo.load(:album_id1=>3, :album_id2=>4)).sql.should == 'SELECT * FROM albums WHERE ((albums.id1 != 3) OR (albums.id2 != 4) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2628
+ end
2629
+
2630
+ it "should be able to exclude on many_to_many associations with composite keys" do
2631
+ @Album.exclude(:ctags=>@Tag.load(:tid1=>3, :tid2=>4)).sql.should == 'SELECT * FROM albums WHERE (((albums.id1, albums.id2) NOT IN (SELECT albums_tags.album_id1, albums_tags.album_id2 FROM albums_tags WHERE ((albums_tags.tag_id1 = 3) AND (albums_tags.tag_id2 = 4) AND (albums_tags.album_id1 IS NOT NULL) AND (albums_tags.album_id2 IS NOT NULL)))) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2632
+ end
2633
+
2634
+ it "should be able to filter on multiple many_to_one associations" do
2635
+ @Album.filter(:artist=>[@Artist.load(:id=>3), @Artist.load(:id=>4)]).sql.should == 'SELECT * FROM albums WHERE (albums.artist_id IN (3, 4))'
2636
+ end
2637
+
2638
+ it "should be able to filter on multiple one_to_many associations" do
2639
+ @Album.filter(:tracks=>[@Track.load(:album_id=>3), @Track.load(:album_id=>4)]).sql.should == 'SELECT * FROM albums WHERE (albums.id IN (3, 4))'
2640
+ end
2641
+
2642
+ it "should be able to filter on multiple one_to_one associations" do
2643
+ @Album.filter(:album_info=>[@AlbumInfo.load(:album_id=>3), @AlbumInfo.load(:album_id=>4)]).sql.should == 'SELECT * FROM albums WHERE (albums.id IN (3, 4))'
2644
+ end
2645
+
2646
+ it "should be able to filter on multiple many_to_many associations" do
2647
+ @Album.filter(:tags=>[@Tag.load(:id=>3), @Tag.load(:id=>4)]).sql.should == 'SELECT * FROM albums WHERE (albums.id IN (SELECT albums_tags.album_id FROM albums_tags WHERE ((albums_tags.tag_id IN (3, 4)) AND (albums_tags.album_id IS NOT NULL))))'
2648
+ end
2649
+
2650
+ it "should be able to filter on multiple many_to_one associations with composite keys" do
2651
+ @Album.filter(:cartist=>[@Artist.load(:id1=>3, :id2=>4), @Artist.load(:id1=>5, :id2=>6)]).sql.should == 'SELECT * FROM albums WHERE ((albums.artist_id1, albums.artist_id2) IN ((3, 4), (5, 6)))'
2652
+ end
2653
+
2654
+ it "should be able to filter on multiple one_to_many associations with composite keys" do
2655
+ @Album.filter(:ctracks=>[@Track.load(:album_id1=>3, :album_id2=>4), @Track.load(:album_id1=>5, :album_id2=>6)]).sql.should == 'SELECT * FROM albums WHERE ((albums.id1, albums.id2) IN ((3, 4), (5, 6)))'
2656
+ end
2657
+
2658
+ it "should be able to filter on multiple one_to_one associations with composite keys" do
2659
+ @Album.filter(:calbum_info=>[@AlbumInfo.load(:album_id1=>3, :album_id2=>4), @AlbumInfo.load(:album_id1=>5, :album_id2=>6)]).sql.should == 'SELECT * FROM albums WHERE ((albums.id1, albums.id2) IN ((3, 4), (5, 6)))'
2660
+ end
2661
+
2662
+ it "should be able to filter on multiple many_to_many associations with composite keys" do
2663
+ @Album.filter(:ctags=>[@Tag.load(:tid1=>3, :tid2=>4), @Tag.load(:tid1=>5, :tid2=>6)]).sql.should == 'SELECT * FROM albums WHERE ((albums.id1, albums.id2) IN (SELECT albums_tags.album_id1, albums_tags.album_id2 FROM albums_tags WHERE (((albums_tags.tag_id1, albums_tags.tag_id2) IN ((3, 4), (5, 6))) AND (albums_tags.album_id1 IS NOT NULL) AND (albums_tags.album_id2 IS NOT NULL))))'
2664
+ end
2665
+
2666
+ it "should be able to exclude on multiple many_to_one associations" do
2667
+ @Album.exclude(:artist=>[@Artist.load(:id=>3), @Artist.load(:id=>4)]).sql.should == 'SELECT * FROM albums WHERE ((albums.artist_id NOT IN (3, 4)) OR (albums.artist_id IS NULL))'
2668
+ end
2669
+
2670
+ it "should be able to exclude on multiple one_to_many associations" do
2671
+ @Album.exclude(:tracks=>[@Track.load(:album_id=>3), @Track.load(:album_id=>4)]).sql.should == 'SELECT * FROM albums WHERE ((albums.id NOT IN (3, 4)) OR (albums.id IS NULL))'
2672
+ end
2673
+
2674
+ it "should be able to exclude on multiple one_to_one associations" do
2675
+ @Album.exclude(:album_info=>[@AlbumInfo.load(:album_id=>3), @AlbumInfo.load(:album_id=>4)]).sql.should == 'SELECT * FROM albums WHERE ((albums.id NOT IN (3, 4)) OR (albums.id IS NULL))'
2676
+ end
2677
+
2678
+ it "should be able to exclude on multiple many_to_many associations" do
2679
+ @Album.exclude(:tags=>[@Tag.load(:id=>3), @Tag.load(:id=>4)]).sql.should == 'SELECT * FROM albums WHERE ((albums.id NOT IN (SELECT albums_tags.album_id FROM albums_tags WHERE ((albums_tags.tag_id IN (3, 4)) AND (albums_tags.album_id IS NOT NULL)))) OR (albums.id IS NULL))'
2680
+ end
2681
+
2682
+ it "should be able to exclude on multiple many_to_one associations with composite keys" do
2683
+ @Album.exclude(:cartist=>[@Artist.load(:id1=>3, :id2=>4), @Artist.load(:id1=>5, :id2=>6)]).sql.should == 'SELECT * FROM albums WHERE (((albums.artist_id1, albums.artist_id2) NOT IN ((3, 4), (5, 6))) OR (albums.artist_id1 IS NULL) OR (albums.artist_id2 IS NULL))'
2684
+ end
2685
+
2686
+ it "should be able to exclude on multiple one_to_many associations with composite keys" do
2687
+ @Album.exclude(:ctracks=>[@Track.load(:album_id1=>3, :album_id2=>4), @Track.load(:album_id1=>5, :album_id2=>6)]).sql.should == 'SELECT * FROM albums WHERE (((albums.id1, albums.id2) NOT IN ((3, 4), (5, 6))) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2688
+ end
2689
+
2690
+ it "should be able to exclude on multiple one_to_one associations with composite keys" do
2691
+ @Album.exclude(:calbum_info=>[@AlbumInfo.load(:album_id1=>3, :album_id2=>4), @AlbumInfo.load(:album_id1=>5, :album_id2=>6)]).sql.should == 'SELECT * FROM albums WHERE (((albums.id1, albums.id2) NOT IN ((3, 4), (5, 6))) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2692
+ end
2693
+
2694
+ it "should be able to exclude on multiple many_to_many associations with composite keys" do
2695
+ @Album.exclude(:ctags=>[@Tag.load(:tid1=>3, :tid2=>4), @Tag.load(:tid1=>5, :tid2=>6)]).sql.should == 'SELECT * FROM albums WHERE (((albums.id1, albums.id2) NOT IN (SELECT albums_tags.album_id1, albums_tags.album_id2 FROM albums_tags WHERE (((albums_tags.tag_id1, albums_tags.tag_id2) IN ((3, 4), (5, 6))) AND (albums_tags.album_id1 IS NOT NULL) AND (albums_tags.album_id2 IS NOT NULL)))) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2696
+ end
2697
+
2698
+ it "should be able to handle NULL values when filtering many_to_one associations" do
2699
+ @Album.filter(:artist=>@Artist.new).sql.should == 'SELECT * FROM albums WHERE \'f\''
2700
+ end
2701
+
2702
+ it "should be able to handle NULL values when filtering one_to_many associations" do
2703
+ @Album.filter(:tracks=>@Track.new).sql.should == 'SELECT * FROM albums WHERE \'f\''
2704
+ end
2705
+
2706
+ it "should be able to handle NULL values when filtering one_to_one associations" do
2707
+ @Album.filter(:album_info=>@AlbumInfo.new).sql.should == 'SELECT * FROM albums WHERE \'f\''
2708
+ end
2709
+
2710
+ it "should be able to handle NULL values when filtering many_to_many associations" do
2711
+ @Album.filter(:tags=>@Tag.new).sql.should == 'SELECT * FROM albums WHERE \'f\''
2712
+ end
2713
+
2714
+ it "should be able to handle filtering with NULL values for many_to_one associations with composite keys" do
2715
+ @Album.filter(:cartist=>@Artist.load(:id2=>4)).sql.should == 'SELECT * FROM albums WHERE \'f\''
2716
+ @Album.filter(:cartist=>@Artist.load(:id1=>3)).sql.should == 'SELECT * FROM albums WHERE \'f\''
2717
+ @Album.filter(:cartist=>@Artist.new).sql.should == 'SELECT * FROM albums WHERE \'f\''
2718
+ end
2719
+
2720
+ it "should be able to filter with NULL values for one_to_many associations with composite keys" do
2721
+ @Album.filter(:ctracks=>@Track.load(:album_id2=>4)).sql.should == 'SELECT * FROM albums WHERE \'f\''
2722
+ @Album.filter(:ctracks=>@Track.load(:album_id1=>3)).sql.should == 'SELECT * FROM albums WHERE \'f\''
2723
+ @Album.filter(:ctracks=>@Track.new).sql.should == 'SELECT * FROM albums WHERE \'f\''
2724
+ end
2725
+
2726
+ it "should be able to filter with NULL values for one_to_one associations with composite keys" do
2727
+ @Album.filter(:calbum_info=>@AlbumInfo.load(:album_id2=>4)).sql.should == 'SELECT * FROM albums WHERE \'f\''
2728
+ @Album.filter(:calbum_info=>@AlbumInfo.load(:album_id1=>3)).sql.should == 'SELECT * FROM albums WHERE \'f\''
2729
+ @Album.filter(:calbum_info=>@AlbumInfo.new).sql.should == 'SELECT * FROM albums WHERE \'f\''
2730
+ end
2731
+
2732
+ it "should be able to filter with NULL values for many_to_many associations with composite keys" do
2733
+ @Album.filter(:ctags=>@Tag.load(:tid1=>3)).sql.should == 'SELECT * FROM albums WHERE \'f\''
2734
+ @Album.filter(:ctags=>@Tag.load(:tid2=>4)).sql.should == 'SELECT * FROM albums WHERE \'f\''
2735
+ @Album.filter(:ctags=>@Tag.new).sql.should == 'SELECT * FROM albums WHERE \'f\''
2736
+ end
2737
+
2738
+ it "should be able to handle NULL values when excluding many_to_one associations" do
2739
+ @Album.exclude(:artist=>@Artist.new).sql.should == 'SELECT * FROM albums WHERE \'t\''
2740
+ end
2741
+
2742
+ it "should be able to handle NULL values when excluding one_to_many associations" do
2743
+ @Album.exclude(:tracks=>@Track.new).sql.should == 'SELECT * FROM albums WHERE \'t\''
2744
+ end
2745
+
2746
+ it "should be able to handle NULL values when excluding one_to_one associations" do
2747
+ @Album.exclude(:album_info=>@AlbumInfo.new).sql.should == 'SELECT * FROM albums WHERE \'t\''
2748
+ end
2749
+
2750
+ it "should be able to handle NULL values when excluding many_to_many associations" do
2751
+ @Album.exclude(:tags=>@Tag.new).sql.should == 'SELECT * FROM albums WHERE \'t\''
2752
+ end
2753
+
2754
+ it "should be able to handle excluding with NULL values for many_to_one associations with composite keys" do
2755
+ @Album.exclude(:cartist=>@Artist.load(:id2=>4)).sql.should == 'SELECT * FROM albums WHERE \'t\''
2756
+ @Album.exclude(:cartist=>@Artist.load(:id1=>3)).sql.should == 'SELECT * FROM albums WHERE \'t\''
2757
+ @Album.exclude(:cartist=>@Artist.new).sql.should == 'SELECT * FROM albums WHERE \'t\''
2758
+ end
2759
+
2760
+ it "should be able to excluding with NULL values for one_to_many associations with composite keys" do
2761
+ @Album.exclude(:ctracks=>@Track.load(:album_id2=>4)).sql.should == 'SELECT * FROM albums WHERE \'t\''
2762
+ @Album.exclude(:ctracks=>@Track.load(:album_id1=>3)).sql.should == 'SELECT * FROM albums WHERE \'t\''
2763
+ @Album.exclude(:ctracks=>@Track.new).sql.should == 'SELECT * FROM albums WHERE \'t\''
2764
+ end
2765
+
2766
+ it "should be able to excluding with NULL values for one_to_one associations with composite keys" do
2767
+ @Album.exclude(:calbum_info=>@AlbumInfo.load(:album_id2=>4)).sql.should == 'SELECT * FROM albums WHERE \'t\''
2768
+ @Album.exclude(:calbum_info=>@AlbumInfo.load(:album_id1=>3)).sql.should == 'SELECT * FROM albums WHERE \'t\''
2769
+ @Album.exclude(:calbum_info=>@AlbumInfo.new).sql.should == 'SELECT * FROM albums WHERE \'t\''
2770
+ end
2771
+
2772
+ it "should be able to excluding with NULL values for many_to_many associations with composite keys" do
2773
+ @Album.exclude(:ctags=>@Tag.load(:tid1=>3)).sql.should == 'SELECT * FROM albums WHERE \'t\''
2774
+ @Album.exclude(:ctags=>@Tag.load(:tid2=>4)).sql.should == 'SELECT * FROM albums WHERE \'t\''
2775
+ @Album.exclude(:ctags=>@Tag.new).sql.should == 'SELECT * FROM albums WHERE \'t\''
2776
+ end
2777
+
2778
+ it "should be able to handle NULL values when filtering multiple many_to_one associations" do
2779
+ @Album.filter(:artist=>[@Artist.load(:id=>3), @Artist.new]).sql.should == 'SELECT * FROM albums WHERE (albums.artist_id IN (3))'
2780
+ @Album.filter(:artist=>[@Artist.new, @Artist.new]).sql.should == 'SELECT * FROM albums WHERE \'f\''
2781
+ end
2782
+
2783
+ it "should be able to handle NULL values when filtering multiple one_to_many associations" do
2784
+ @Album.filter(:tracks=>[@Track.load(:album_id=>3), @Track.new]).sql.should == 'SELECT * FROM albums WHERE (albums.id IN (3))'
2785
+ @Album.filter(:tracks=>[@Track.new, @Track.new]).sql.should == 'SELECT * FROM albums WHERE \'f\''
2786
+ end
2787
+
2788
+ it "should be able to handle NULL values when filtering multiple one_to_one associations" do
2789
+ @Album.filter(:album_info=>[@AlbumInfo.load(:album_id=>3), @AlbumInfo.new]).sql.should == 'SELECT * FROM albums WHERE (albums.id IN (3))'
2790
+ @Album.filter(:album_info=>[@AlbumInfo.new, @AlbumInfo.new]).sql.should == 'SELECT * FROM albums WHERE \'f\''
2791
+ end
2792
+
2793
+ it "should be able to handle NULL values when filtering multiple many_to_many associations" do
2794
+ @Album.filter(:tags=>[@Tag.load(:id=>3), @Tag.new]).sql.should == 'SELECT * FROM albums WHERE (albums.id IN (SELECT albums_tags.album_id FROM albums_tags WHERE ((albums_tags.tag_id IN (3)) AND (albums_tags.album_id IS NOT NULL))))'
2795
+ @Album.filter(:tags=>[@Tag.new, @Tag.new]).sql.should == 'SELECT * FROM albums WHERE \'f\''
2796
+ end
2797
+
2798
+ it "should be able to handle NULL values when filtering multiple many_to_one associations with composite keys" do
2799
+ @Album.filter(:cartist=>[@Artist.load(:id1=>3, :id2=>4), @Artist.load(:id1=>3)]).sql.should == 'SELECT * FROM albums WHERE ((albums.artist_id1, albums.artist_id2) IN ((3, 4)))'
2800
+ @Album.filter(:cartist=>[@Artist.load(:id1=>3, :id2=>4), @Artist.new]).sql.should == 'SELECT * FROM albums WHERE ((albums.artist_id1, albums.artist_id2) IN ((3, 4)))'
2801
+ end
2802
+
2803
+ it "should be able handle NULL values when filtering multiple one_to_many associations with composite keys" do
2804
+ @Album.filter(:ctracks=>[@Track.load(:album_id1=>3, :album_id2=>4), @Track.load(:album_id1=>3)]).sql.should == 'SELECT * FROM albums WHERE ((albums.id1, albums.id2) IN ((3, 4)))'
2805
+ @Album.filter(:ctracks=>[@Track.load(:album_id1=>3, :album_id2=>4), @Track.new]).sql.should == 'SELECT * FROM albums WHERE ((albums.id1, albums.id2) IN ((3, 4)))'
2806
+ end
2807
+
2808
+ it "should be able to handle NULL values when filtering multiple one_to_one associations with composite keys" do
2809
+ @Album.filter(:calbum_info=>[@AlbumInfo.load(:album_id1=>3, :album_id2=>4), @AlbumInfo.load(:album_id1=>5)]).sql.should == 'SELECT * FROM albums WHERE ((albums.id1, albums.id2) IN ((3, 4)))'
2810
+ @Album.filter(:calbum_info=>[@AlbumInfo.load(:album_id1=>3, :album_id2=>4), @AlbumInfo.new]).sql.should == 'SELECT * FROM albums WHERE ((albums.id1, albums.id2) IN ((3, 4)))'
2811
+ end
2812
+
2813
+ it "should be able to handle NULL values when filtering multiple many_to_many associations with composite keys" do
2814
+ @Album.filter(:ctags=>[@Tag.load(:tid1=>3, :tid2=>4), @Tag.load(:tid1=>5)]).sql.should == 'SELECT * FROM albums WHERE ((albums.id1, albums.id2) IN (SELECT albums_tags.album_id1, albums_tags.album_id2 FROM albums_tags WHERE (((albums_tags.tag_id1, albums_tags.tag_id2) IN ((3, 4))) AND (albums_tags.album_id1 IS NOT NULL) AND (albums_tags.album_id2 IS NOT NULL))))'
2815
+ @Album.filter(:ctags=>[@Tag.load(:tid1=>3, :tid2=>4), @Tag.new]).sql.should == 'SELECT * FROM albums WHERE ((albums.id1, albums.id2) IN (SELECT albums_tags.album_id1, albums_tags.album_id2 FROM albums_tags WHERE (((albums_tags.tag_id1, albums_tags.tag_id2) IN ((3, 4))) AND (albums_tags.album_id1 IS NOT NULL) AND (albums_tags.album_id2 IS NOT NULL))))'
2816
+ end
2817
+
2818
+ it "should be able to handle NULL values when excluding multiple many_to_one associations" do
2819
+ @Album.exclude(:artist=>[@Artist.load(:id=>3), @Artist.new]).sql.should == 'SELECT * FROM albums WHERE ((albums.artist_id NOT IN (3)) OR (albums.artist_id IS NULL))'
2820
+ @Album.exclude(:artist=>[@Artist.new, @Artist.new]).sql.should == 'SELECT * FROM albums WHERE \'t\''
2821
+ end
2822
+
2823
+ it "should be able to handle NULL values when excluding multiple one_to_many associations" do
2824
+ @Album.exclude(:tracks=>[@Track.load(:album_id=>3), @Track.new]).sql.should == 'SELECT * FROM albums WHERE ((albums.id NOT IN (3)) OR (albums.id IS NULL))'
2825
+ @Album.exclude(:tracks=>[@Track.new, @Track.new]).sql.should == 'SELECT * FROM albums WHERE \'t\''
2826
+ end
2827
+
2828
+ it "should be able to handle NULL values when excluding multiple one_to_one associations" do
2829
+ @Album.exclude(:album_info=>[@AlbumInfo.load(:album_id=>3), @AlbumInfo.new]).sql.should == 'SELECT * FROM albums WHERE ((albums.id NOT IN (3)) OR (albums.id IS NULL))'
2830
+ @Album.exclude(:album_info=>[@AlbumInfo.new, @AlbumInfo.new]).sql.should == 'SELECT * FROM albums WHERE \'t\''
2831
+ end
2832
+
2833
+ it "should be able to handle NULL values when excluding multiple many_to_many associations" do
2834
+ @Album.exclude(:tags=>[@Tag.load(:id=>3), @Tag.new]).sql.should == 'SELECT * FROM albums WHERE ((albums.id NOT IN (SELECT albums_tags.album_id FROM albums_tags WHERE ((albums_tags.tag_id IN (3)) AND (albums_tags.album_id IS NOT NULL)))) OR (albums.id IS NULL))'
2835
+ @Album.exclude(:tags=>[@Tag.new, @Tag.new]).sql.should == 'SELECT * FROM albums WHERE \'t\''
2836
+ end
2837
+
2838
+ it "should be able to handle NULL values when excluding multiple many_to_one associations with composite keys" do
2839
+ @Album.exclude(:cartist=>[@Artist.load(:id1=>3, :id2=>4), @Artist.load(:id1=>3)]).sql.should == 'SELECT * FROM albums WHERE (((albums.artist_id1, albums.artist_id2) NOT IN ((3, 4))) OR (albums.artist_id1 IS NULL) OR (albums.artist_id2 IS NULL))'
2840
+ @Album.exclude(:cartist=>[@Artist.load(:id1=>3, :id2=>4), @Artist.new]).sql.should == 'SELECT * FROM albums WHERE (((albums.artist_id1, albums.artist_id2) NOT IN ((3, 4))) OR (albums.artist_id1 IS NULL) OR (albums.artist_id2 IS NULL))'
2841
+ end
2842
+
2843
+ it "should be able handle NULL values when excluding multiple one_to_many associations with composite keys" do
2844
+ @Album.exclude(:ctracks=>[@Track.load(:album_id1=>3, :album_id2=>4), @Track.load(:album_id1=>3)]).sql.should == 'SELECT * FROM albums WHERE (((albums.id1, albums.id2) NOT IN ((3, 4))) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2845
+ @Album.exclude(:ctracks=>[@Track.load(:album_id1=>3, :album_id2=>4), @Track.new]).sql.should == 'SELECT * FROM albums WHERE (((albums.id1, albums.id2) NOT IN ((3, 4))) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2846
+ end
2847
+
2848
+ it "should be able to handle NULL values when excluding multiple one_to_one associations with composite keys" do
2849
+ @Album.exclude(:calbum_info=>[@AlbumInfo.load(:album_id1=>3, :album_id2=>4), @AlbumInfo.load(:album_id1=>5)]).sql.should == 'SELECT * FROM albums WHERE (((albums.id1, albums.id2) NOT IN ((3, 4))) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2850
+ @Album.exclude(:calbum_info=>[@AlbumInfo.load(:album_id1=>3, :album_id2=>4), @AlbumInfo.new]).sql.should == 'SELECT * FROM albums WHERE (((albums.id1, albums.id2) NOT IN ((3, 4))) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2851
+ end
2852
+
2853
+ it "should be able to handle NULL values when excluding multiple many_to_many associations with composite keys" do
2854
+ @Album.exclude(:ctags=>[@Tag.load(:tid1=>3, :tid2=>4), @Tag.load(:tid1=>5)]).sql.should == 'SELECT * FROM albums WHERE (((albums.id1, albums.id2) NOT IN (SELECT albums_tags.album_id1, albums_tags.album_id2 FROM albums_tags WHERE (((albums_tags.tag_id1, albums_tags.tag_id2) IN ((3, 4))) AND (albums_tags.album_id1 IS NOT NULL) AND (albums_tags.album_id2 IS NOT NULL)))) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2855
+ @Album.exclude(:ctags=>[@Tag.load(:tid1=>3, :tid2=>4), @Tag.new]).sql.should == 'SELECT * FROM albums WHERE (((albums.id1, albums.id2) NOT IN (SELECT albums_tags.album_id1, albums_tags.album_id2 FROM albums_tags WHERE (((albums_tags.tag_id1, albums_tags.tag_id2) IN ((3, 4))) AND (albums_tags.album_id1 IS NOT NULL) AND (albums_tags.album_id2 IS NOT NULL)))) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2856
+ end
2857
+
2858
+ it "should be able to filter on many_to_one association datasets" do
2859
+ @Album.filter(:artist=>@Artist.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE (albums.artist_id IN (SELECT artists.id FROM artists WHERE ((x = 1) AND (artists.id IS NOT NULL))))'
2860
+ end
2861
+
2862
+ it "should be able to filter on one_to_many association datasets" do
2863
+ @Album.filter(:tracks=>@Track.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE (albums.id IN (SELECT tracks.album_id FROM tracks WHERE ((x = 1) AND (tracks.album_id IS NOT NULL))))'
2864
+ end
2865
+
2866
+ it "should be able to filter on one_to_one association datasets" do
2867
+ @Album.filter(:album_info=>@AlbumInfo.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE (albums.id IN (SELECT album_infos.album_id FROM album_infos WHERE ((x = 1) AND (album_infos.album_id IS NOT NULL))))'
2868
+ end
2869
+
2870
+ it "should be able to filter on many_to_many association datasets" do
2871
+ @Album.filter(:tags=>@Tag.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE (albums.id IN (SELECT albums_tags.album_id FROM albums_tags WHERE ((albums_tags.tag_id IN (SELECT tags.id FROM tags WHERE ((x = 1) AND (tags.id IS NOT NULL)))) AND (albums_tags.album_id IS NOT NULL))))'
2872
+ end
2873
+
2874
+ it "should be able to filter on many_to_one association datasets with composite keys" do
2875
+ @Album.filter(:cartist=>@Artist.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE ((albums.artist_id1, albums.artist_id2) IN (SELECT artists.id1, artists.id2 FROM artists WHERE ((x = 1) AND (artists.id1 IS NOT NULL) AND (artists.id2 IS NOT NULL))))'
2876
+ end
2877
+
2878
+ it "should be able to filter on one_to_many association datasets with composite keys" do
2879
+ @Album.filter(:ctracks=>@Track.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE ((albums.id1, albums.id2) IN (SELECT tracks.album_id1, tracks.album_id2 FROM tracks WHERE ((x = 1) AND (tracks.album_id1 IS NOT NULL) AND (tracks.album_id2 IS NOT NULL))))'
2880
+ end
2881
+
2882
+ it "should be able to filter on one_to_one association datasets with composite keys" do
2883
+ @Album.filter(:calbum_info=>@AlbumInfo.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE ((albums.id1, albums.id2) IN (SELECT album_infos.album_id1, album_infos.album_id2 FROM album_infos WHERE ((x = 1) AND (album_infos.album_id1 IS NOT NULL) AND (album_infos.album_id2 IS NOT NULL))))'
2884
+ end
2885
+
2886
+ it "should be able to filter on many_to_many association datasets with composite keys" do
2887
+ @Album.filter(:ctags=>@Tag.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE ((albums.id1, albums.id2) IN (SELECT albums_tags.album_id1, albums_tags.album_id2 FROM albums_tags WHERE (((albums_tags.tag_id1, albums_tags.tag_id2) IN (SELECT tags.tid1, tags.tid2 FROM tags WHERE ((x = 1) AND (tags.tid1 IS NOT NULL) AND (tags.tid2 IS NOT NULL)))) AND (albums_tags.album_id1 IS NOT NULL) AND (albums_tags.album_id2 IS NOT NULL))))'
2888
+ end
2889
+
2890
+ it "should be able to exclude on many_to_one association datasets" do
2891
+ @Album.exclude(:artist=>@Artist.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE ((albums.artist_id NOT IN (SELECT artists.id FROM artists WHERE ((x = 1) AND (artists.id IS NOT NULL)))) OR (albums.artist_id IS NULL))'
2892
+ end
2893
+
2894
+ it "should be able to exclude on one_to_many association datasets" do
2895
+ @Album.exclude(:tracks=>@Track.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE ((albums.id NOT IN (SELECT tracks.album_id FROM tracks WHERE ((x = 1) AND (tracks.album_id IS NOT NULL)))) OR (albums.id IS NULL))'
2896
+ end
2897
+
2898
+ it "should be able to exclude on one_to_one association datasets" do
2899
+ @Album.exclude(:album_info=>@AlbumInfo.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE ((albums.id NOT IN (SELECT album_infos.album_id FROM album_infos WHERE ((x = 1) AND (album_infos.album_id IS NOT NULL)))) OR (albums.id IS NULL))'
2900
+ end
2901
+
2902
+ it "should be able to exclude on many_to_many association datasets" do
2903
+ @Album.exclude(:tags=>@Tag.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE ((albums.id NOT IN (SELECT albums_tags.album_id FROM albums_tags WHERE ((albums_tags.tag_id IN (SELECT tags.id FROM tags WHERE ((x = 1) AND (tags.id IS NOT NULL)))) AND (albums_tags.album_id IS NOT NULL)))) OR (albums.id IS NULL))'
2904
+ end
2905
+
2906
+ it "should be able to exclude on many_to_one association datasets with composite keys" do
2907
+ @Album.exclude(:cartist=>@Artist.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE (((albums.artist_id1, albums.artist_id2) NOT IN (SELECT artists.id1, artists.id2 FROM artists WHERE ((x = 1) AND (artists.id1 IS NOT NULL) AND (artists.id2 IS NOT NULL)))) OR (albums.artist_id1 IS NULL) OR (albums.artist_id2 IS NULL))'
2908
+ end
2909
+
2910
+ it "should be able to exclude on one_to_many association datasets with composite keys" do
2911
+ @Album.exclude(:ctracks=>@Track.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE (((albums.id1, albums.id2) NOT IN (SELECT tracks.album_id1, tracks.album_id2 FROM tracks WHERE ((x = 1) AND (tracks.album_id1 IS NOT NULL) AND (tracks.album_id2 IS NOT NULL)))) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2912
+ end
2913
+
2914
+ it "should be able to exclude on one_to_one association datasets with composite keys" do
2915
+ @Album.exclude(:calbum_info=>@AlbumInfo.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE (((albums.id1, albums.id2) NOT IN (SELECT album_infos.album_id1, album_infos.album_id2 FROM album_infos WHERE ((x = 1) AND (album_infos.album_id1 IS NOT NULL) AND (album_infos.album_id2 IS NOT NULL)))) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2916
+ end
2917
+
2918
+ it "should be able to exclude on many_to_many association datasets with composite keys" do
2919
+ @Album.exclude(:ctags=>@Tag.filter(:x=>1)).sql.should == 'SELECT * FROM albums WHERE (((albums.id1, albums.id2) NOT IN (SELECT albums_tags.album_id1, albums_tags.album_id2 FROM albums_tags WHERE (((albums_tags.tag_id1, albums_tags.tag_id2) IN (SELECT tags.tid1, tags.tid2 FROM tags WHERE ((x = 1) AND (tags.tid1 IS NOT NULL) AND (tags.tid2 IS NOT NULL)))) AND (albums_tags.album_id1 IS NOT NULL) AND (albums_tags.album_id2 IS NOT NULL)))) OR (albums.id1 IS NULL) OR (albums.id2 IS NULL))'
2920
+ end
2921
+
2922
+ it "should do a regular IN query if the dataset for a different model is used" do
2923
+ @Album.filter(:artist=>@Album.select(:x)).sql.should == 'SELECT * FROM albums WHERE (artist IN (SELECT x FROM albums))'
2924
+ end
2925
+
2926
+ it "should do a regular IN query if a non-model dataset is used" do
2927
+ @Album.filter(:artist=>@Album.db.from(:albums).select(:x)).sql.should == 'SELECT * FROM albums WHERE (artist IN (SELECT x FROM albums))'
2725
2928
  end
2726
2929
  end
2930
+
2931
+ describe "Sequel::Model Associations with clashing column names" do
2932
+ before do
2933
+ @db = Sequel.mock(:fetch=>{:id=>1, :object_id=>2})
2934
+ @Foo = Class.new(Sequel::Model(@db[:foos]))
2935
+ @Bar = Class.new(Sequel::Model(@db[:bars]))
2936
+ @Foo.columns :id, :object_id
2937
+ @Bar.columns :id, :object_id
2938
+ @Foo.def_column_alias(:obj_id, :object_id)
2939
+ @Bar.def_column_alias(:obj_id, :object_id)
2940
+ @Foo.one_to_many :bars, :primary_key=>:obj_id, :primary_key_column=>:object_id, :key=>:object_id, :key_method=>:obj_id, :eager_loader_key=>:object_id, :class=>@Bar
2941
+ @Foo.one_to_one :bar, :primary_key=>:obj_id, :primary_key_column=>:object_id, :key=>:object_id, :key_method=>:obj_id, :eager_loader_key=>:object_id, :class=>@Bar
2942
+ @Bar.many_to_one :foo, :key=>:obj_id, :key_column=>:object_id, :primary_key=>:object_id, :primary_key_method=>:obj_id, :class=>@Foo
2943
+ @Foo.many_to_many :mtmbars, :join_table=>:bars_foos, :left_primary_key=>:obj_id, :left_primary_key_column=>:object_id, :right_primary_key=>:object_id, :right_primary_key_method=>:obj_id, :left_key=>:foo_id, :right_key=>:object_id, :eager_loader_key=>:object_id, :class=>@Bar
2944
+ @Bar.many_to_many :mtmfoos, :join_table=>:bars_foos, :left_primary_key=>:obj_id, :left_primary_key_column=>:object_id, :right_primary_key=>:object_id, :right_primary_key_method=>:obj_id, :left_key=>:object_id, :right_key=>:foo_id, :eager_loader_key=>:object_id, :class=>@Foo
2945
+ @foo = @Foo.load(:id=>1, :object_id=>2)
2946
+ @bar = @Bar.load(:id=>1, :object_id=>2)
2947
+ @db.sqls
2948
+ end
2949
+
2950
+ it "should have working regular association methods" do
2951
+ @Bar.first.foo.should == @foo
2952
+ @Foo.first.bars.should == [@bar]
2953
+ @Foo.first.bar.should == @bar
2954
+ @Foo.first.mtmbars.should == [@bar]
2955
+ @Bar.first.mtmfoos.should == [@foo]
2956
+ end
2957
+
2958
+ it "should have working eager loading methods" do
2959
+ @Bar.eager(:foo).all.map{|o| [o, o.foo]}.should == [[@bar, @foo]]
2960
+ @Foo.eager(:bars).all.map{|o| [o, o.bars]}.should == [[@foo, [@bar]]]
2961
+ @Foo.eager(:bar).all.map{|o| [o, o.bar]}.should == [[@foo, @bar]]
2962
+ @db.fetch = [[{:id=>1, :object_id=>2}], [{:id=>1, :object_id=>2, :x_foreign_key_x=>2}]]
2963
+ @Foo.eager(:mtmbars).all.map{|o| [o, o.mtmbars]}.should == [[@foo, [@bar]]]
2964
+ @db.fetch = [[{:id=>1, :object_id=>2}], [{:id=>1, :object_id=>2, :x_foreign_key_x=>2}]]
2965
+ @Bar.eager(:mtmfoos).all.map{|o| [o, o.mtmfoos]}.should == [[@bar, [@foo]]]
2966
+ end
2967
+
2968
+ it "should have working eager graphing methods" do
2969
+ @db.fetch = {:id=>1, :object_id=>2, :foo_id=>1, :foo_object_id=>2}
2970
+ @Bar.eager_graph(:foo).all.map{|o| [o, o.foo]}.should == [[@bar, @foo]]
2971
+ @db.fetch = {:id=>1, :object_id=>2, :bars_id=>1, :bars_object_id=>2}
2972
+ @Foo.eager_graph(:bars).all.map{|o| [o, o.bars]}.should == [[@foo, [@bar]]]
2973
+ @db.fetch = {:id=>1, :object_id=>2, :bar_id=>1, :bar_object_id=>2}
2974
+ @Foo.eager_graph(:bar).all.map{|o| [o, o.bar]}.should == [[@foo, @bar]]
2975
+ @db.fetch = {:id=>1, :object_id=>2, :mtmfoos_id=>1, :mtmfoos_object_id=>2}
2976
+ @Bar.eager_graph(:mtmfoos).all.map{|o| [o, o.mtmfoos]}.should == [[@bar, [@foo]]]
2977
+ @db.fetch = {:id=>1, :object_id=>2, :mtmbars_id=>1, :mtmbars_object_id=>2}
2978
+ @Foo.eager_graph(:mtmbars).all.map{|o| [o, o.mtmbars]}.should == [[@foo, [@bar]]]
2979
+ end
2980
+
2981
+ it "should have working modification methods" do
2982
+ b = @Bar.load(:id=>2, :object_id=>3)
2983
+ f = @Foo.load(:id=>2, :object_id=>3)
2984
+ @db.numrows = 1
2985
+
2986
+ @bar.foo = f
2987
+ @bar.obj_id.should == 3
2988
+ @foo.bar = @bar
2989
+ @bar.obj_id.should == 2
2990
+
2991
+ @foo.add_bar(b)
2992
+ @db.fetch = [[{:id=>1, :object_id=>2}, {:id=>2, :object_id=>2}], [{:id=>1, :object_id=>2}]]
2993
+ @foo.bars.should == [@bar, b]
2994
+ @foo.remove_bar(b)
2995
+ @foo.bars.should == [@bar]
2996
+ @foo.remove_all_bars
2997
+ @foo.bars.should == []
2998
+
2999
+ @db.fetch = [[{:id=>1, :object_id=>2}], [], [{:id=>2, :object_id=>2}]]
3000
+ @bar = @Bar.load(:id=>1, :object_id=>2)
3001
+ @foo.mtmbars.should == [@bar]
3002
+ @foo.remove_all_mtmbars
3003
+ @foo.mtmbars.should == []
3004
+ @foo.add_mtmbar(b)
3005
+ @foo.mtmbars.should == [b]
3006
+ @foo.remove_mtmbar(b)
3007
+ @foo.mtmbars.should == []
3008
+
3009
+ @db.fetch = [[{:id=>2, :object_id=>3}], [], [{:id=>2, :object_id=>3}]]
3010
+ @bar.add_mtmfoo(f)
3011
+ @bar.mtmfoos.should == [f]
3012
+ @bar.remove_all_mtmfoos
3013
+ @bar.mtmfoos.should == []
3014
+ @bar.add_mtmfoo(f)
3015
+ @bar.mtmfoos.should == [f]
3016
+ @bar.remove_mtmfoo(f)
3017
+ @bar.mtmfoos.should == []
3018
+ end
3019
+ end