viking-sequel 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. metadata +346 -0
@@ -0,0 +1,2633 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe Sequel::Model, "associate" do
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
8
+ 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
+ end
18
+
19
+ 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
30
+ end
31
+ 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
+ end
38
+
39
+ 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
+ klass = Class.new(Sequel::Model(:nodes)) do
42
+ columns :id, :a_id
43
+ end
44
+ mod = Module.new do
45
+ def blah
46
+ filter{|o| o.__send__(association_reflection[:key]) > model_object.id*2}
47
+ end
48
+ end
49
+
50
+ klass.associate :many_to_one, :a, :class=>klass
51
+ klass.associate :one_to_many, :bs, :key=>:b_id, :class=>klass, :extend=>mod
52
+ klass.associate :many_to_many, :cs, :class=>klass
53
+
54
+ node = klass.load(:id=>1)
55
+ node.a_dataset.model_object.should == node
56
+ node.bs_dataset.model_object.should == node
57
+ node.cs_dataset.model_object.should == node
58
+
59
+ node.a_dataset.association_reflection.should == klass.association_reflection(:a)
60
+ node.bs_dataset.association_reflection.should == klass.association_reflection(:bs)
61
+ node.cs_dataset.association_reflection.should == klass.association_reflection(:cs)
62
+
63
+ node.bs_dataset.blah.sql.should == 'SELECT * FROM nodes WHERE ((nodes.b_id = 1) AND (b_id > 2))'
64
+ end
65
+
66
+ it "should allow extending the dataset with :extend option" do
67
+ MODEL_DB.reset
68
+ klass = Class.new(Sequel::Model(:nodes)) do
69
+ columns :id, :a_id
70
+ end
71
+ mod = Module.new do
72
+ def blah
73
+ 1
74
+ end
75
+ end
76
+ mod2 = Module.new do
77
+ def blar
78
+ 2
79
+ end
80
+ end
81
+
82
+ klass.associate :many_to_one, :a, :class=>klass, :extend=>mod
83
+ klass.associate :one_to_many, :bs, :class=>klass, :extend=>[mod]
84
+ klass.associate :many_to_many, :cs, :class=>klass, :extend=>[mod, mod2]
85
+
86
+ node = klass.load(:id=>1)
87
+ node.a_dataset.blah.should == 1
88
+ node.bs_dataset.blah.should == 1
89
+ node.cs_dataset.blah.should == 1
90
+ node.cs_dataset.blar.should == 2
91
+ end
92
+
93
+ 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}
100
+
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
118
+ end
119
+
120
+ end
121
+
122
+ describe Sequel::Model, "many_to_one" do
123
+ before do
124
+ MODEL_DB.reset
125
+
126
+ @c2 = Class.new(Sequel::Model(:nodes)) do
127
+ unrestrict_primary_key
128
+ columns :id, :parent_id, :par_parent_id, :blah
129
+ end
130
+
131
+ @dataset = @c2.dataset
132
+ end
133
+
134
+ it "should use implicit key if omitted" do
135
+ @c2.many_to_one :parent, :class => @c2
136
+
137
+ d = @c2.new(:id => 1, :parent_id => 234)
138
+ p = d.parent
139
+ p.class.should == @c2
140
+ p.values.should == {:x => 1, :id => 1}
141
+
142
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 234) LIMIT 1"]
143
+ end
144
+
145
+ it "should use implicit class if omitted" do
146
+ class ::ParParent < Sequel::Model
147
+ 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
+ end
157
+
158
+ it "should use class inside module if given as a string" do
159
+ module ::Par
160
+ class Parent < Sequel::Model
161
+ end
162
+ 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
+ end
172
+
173
+ it "should use explicit key if given" do
174
+ @c2.many_to_one :parent, :class => @c2, :key => :blah
175
+
176
+ d = @c2.new(:id => 1, :blah => 567)
177
+ p = d.parent
178
+ p.class.should == @c2
179
+ p.values.should == {:x => 1, :id => 1}
180
+
181
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 567) LIMIT 1"]
182
+ end
183
+
184
+ it "should use :primary_key option if given" do
185
+ @c2.many_to_one :parent, :class => @c2, :key => :blah, :primary_key => :pk
186
+ @c2.new(:id => 1, :blah => 567).parent
187
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.pk = 567) LIMIT 1"]
188
+ end
189
+
190
+ it "should support composite keys" do
191
+ @c2.many_to_one :parent, :class => @c2, :key=>[:id, :parent_id], :primary_key=>[:parent_id, :id]
192
+ @c2.new(:id => 1, :parent_id => 234).parent
193
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.parent_id = 1) AND (nodes.id = 234)) LIMIT 1"]
194
+ end
195
+
196
+ it "should not issue query if not all keys have values" do
197
+ @c2.many_to_one :parent, :class => @c2, :key=>[:id, :parent_id], :primary_key=>[:parent_id, :id]
198
+ @c2.new(:id => 1, :parent_id => nil).parent.should == nil
199
+ MODEL_DB.sqls.should == []
200
+ end
201
+
202
+ it "should raise an Error unless same number of composite keys used" do
203
+ proc{@c2.many_to_one :parent, :class => @c2, :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
204
+ proc{@c2.many_to_one :parent, :class => @c2, :key=>[:id, :parent_id], :primary_key=>:id}.should raise_error(Sequel::Error)
205
+ proc{@c2.many_to_one :parent, :class => @c2, :key=>:id, :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
206
+ proc{@c2.many_to_one :parent, :class => @c2, :key=>[:id, :parent_id, :blah], :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
207
+ end
208
+
209
+ it "should use :select option if given" do
210
+ @c2.many_to_one :parent, :class => @c2, :key => :blah, :select=>[:id, :name]
211
+ @c2.new(:id => 1, :blah => 567).parent
212
+ MODEL_DB.sqls.should == ["SELECT id, name FROM nodes WHERE (nodes.id = 567) LIMIT 1"]
213
+ end
214
+
215
+ it "should use :conditions option if given" do
216
+ @c2.many_to_one :parent, :class => @c2, :key => :blah, :conditions=>{:a=>32}
217
+ @c2.new(:id => 1, :blah => 567).parent
218
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.id = 567) AND (a = 32)) LIMIT 1"]
219
+
220
+ @c2.many_to_one :parent, :class => @c2, :key => :blah, :conditions=>:a
221
+ MODEL_DB.sqls.clear
222
+ @c2.new(:id => 1, :blah => 567).parent
223
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.id = 567) AND a) LIMIT 1"]
224
+ end
225
+
226
+ 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|
229
+ ds.filter(:x.sql_number > 1)
230
+ end
231
+ @c2.load(:id => 100).child_20
232
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((parent_id = 100) AND (x > 1)) ORDER BY name LIMIT 1 OFFSET 20"]
233
+ end
234
+
235
+ it "should return nil if key value is nil" do
236
+ @c2.many_to_one :parent, :class => @c2
237
+
238
+ d = @c2.new(:id => 1)
239
+ d.parent.should == nil
240
+ end
241
+
242
+ it "should cache negative lookup" do
243
+ @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
+
249
+ d = @c2.new(:id => 1, :parent_id=>555)
250
+ MODEL_DB.sqls.should == []
251
+ d.parent.should == nil
252
+ MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.id = 555) LIMIT 1']
253
+ d.parent.should == nil
254
+ MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.id = 555) LIMIT 1']
255
+ end
256
+
257
+ it "should define a setter method" do
258
+ @c2.many_to_one :parent, :class => @c2
259
+
260
+ d = @c2.new(:id => 1)
261
+ d.parent = @c2.new(:id => 4321)
262
+ d.values.should == {:id => 1, :parent_id => 4321}
263
+
264
+ d.parent = nil
265
+ d.values.should == {:id => 1, :parent_id => nil}
266
+
267
+ e = @c2.new(:id => 6677)
268
+ d.parent = e
269
+ d.values.should == {:id => 1, :parent_id => 6677}
270
+ end
271
+
272
+ it "should have the setter method respect the :primary_key option" do
273
+ @c2.many_to_one :parent, :class => @c2, :primary_key=>:blah
274
+
275
+ d = @c2.new(:id => 1)
276
+ d.parent = @c2.new(:id => 4321, :blah=>444)
277
+ d.values.should == {:id => 1, :parent_id => 444}
278
+
279
+ d.parent = nil
280
+ d.values.should == {:id => 1, :parent_id => nil}
281
+
282
+ e = @c2.new(:id => 6677, :blah=>8)
283
+ d.parent = e
284
+ d.values.should == {:id => 1, :parent_id => 8}
285
+ end
286
+
287
+ it "should have the setter method respect composite keys" do
288
+ @c2.many_to_one :parent, :class => @c2, :key=>[:id, :parent_id], :primary_key=>[:parent_id, :id]
289
+
290
+ d = @c2.new(:id => 1, :parent_id=> 234)
291
+ d.parent = @c2.new(:id => 4, :parent_id=>52)
292
+ d.values.should == {:id => 52, :parent_id => 4}
293
+
294
+ d.parent = nil
295
+ d.values.should == {:id => nil, :parent_id => nil}
296
+
297
+ e = @c2.new(:id => 6677, :parent_id=>8)
298
+ d.parent = e
299
+ d.values.should == {:id => 8, :parent_id => 6677}
300
+ end
301
+
302
+ it "should not persist changes until saved" do
303
+ @c2.many_to_one :parent, :class => @c2
304
+
305
+ d = @c2.load(:id => 1)
306
+ MODEL_DB.reset
307
+ d.parent = @c2.new(:id => 345)
308
+ MODEL_DB.sqls.should == []
309
+ d.save_changes
310
+ MODEL_DB.sqls.should == ['UPDATE nodes SET parent_id = 345 WHERE (id = 1)']
311
+ end
312
+
313
+ it "should set cached instance variable when accessed" do
314
+ @c2.many_to_one :parent, :class => @c2
315
+
316
+ d = @c2.load(:id => 1)
317
+ MODEL_DB.reset
318
+ d.parent_id = 234
319
+ d.associations[:parent].should == nil
320
+ ds = @c2.dataset
321
+ def ds.fetch_rows(sql, &block); MODEL_DB.sqls << sql; yield({:id=>234}) end
322
+ e = d.parent
323
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 234) LIMIT 1"]
324
+ d.associations[:parent].should == e
325
+ end
326
+
327
+ it "should set cached instance variable when assigned" do
328
+ @c2.many_to_one :parent, :class => @c2
329
+
330
+ d = @c2.create(:id => 1)
331
+ MODEL_DB.reset
332
+ d.associations[:parent].should == nil
333
+ d.parent = @c2.new(:id => 234)
334
+ e = d.parent
335
+ d.associations[:parent].should == e
336
+ MODEL_DB.sqls.should == []
337
+ end
338
+
339
+ it "should use cached instance variable if available" do
340
+ @c2.many_to_one :parent, :class => @c2
341
+
342
+ d = @c2.create(:id => 1, :parent_id => 234)
343
+ MODEL_DB.reset
344
+ d.associations[:parent] = 42
345
+ d.parent.should == 42
346
+ MODEL_DB.sqls.should == []
347
+ end
348
+
349
+ it "should not use cached instance variable if asked to reload" do
350
+ @c2.many_to_one :parent, :class => @c2
351
+
352
+ d = @c2.create(:id => 1)
353
+ MODEL_DB.reset
354
+ d.parent_id = 234
355
+ d.associations[:parent] = 42
356
+ d.parent(true).should_not == 42
357
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 234) LIMIT 1"]
358
+ end
359
+
360
+ it "should have the setter add to the reciprocal one_to_many cached association list if it exists" do
361
+ @c2.many_to_one :parent, :class => @c2
362
+ @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
367
+
368
+ d = @c2.new(:id => 1)
369
+ e = @c2.new(:id => 2)
370
+ MODEL_DB.sqls.should == []
371
+ d.parent = e
372
+ e.children.should_not(include(d))
373
+ MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.parent_id = 2)']
374
+
375
+ MODEL_DB.reset
376
+ d = @c2.new(:id => 1)
377
+ e = @c2.new(:id => 2)
378
+ e.children.should_not(include(d))
379
+ MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.parent_id = 2)']
380
+ d.parent = e
381
+ e.children.should(include(d))
382
+ MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.parent_id = 2)']
383
+ end
384
+
385
+ it "should have many_to_one setter deal with a one_to_one reciprocal" do
386
+ @c2.many_to_one :parent, :class => @c2, :key=>:parent_id
387
+ @c2.one_to_one :child, :class => @c2, :key=>:parent_id
388
+
389
+ d = @c2.new(:id => 1)
390
+ e = @c2.new(:id => 2)
391
+ e.associations[:child] = nil
392
+ d.parent = e
393
+ e.child.should == d
394
+ d.parent = nil
395
+ e.child.should == nil
396
+ d.parent = e
397
+ e.child.should == d
398
+
399
+ f = @c2.new(:id => 3)
400
+ d.parent = nil
401
+ e.child.should == nil
402
+ e.associations[:child] = f
403
+ d.parent = e
404
+ e.child.should == d
405
+ end
406
+
407
+ 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
+ @c2.many_to_one :parent, :class => @c2
409
+ @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
414
+
415
+ d = @c2.new(:id => 1)
416
+ e = @c2.new(:id => 2)
417
+ f = @c2.new(:id => 3)
418
+ e.children.should_not(include(d))
419
+ f.children.should_not(include(d))
420
+ MODEL_DB.reset
421
+ d.parent = e
422
+ e.children.should(include(d))
423
+ d.parent = f
424
+ f.children.should(include(d))
425
+ e.children.should_not(include(d))
426
+ d.parent = nil
427
+ f.children.should_not(include(d))
428
+ MODEL_DB.sqls.should == []
429
+ end
430
+
431
+ it "should get all matching records and only return the first if :key option is set to nil" do
432
+ c2 = @c2
433
+ @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
443
+ p = @c2.new(:parent_id=>2)
444
+ fgp = p.first_grand_parent
445
+ 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)"]
446
+ fgp.values.should == {:id=>1, :parent_id=>0, :par_parent_id=>3, :blah=>4}
447
+ fgp.children.first.values.should == {:id=>2, :parent_id=>1, :par_parent_id=>5, :blah=>6}
448
+ end
449
+
450
+ it "should not create the setter method if :read_only option is used" do
451
+ @c2.many_to_one :parent, :class => @c2, :read_only=>true
452
+ @c2.instance_methods.collect{|x| x.to_s}.should(include('parent'))
453
+ @c2.instance_methods.collect{|x| x.to_s}.should_not(include('parent='))
454
+ end
455
+
456
+ it "should not add associations methods directly to class" do
457
+ @c2.many_to_one :parent, :class => @c2
458
+ @c2.instance_methods.collect{|x| x.to_s}.should(include('parent'))
459
+ @c2.instance_methods.collect{|x| x.to_s}.should(include('parent='))
460
+ @c2.instance_methods(false).collect{|x| x.to_s}.should_not(include('parent'))
461
+ @c2.instance_methods(false).collect{|x| x.to_s}.should_not(include('parent='))
462
+ end
463
+
464
+ it "should raise an error if trying to set a model object that doesn't have a valid primary key" do
465
+ @c2.many_to_one :parent, :class => @c2
466
+ p = @c2.new
467
+ c = @c2.load(:id=>123)
468
+ proc{c.parent = p}.should raise_error(Sequel::Error)
469
+ end
470
+
471
+ it "should make the change to the foreign_key value inside a _association= method" do
472
+ @c2.many_to_one :parent, :class => @c2
473
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_parent="))
474
+ p = @c2.new
475
+ c = @c2.load(:id=>123)
476
+ def p._parent=(x)
477
+ @x = x
478
+ end
479
+ p.should_not_receive(:parent_id=)
480
+ p.parent = c
481
+ p.instance_variable_get(:@x).should == c
482
+ end
483
+
484
+ it "should support (before|after)_set callbacks" do
485
+ h = []
486
+ @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}
487
+ @c2.class_eval do
488
+ @@blah = h
489
+ def []=(a, v)
490
+ a == :parent_id ? (@@blah << (v ? 4 : 5)) : super
491
+ end
492
+ def blah(x)
493
+ @@blah << (x ? x.pk : :x)
494
+ end
495
+ def blahr(x)
496
+ @@blah << 6
497
+ end
498
+ end
499
+ p = @c2.load(:id=>10)
500
+ c = @c2.load(:id=>123)
501
+ h.should == []
502
+ p.parent = c
503
+ h.should == [10, -123, 123, 4, 3]
504
+ p.parent = nil
505
+ h.should == [10, -123, 123, 4, 3, 10, :y, :x, 5, 3]
506
+ end
507
+
508
+ it "should support after_load association callback" do
509
+ h = []
510
+ @c2.many_to_one :parent, :class => @c2, :after_load=>[proc{|x,y| h << [x.pk, y.pk]}, :al]
511
+ @c2.class_eval do
512
+ @@blah = h
513
+ def al(v)
514
+ @@blah << v.pk
515
+ end
516
+ def @dataset.fetch_rows(sql)
517
+ yield({:id=>20})
518
+ end
519
+ end
520
+ p = @c2.load(:id=>10, :parent_id=>20)
521
+ parent = p.parent
522
+ h.should == [[10, 20], 20]
523
+ parent.pk.should == 20
524
+ end
525
+
526
+ 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
527
+ # The reason for this is that assignment in ruby always returns the argument instead of the result
528
+ # of the method, so we can't return nil to signal that the association callback prevented the modification
529
+ p = @c2.new
530
+ c = @c2.load(:id=>123)
531
+ p.raise_on_save_failure = false
532
+ @c2.many_to_one :parent, :class => @c2, :before_set=>:bs
533
+ p.meta_def(:bs){|x| false}
534
+ p.should_not_receive(:_parent=)
535
+ proc{p.parent = c}.should raise_error(Sequel::Error)
536
+
537
+ p.parent.should == nil
538
+ p.associations[:parent] = c
539
+ p.parent.should == c
540
+ proc{p.parent = nil}.should raise_error(Sequel::Error)
541
+ end
542
+
543
+ it "should raise an error if a callback is not a proc or symbol" do
544
+ @c2.many_to_one :parent, :class => @c2, :before_set=>Object.new
545
+ proc{@c2.new.parent = @c2.load(:id=>1)}.should raise_error(Sequel::Error)
546
+ end
547
+
548
+ it "should call the remove callbacks for the previous object and the add callbacks for the new object" do
549
+ c = @c2.load(:id=>123)
550
+ d = @c2.load(:id=>321)
551
+ p = @c2.new
552
+ p.associations[:parent] = d
553
+ h = []
554
+ @c2.many_to_one :parent, :class => @c2, :before_set=>:bs, :after_set=>:as
555
+ @c2.class_eval do
556
+ @@blah = h
557
+ def []=(a, v)
558
+ a == :parent_id ? (@@blah << 5) : super
559
+ end
560
+ def bs(x)
561
+ @@blah << x.pk
562
+ end
563
+ def as(x)
564
+ @@blah << x.pk * 2
565
+ end
566
+ end
567
+ p.parent = c
568
+ h.should == [123, 5, 246]
569
+ end
570
+ end
571
+
572
+ describe Sequel::Model, "one_to_one" do
573
+ before do
574
+ @c1 = Class.new(Sequel::Model(:attributes)) do
575
+ def _refresh(ds); end
576
+ unrestrict_primary_key
577
+ columns :id, :node_id, :y
578
+ end
579
+
580
+ @c2 = Class.new(Sequel::Model(:nodes)) do
581
+ def _refresh(ds); end
582
+ unrestrict_primary_key
583
+ attr_accessor :xxx
584
+
585
+ def self.name; 'Node'; end
586
+ def self.to_s; 'Node'; end
587
+ columns :id, :x, :parent_id, :par_parent_id, :blah, :node_id
588
+ end
589
+ @dataset = @c2.dataset
590
+
591
+ @c2.dataset.extend(Module.new {
592
+ def empty?; false; end
593
+ def fetch_rows(sql)
594
+ @db << sql
595
+ yield Hash.new
596
+ end
597
+ })
598
+
599
+ @c1.dataset.extend(Module.new {
600
+ def empty?; opts.has_key?(:empty) ? (super; true) : false; end
601
+ def fetch_rows(sql)
602
+ @db << sql
603
+ yield Hash.new
604
+ end
605
+ })
606
+
607
+ @dataset = @c2.dataset
608
+ MODEL_DB.reset
609
+ end
610
+
611
+ it "should have the getter method return a single object if the :one_to_one option is true" do
612
+ @c2.one_to_one :attribute, :class => @c1
613
+ att = @c2.new(:id => 1234).attribute
614
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234) LIMIT 1']
615
+ att.should be_a_kind_of(@c1)
616
+ att.values.should == {}
617
+ end
618
+
619
+ it "should not add a setter method if the :read_only option is true" do
620
+ @c2.one_to_one :attribute, :class => @c1, :read_only=>true
621
+ im = @c2.instance_methods.collect{|x| x.to_s}
622
+ im.should(include('attribute'))
623
+ im.should_not(include('attribute='))
624
+ end
625
+
626
+ it "should add a setter method" do
627
+ @c2.one_to_one :attribute, :class => @c1
628
+ attrib = @c1.new(:id=>3)
629
+ d = @c1.dataset
630
+ @c1.class_eval{remove_method :_refresh}
631
+ def d.fetch_rows(s); yield({:id=>3}) end
632
+ @c2.new(:id => 1234).attribute = attrib
633
+ ['INSERT INTO attributes (node_id, id) VALUES (1234, 3)',
634
+ 'INSERT INTO attributes (id, node_id) VALUES (3, 1234)'].should(include(MODEL_DB.sqls.last))
635
+ MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))'
636
+ MODEL_DB.sqls.length.should == 2
637
+ @c2.new(:id => 1234).attribute.should == attrib
638
+ MODEL_DB.sqls.clear
639
+ attrib = @c1.load(:id=>3)
640
+ @c2.new(:id => 1234).attribute = attrib
641
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))',
642
+ "UPDATE attributes SET node_id = 1234 WHERE (id = 3)"]
643
+ end
644
+
645
+ it "should use a transaction in the setter method" do
646
+ @c2.one_to_one :attribute, :class => @c1
647
+ @c2.use_transactions = true
648
+ MODEL_DB.sqls.clear
649
+ attrib = @c1.load(:id=>3)
650
+ @c2.new(:id => 1234).attribute = attrib
651
+ MODEL_DB.sqls.should == ['BEGIN',
652
+ 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))',
653
+ "UPDATE attributes SET node_id = 1234 WHERE (id = 3)",
654
+ 'COMMIT']
655
+ end
656
+
657
+ it "should have setter method respect association filters" do
658
+ @c2.one_to_one :attribute, :class => @c1, :conditions=>{:a=>1} do |ds|
659
+ ds.filter(:b=>2)
660
+ end
661
+ MODEL_DB.sqls.clear
662
+ attrib = @c1.load(:id=>3)
663
+ @c2.new(:id => 1234).attribute = attrib
664
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (a = 1) AND (b = 2) AND (id != 3))',
665
+ "UPDATE attributes SET node_id = 1234 WHERE (id = 3)"]
666
+ end
667
+
668
+ it "should have the setter method respect the :primary_key option" do
669
+ @c2.one_to_one :attribute, :class => @c1, :primary_key=>:xxx
670
+ attrib = @c1.new(:id=>3)
671
+ d = @c1.dataset
672
+ @c1.class_eval{remove_method :_refresh}
673
+ def d.fetch_rows(s); yield({:id=>3}) end
674
+ @c2.new(:id => 1234, :xxx=>5).attribute = attrib
675
+ ['INSERT INTO attributes (node_id, id) VALUES (5, 3)',
676
+ 'INSERT INTO attributes (id, node_id) VALUES (3, 5)'].should(include(MODEL_DB.sqls.last))
677
+ MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))'
678
+ MODEL_DB.sqls.length.should == 2
679
+ @c2.new(:id => 321, :xxx=>5).attribute.should == attrib
680
+ MODEL_DB.sqls.clear
681
+ attrib = @c1.load(:id=>3)
682
+ @c2.new(:id => 621, :xxx=>5).attribute = attrib
683
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))',
684
+ 'UPDATE attributes SET node_id = 5 WHERE (id = 3)']
685
+ end
686
+
687
+ it "should have the setter method respect composite keys" do
688
+ @c2.one_to_one :attribute, :class => @c1, :key=>[:node_id, :y], :primary_key=>[:id, :x]
689
+ attrib = @c1.load(:id=>3, :y=>6)
690
+ d = @c1.dataset
691
+ def d.fetch_rows(s); yield({:id=>3, :y=>6}) end
692
+ @c2.load(:id => 1234, :x=>5).attribute = attrib
693
+ MODEL_DB.sqls.last.should =~ /UPDATE attributes SET (node_id = 1234|y = 5), (node_id = 1234|y = 5) WHERE \(id = 3\)/
694
+ 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\)\)/
695
+ end
696
+
697
+ it "should use implicit key if omitted" do
698
+ @c2.one_to_one :parent, :class => @c2
699
+
700
+ d = @c2.new(:id => 234)
701
+ p = d.parent
702
+ p.class.should == @c2
703
+ p.values.should == {}
704
+
705
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.node_id = 234) LIMIT 1"]
706
+ end
707
+
708
+ it "should use implicit class if omitted" do
709
+ class ::ParParent < Sequel::Model
710
+ end
711
+
712
+ @c2.one_to_one :par_parent
713
+
714
+ d = @c2.new(:id => 234)
715
+ p = d.par_parent
716
+ p.class.should == ParParent
717
+
718
+ MODEL_DB.sqls.should == ["SELECT * FROM par_parents WHERE (par_parents.node_id = 234) LIMIT 1"]
719
+ end
720
+
721
+ it "should use class inside module if given as a string" do
722
+ module ::Par
723
+ class Parent < Sequel::Model
724
+ end
725
+ end
726
+
727
+ @c2.one_to_one :par_parent, :class=>"Par::Parent"
728
+
729
+ d = @c2.new(:id => 234)
730
+ p = d.par_parent
731
+ p.class.should == Par::Parent
732
+
733
+ MODEL_DB.sqls.should == ["SELECT * FROM parents WHERE (parents.node_id = 234) LIMIT 1"]
734
+ end
735
+
736
+ it "should use explicit key if given" do
737
+ @c2.one_to_one :parent, :class => @c2, :key => :blah
738
+
739
+ d = @c2.new(:id => 234)
740
+ p = d.parent
741
+ p.class.should == @c2
742
+ p.values.should == {}
743
+
744
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.blah = 234) LIMIT 1"]
745
+ end
746
+
747
+ it "should use :primary_key option if given" do
748
+ @c2.one_to_one :parent, :class => @c2, :key => :pk, :primary_key => :blah
749
+ @c2.new(:id => 1, :blah => 567).parent
750
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.pk = 567) LIMIT 1"]
751
+ end
752
+
753
+ it "should support composite keys" do
754
+ @c2.one_to_one :parent, :class => @c2, :primary_key=>[:id, :parent_id], :key=>[:parent_id, :id]
755
+ @c2.new(:id => 1, :parent_id => 234).parent
756
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.parent_id = 1) AND (nodes.id = 234)) LIMIT 1"]
757
+ end
758
+
759
+ it "should not issue query if not all keys have values" do
760
+ @c2.one_to_one :parent, :class => @c2, :key=>[:id, :parent_id], :primary_key=>[:parent_id, :id]
761
+ @c2.new(:id => 1, :parent_id => nil).parent.should == nil
762
+ MODEL_DB.sqls.should == []
763
+ end
764
+
765
+ it "should raise an Error unless same number of composite keys used" do
766
+ proc{@c2.one_to_one :parent, :class => @c2, :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
767
+ proc{@c2.one_to_one :parent, :class => @c2, :key=>[:id, :parent_id], :primary_key=>:id}.should raise_error(Sequel::Error)
768
+ proc{@c2.one_to_one :parent, :class => @c2, :key=>:id, :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
769
+ proc{@c2.one_to_one :parent, :class => @c2, :key=>[:id, :parent_id, :blah], :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
770
+ end
771
+
772
+ it "should use :select option if given" do
773
+ @c2.one_to_one :parent, :class => @c2, :select=>[:id, :name]
774
+ @c2.new(:id => 567).parent
775
+ MODEL_DB.sqls.should == ["SELECT id, name FROM nodes WHERE (nodes.node_id = 567) LIMIT 1"]
776
+ end
777
+
778
+ it "should use :conditions option if given" do
779
+ @c2.one_to_one :parent, :class => @c2, :conditions=>{:a=>32}
780
+ @c2.new(:id => 567).parent
781
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.node_id = 567) AND (a = 32)) LIMIT 1"]
782
+
783
+ @c2.one_to_one :parent, :class => @c2, :conditions=>:a
784
+ MODEL_DB.sqls.clear
785
+ @c2.new(:id => 567).parent
786
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.node_id = 567) AND a) LIMIT 1"]
787
+ end
788
+
789
+ it "should support :order, :limit (only for offset), and :dataset options, as well as a block" do
790
+ c2 = @c2
791
+ @c2.one_to_one :child_20, :class => @c2, :key=>:id, :dataset=>proc{c2.filter(:parent_id=>pk)}, :limit=>[10,20], :order=>:name do |ds|
792
+ ds.filter(:x.sql_number > 1)
793
+ end
794
+ @c2.load(:id => 100).child_20
795
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((parent_id = 100) AND (x > 1)) ORDER BY name LIMIT 1 OFFSET 20"]
796
+ end
797
+
798
+ it "should return nil if primary_key value is nil" do
799
+ @c2.one_to_one :parent, :class => @c2, :primary_key=>:node_id
800
+
801
+ d = @c2.new(:id => 1)
802
+ d.parent.should == nil
803
+ MODEL_DB.sqls.should == []
804
+ end
805
+
806
+ it "should cache negative lookup" do
807
+ @c2.one_to_one :parent, :class => @c2
808
+ ds = @c2.dataset
809
+ def ds.fetch_rows(sql, &block)
810
+ MODEL_DB.sqls << sql
811
+ end
812
+
813
+ d = @c2.new(:id => 555)
814
+ MODEL_DB.sqls.should == []
815
+ d.parent.should == nil
816
+ MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.node_id = 555) LIMIT 1']
817
+ d.parent.should == nil
818
+ MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.node_id = 555) LIMIT 1']
819
+ end
820
+
821
+ it "should define a setter method" do
822
+ @c2.one_to_one :parent, :class => @c2
823
+
824
+ d = @c2.new(:id => 1)
825
+ f = @c2.new(:id => 3, :node_id=> 4321)
826
+ d.parent = f
827
+ f.values.should == {:id => 3, :node_id=>1}
828
+ d.parent.should == f
829
+
830
+ d.parent = nil
831
+ d.parent.should == nil
832
+ end
833
+
834
+ it "should have the setter method respect the :primary_key option" do
835
+ @c2.one_to_one :parent, :class => @c2, :primary_key=>:blah
836
+ d = @c2.new(:id => 1, :blah => 3)
837
+ e = @c2.new(:id => 4321, :node_id=>444)
838
+ d.parent = e
839
+ e.values.should == {:id => 4321, :node_id => 3}
840
+ end
841
+
842
+ it "should have the setter method respect the :key option" do
843
+ @c2.one_to_one :parent, :class => @c2, :key=>:blah
844
+ d = @c2.new(:id => 3)
845
+ e = @c2.new(:id => 4321, :blah=>444)
846
+ d.parent = e
847
+ e.values.should == {:id => 4321, :blah => 3}
848
+ end
849
+
850
+ it "should persist changes to associated object when the setter is called" do
851
+ @c2.one_to_one :parent, :class => @c2
852
+ d = @c2.load(:id => 1)
853
+ d.parent = @c2.load(:id => 3, :node_id=>345)
854
+ MODEL_DB.sqls.should == ["UPDATE nodes SET node_id = NULL WHERE ((node_id = 1) AND (id != 3))",
855
+ "UPDATE nodes SET node_id = 1 WHERE (id = 3)"]
856
+ end
857
+
858
+ it "should set cached instance variable when accessed" do
859
+ @c2.one_to_one :parent, :class => @c2
860
+
861
+ d = @c2.load(:id => 1)
862
+ d.associations[:parent].should == nil
863
+ ds = @c2.dataset
864
+ def ds.fetch_rows(sql, &block); MODEL_DB.sqls << sql; yield({:id=>234}) end
865
+ e = d.parent
866
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.node_id = 1) LIMIT 1"]
867
+ d.parent
868
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.node_id = 1) LIMIT 1"]
869
+ d.associations[:parent].should == e
870
+ end
871
+
872
+ it "should set cached instance variable when assigned" do
873
+ @c2.one_to_one :parent, :class => @c2
874
+
875
+ d = @c2.load(:id => 1)
876
+ d.associations[:parent].should == nil
877
+ e = @c2.load(:id => 234)
878
+ d.parent = e
879
+ f = d.parent
880
+ d.associations[:parent].should == e
881
+ e.should == f
882
+ end
883
+
884
+ it "should use cached instance variable if available" do
885
+ @c2.one_to_one :parent, :class => @c2
886
+ d = @c2.load(:id => 1, :parent_id => 234)
887
+ d.associations[:parent] = 42
888
+ d.parent.should == 42
889
+ MODEL_DB.sqls.should == []
890
+ end
891
+
892
+ it "should not use cached instance variable if asked to reload" do
893
+ @c2.one_to_one :parent, :class => @c2
894
+ d = @c2.load(:id => 1)
895
+ d.associations[:parent] = [42]
896
+ d.parent(true).should_not == 42
897
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.node_id = 1) LIMIT 1"]
898
+ end
899
+
900
+ it "should have the setter set the reciprocal many_to_one cached association" do
901
+ @c2.one_to_one :parent, :class => @c2, :key=>:parent_id
902
+ @c2.many_to_one :child, :class => @c2, :key=>:parent_id
903
+
904
+ d = @c2.load(:id => 1)
905
+ e = @c2.load(:id => 2)
906
+ d.parent = e
907
+ e.child.should == d
908
+ MODEL_DB.sqls.should == ["UPDATE nodes SET parent_id = NULL WHERE ((parent_id = 1) AND (id != 2))",
909
+ "UPDATE nodes SET parent_id = 1 WHERE (id = 2)"]
910
+ MODEL_DB.reset
911
+ d.parent = nil
912
+ e.child.should == nil
913
+ MODEL_DB.sqls.should == ["UPDATE nodes SET parent_id = NULL WHERE (parent_id = 1)"]
914
+ end
915
+
916
+ 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
917
+ @c2.one_to_one :parent, :class => @c2, :key=>:parent_id
918
+ @c2.many_to_one :child, :class => @c2, :key=>:parent_id
919
+ ds = @c2.dataset
920
+ def ds.fetch_rows(sql, &block)
921
+ MODEL_DB.sqls << sql
922
+ end
923
+
924
+ d = @c2.load(:id => 1)
925
+ e = @c2.load(:id => 2)
926
+ f = @c2.load(:id => 3)
927
+ e.child.should == nil
928
+ f.child.should == nil
929
+ MODEL_DB.reset
930
+ d.parent = e
931
+ e.child.should == d
932
+ d.parent = f
933
+ f.child.should == d
934
+ e.child.should == nil
935
+ d.parent = nil
936
+ f.child.should == nil
937
+ end
938
+
939
+ it "should not add associations methods directly to class" do
940
+ @c2.one_to_one :parent, :class => @c2
941
+ @c2.instance_methods.collect{|x| x.to_s}.should(include('parent'))
942
+ @c2.instance_methods.collect{|x| x.to_s}.should(include('parent='))
943
+ @c2.instance_methods(false).collect{|x| x.to_s}.should_not(include('parent'))
944
+ @c2.instance_methods(false).collect{|x| x.to_s}.should_not(include('parent='))
945
+ end
946
+
947
+ it "should raise an error if the current model object that doesn't have a valid primary key" do
948
+ @c2.one_to_one :parent, :class => @c2
949
+ p = @c2.new
950
+ c = @c2.load(:id=>123)
951
+ proc{p.parent = c}.should raise_error(Sequel::Error)
952
+ end
953
+
954
+ it "should make the change to the foreign_key value inside a _association= method" do
955
+ @c2.one_to_one :parent, :class => @c2
956
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_parent="))
957
+ c = @c2.new
958
+ p = @c2.load(:id=>123)
959
+ def p._parent=(x)
960
+ @x = x
961
+ end
962
+ p.should_not_receive(:parent_id=)
963
+ p.parent = c
964
+ p.instance_variable_get(:@x).should == c
965
+ end
966
+
967
+ it "should support (before|after)_set callbacks" do
968
+ h = []
969
+ @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}
970
+ @c2.class_eval do
971
+ @@blah = h
972
+ def blah(x)
973
+ @@blah << (x ? x.pk : :x)
974
+ end
975
+ def blahr(x)
976
+ @@blah << 6
977
+ end
978
+ end
979
+ p = @c2.load(:id=>10)
980
+ c = @c2.load(:id=>123)
981
+ h.should == []
982
+ p.parent = c
983
+ h.should == [10, -123, 123, 3]
984
+ p.parent = nil
985
+ h.should == [10, -123, 123, 3, 10, :y, :x, 3]
986
+ end
987
+
988
+ it "should support after_load association callback" do
989
+ h = []
990
+ @c2.one_to_one :parent, :class => @c2, :after_load=>[proc{|x,y| h << [x.pk, y.pk]}, :al]
991
+ @c2.class_eval do
992
+ @@blah = h
993
+ def al(v)
994
+ @@blah << v.pk
995
+ end
996
+ def @dataset.fetch_rows(sql)
997
+ yield({:id=>20})
998
+ end
999
+ end
1000
+ p = @c2.load(:id=>10)
1001
+ parent = p.parent
1002
+ h.should == [[10, 20], 20]
1003
+ parent.pk.should == 20
1004
+ end
1005
+
1006
+ 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
1007
+ # The reason for this is that assignment in ruby always returns the argument instead of the result
1008
+ # of the method, so we can't return nil to signal that the association callback prevented the modification
1009
+ p = @c2.new
1010
+ c = @c2.load(:id=>123)
1011
+ p.raise_on_save_failure = false
1012
+ @c2.one_to_one :parent, :class => @c2, :before_set=>:bs
1013
+ p.meta_def(:bs){|x| false}
1014
+ p.should_not_receive(:_parent=)
1015
+ proc{p.parent = c}.should raise_error(Sequel::Error)
1016
+
1017
+ p.parent.should == nil
1018
+ p.associations[:parent] = c
1019
+ p.parent.should == c
1020
+ proc{p.parent = nil}.should raise_error(Sequel::Error)
1021
+ end
1022
+
1023
+ it "should raise an error if a callback is not a proc or symbol" do
1024
+ @c2.one_to_one :parent, :class => @c2, :before_set=>Object.new
1025
+ proc{@c2.new.parent = @c2.load(:id=>1)}.should raise_error(Sequel::Error)
1026
+ end
1027
+
1028
+ it "should call the set callbacks" do
1029
+ c = @c2.load(:id=>123)
1030
+ d = @c2.load(:id=>321)
1031
+ p = @c2.load(:id=>32)
1032
+ p.associations[:parent] = [d]
1033
+ h = []
1034
+ @c2.one_to_one :parent, :class => @c2, :before_set=>:bs, :after_set=>:as
1035
+ @c2.class_eval do
1036
+ @@blah = h
1037
+ def []=(a, v)
1038
+ a == :node_id ? (@@blah << 5) : super
1039
+ end
1040
+ def bs(x)
1041
+ @@blah << x.pk
1042
+ end
1043
+ def as(x)
1044
+ @@blah << x.pk * 2
1045
+ end
1046
+ end
1047
+ p.parent = c
1048
+ h.should == [123, 5, 246]
1049
+ end
1050
+
1051
+ it "should work_correctly when used with associate" do
1052
+ @c2.associate :one_to_one, :parent, :class => @c2
1053
+ @c2.load(:id => 567).parent.should == @c2.load({})
1054
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.node_id = 567) LIMIT 1"]
1055
+ end
1056
+ end
1057
+
1058
+ describe Sequel::Model, "one_to_many" do
1059
+ before do
1060
+ MODEL_DB.reset
1061
+
1062
+ @c1 = Class.new(Sequel::Model(:attributes)) do
1063
+ def _refresh(ds); end
1064
+ unrestrict_primary_key
1065
+ columns :id, :node_id, :y
1066
+ end
1067
+
1068
+ @c2 = Class.new(Sequel::Model(:nodes)) do
1069
+ def _refresh(ds); end
1070
+ unrestrict_primary_key
1071
+ attr_accessor :xxx
1072
+
1073
+ def self.name; 'Node'; end
1074
+ def self.to_s; 'Node'; end
1075
+ columns :id, :x
1076
+ end
1077
+ @dataset = @c2.dataset
1078
+
1079
+ @c2.dataset.extend(Module.new {
1080
+ def empty?; false; end
1081
+ def fetch_rows(sql)
1082
+ @db << sql
1083
+ yield Hash.new
1084
+ end
1085
+ })
1086
+
1087
+ @c1.dataset.extend(Module.new {
1088
+ def empty?; opts.has_key?(:empty) ? (super; true) : false; end
1089
+ def fetch_rows(sql)
1090
+ @db << sql
1091
+ yield Hash.new
1092
+ end
1093
+ })
1094
+ end
1095
+
1096
+ it "should use implicit key if omitted" do
1097
+ @c2.one_to_many :attributes, :class => @c1
1098
+
1099
+ n = @c2.new(:id => 1234)
1100
+ a = n.attributes_dataset
1101
+ a.should be_a_kind_of(Sequel::Dataset)
1102
+ a.sql.should == 'SELECT * FROM attributes WHERE (attributes.node_id = 1234)'
1103
+ end
1104
+
1105
+ it "should use implicit class if omitted" do
1106
+ class ::HistoricalValue < Sequel::Model
1107
+ end
1108
+
1109
+ @c2.one_to_many :historical_values
1110
+
1111
+ n = @c2.new(:id => 1234)
1112
+ v = n.historical_values_dataset
1113
+ v.should be_a_kind_of(Sequel::Dataset)
1114
+ v.sql.should == 'SELECT * FROM historical_values WHERE (historical_values.node_id = 1234)'
1115
+ v.model.should == HistoricalValue
1116
+ end
1117
+
1118
+ it "should use class inside a module if given as a string" do
1119
+ module ::Historical
1120
+ class Value < Sequel::Model
1121
+ end
1122
+ end
1123
+
1124
+ @c2.one_to_many :historical_values, :class=>'Historical::Value'
1125
+
1126
+ n = @c2.new(:id => 1234)
1127
+ v = n.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
+ end
1132
+
1133
+ it "should use explicit key if given" do
1134
+ @c2.one_to_many :attributes, :class => @c1, :key => :nodeid
1135
+
1136
+ n = @c2.new(:id => 1234)
1137
+ a = n.attributes_dataset
1138
+ a.should be_a_kind_of(Sequel::Dataset)
1139
+ a.sql.should == 'SELECT * FROM attributes WHERE (attributes.nodeid = 1234)'
1140
+ end
1141
+
1142
+ it "should support_composite keys" do
1143
+ @c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :id], :primary_key=>[:id, :x]
1144
+ @c2.load(:id => 1234, :x=>234).attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (attributes.id = 234))'
1145
+ end
1146
+
1147
+ it "should not issue query if not all keys have values" do
1148
+ @c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :id], :primary_key=>[:id, :x]
1149
+ @c2.load(:id => 1234, :x=>nil).attributes.should == []
1150
+ MODEL_DB.sqls.should == []
1151
+ end
1152
+
1153
+ it "should raise an Error unless same number of composite keys used" do
1154
+ proc{@c2.one_to_many :attributes, :class => @c1, :key=>[:node_id, :id]}.should raise_error(Sequel::Error)
1155
+ proc{@c2.one_to_many :attributes, :class => @c1, :primary_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
1156
+ proc{@c2.one_to_many :attributes, :class => @c1, :key=>[:node_id, :id], :primary_key=>:id}.should raise_error(Sequel::Error)
1157
+ proc{@c2.one_to_many :attributes, :class => @c1, :key=>:id, :primary_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
1158
+ proc{@c2.one_to_many :attributes, :class => @c1, :key=>[:node_id, :id, :x], :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
1159
+ end
1160
+
1161
+ it "should define an add_ method that works on existing records" do
1162
+ @c2.one_to_many :attributes, :class => @c1
1163
+
1164
+ n = @c2.new(:id => 1234)
1165
+ a = @c1.new(:id => 2345)
1166
+ a.save
1167
+ MODEL_DB.reset
1168
+ a.should == n.add_attribute(a)
1169
+ a.values.should == {:node_id => 1234, :id => 2345}
1170
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 1234 WHERE (id = 2345)']
1171
+ end
1172
+
1173
+ it "should define an add_ method that works on new records" do
1174
+ @c2.one_to_many :attributes, :class => @c1
1175
+
1176
+ n = @c2.new(:id => 1234)
1177
+ a = @c1.new(:id => 234)
1178
+ # do not save
1179
+ MODEL_DB.reset
1180
+ a.should == n.add_attribute(a)
1181
+ MODEL_DB.sqls.first.should =~ /INSERT INTO attributes \((node_)?id, (node_)?id\) VALUES \(1?234, 1?234\)/
1182
+ a.values.should == {:node_id => 1234, :id => 234}
1183
+ end
1184
+
1185
+ it "should define a remove_ method that works on existing records" do
1186
+ @c2.one_to_many :attributes, :class => @c1
1187
+
1188
+ n = @c2.new(:id => 1234)
1189
+ a = @c1.new(:id => 2345, :node_id => 1234)
1190
+ a.save
1191
+ MODEL_DB.reset
1192
+ a.should == n.remove_attribute(a)
1193
+ a.values.should == {:node_id => nil, :id => 2345}
1194
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE (id = 2345)']
1195
+ end
1196
+
1197
+ it "should have the remove_ method raise an error if the passed object is not already associated" do
1198
+ @c2.one_to_many :attributes, :class => @c1
1199
+ @c1.dataset.opts[:empty] = true
1200
+
1201
+ n = @c2.new(:id => 1234)
1202
+ a = @c1.load(:id => 2345, :node_id => 1234)
1203
+ MODEL_DB.reset
1204
+ proc{n.remove_attribute(a)}.should raise_error(Sequel::Error)
1205
+ MODEL_DB.sqls.should == ["SELECT 1 FROM attributes WHERE ((attributes.node_id = 1234) AND (id = 2345)) LIMIT 1"]
1206
+ end
1207
+
1208
+ it "should accept a hash for the add_ method and create a new record" do
1209
+ @c2.one_to_many :attributes, :class => @c1
1210
+ n = @c2.new(:id => 1234)
1211
+ MODEL_DB.reset
1212
+ @c1.load(:node_id => 1234, :id => 234).should == n.add_attribute(:id => 234)
1213
+ MODEL_DB.sqls.first.should =~ /INSERT INTO attributes \((node_)?id, (node_)?id\) VALUES \(1?234, 1?234\)/
1214
+ end
1215
+
1216
+ it "should raise an error in the add_ method if the passed associated object is not of the correct type" do
1217
+ @c2.one_to_many :attributes, :class => @c1
1218
+ proc{@c2.new(:id => 1234).add_attribute(@c2.new)}.should raise_error(Sequel::Error)
1219
+ end
1220
+
1221
+ it "should accept a primary key for the remove_ method and remove an existing record" do
1222
+ @c2.one_to_many :attributes, :class => @c1
1223
+ n = @c2.new(:id => 1234)
1224
+ ds = @c1.dataset
1225
+ def ds.fetch_rows(sql)
1226
+ db << sql
1227
+ yield({:id=>234, :node_id=>1234})
1228
+ end
1229
+ MODEL_DB.reset
1230
+ @c1.load(:node_id => nil, :id => 234).should == n.remove_attribute(234)
1231
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (id = 234)) LIMIT 1',
1232
+ 'UPDATE attributes SET node_id = NULL WHERE (id = 234)']
1233
+ end
1234
+
1235
+ it "should raise an error in the remove_ method if the passed associated object is not of the correct type" do
1236
+ @c2.one_to_many :attributes, :class => @c1
1237
+ proc{@c2.new(:id => 1234).remove_attribute(@c2.new)}.should raise_error(Sequel::Error)
1238
+ end
1239
+
1240
+ it "should have add_ method respect the :primary_key option" do
1241
+ @c2.one_to_many :attributes, :class => @c1, :primary_key=>:xxx
1242
+
1243
+ n = @c2.new(:id => 1234, :xxx=>5)
1244
+ a = @c1.new(:id => 2345)
1245
+ a.save
1246
+ MODEL_DB.reset
1247
+ a.should == n.add_attribute(a)
1248
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 5 WHERE (id = 2345)']
1249
+ end
1250
+
1251
+ it "should have add_ method not add the same object to the cached association array if the object is already in the array" do
1252
+ @c2.one_to_many :attributes, :class => @c1
1253
+
1254
+ n = @c2.new(:id => 1234)
1255
+ a = @c1.new(:id => 2345)
1256
+ a.save
1257
+ MODEL_DB.reset
1258
+ n.associations[:attributes] = []
1259
+ a.should == n.add_attribute(a)
1260
+ a.should == n.add_attribute(a)
1261
+ a.values.should == {:node_id => 1234, :id => 2345}
1262
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 1234 WHERE (id = 2345)'] * 2
1263
+ n.attributes.should == [a]
1264
+ end
1265
+
1266
+ it "should have add_ method respect composite keys" do
1267
+ @c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :y], :primary_key=>[:id, :x]
1268
+
1269
+ n = @c2.load(:id => 1234, :x=>5)
1270
+ a = @c1.load(:id => 2345)
1271
+ a.should == n.add_attribute(a)
1272
+ MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id = 1234|y = 5), (node_id = 1234|y = 5) WHERE \(id = 2345\)/
1273
+ end
1274
+
1275
+ it "should have remove_ method respect composite keys" do
1276
+ @c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :y], :primary_key=>[:id, :x]
1277
+
1278
+ n = @c2.load(:id => 1234, :x=>5)
1279
+ a = @c1.load(:id => 2345, :node_id=>1234, :y=>5)
1280
+ a.should == n.remove_attribute(a)
1281
+ MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id|y) = NULL, (node_id|y) = NULL WHERE \(id = 2345\)/
1282
+ end
1283
+
1284
+ it "should accept a array of composite primary key values for the remove_ method and remove an existing record" do
1285
+ @c1.set_primary_key :id, :y
1286
+ @c2.one_to_many :attributes, :class => @c1, :key=>:node_id, :primary_key=>:id
1287
+ n = @c2.new(:id => 123)
1288
+ ds = @c1.dataset
1289
+ def ds.fetch_rows(sql)
1290
+ db << sql
1291
+ yield({:id=>234, :node_id=>123, :y=>5})
1292
+ end
1293
+ MODEL_DB.reset
1294
+ @c1.load(:node_id => nil, :y => 5, :id => 234).should == n.remove_attribute([234, 5])
1295
+ MODEL_DB.sqls.length.should == 2
1296
+ MODEL_DB.sqls.first.should =~ /SELECT \* FROM attributes WHERE \(\(attributes.node_id = 123\) AND \((id|y) = (234|5)\) AND \((id|y) = (234|5)\)\) LIMIT 1/
1297
+ MODEL_DB.sqls.last.should =~ /UPDATE attributes SET node_id = NULL WHERE \(\((id|y) = (234|5)\) AND \((id|y) = (234|5)\)\)/
1298
+ end
1299
+
1300
+ it "should raise an error in add_ and remove_ if the passed object returns false to save (is not valid)" do
1301
+ @c2.one_to_many :attributes, :class => @c1
1302
+ n = @c2.new(:id => 1234)
1303
+ a = @c1.new(:id => 2345)
1304
+ def a.valid?; false; end
1305
+ proc{n.add_attribute(a)}.should raise_error(Sequel::Error)
1306
+ proc{n.remove_attribute(a)}.should raise_error(Sequel::Error)
1307
+ end
1308
+
1309
+ it "should not validate the associated object in add_ and remove_ if the :validate=>false option is used" do
1310
+ @c2.one_to_many :attributes, :class => @c1, :validate=>false
1311
+ n = @c2.new(:id => 1234)
1312
+ a = @c1.new(:id => 2345)
1313
+ def a.valid?; false; end
1314
+ n.add_attribute(a).should == a
1315
+ n.remove_attribute(a).should == a
1316
+ end
1317
+
1318
+ it "should raise an error if the model object doesn't have a valid primary key" do
1319
+ @c2.one_to_many :attributes, :class => @c1
1320
+ a = @c2.new
1321
+ n = @c1.load(:id=>123)
1322
+ proc{a.attributes_dataset}.should raise_error(Sequel::Error)
1323
+ proc{a.add_attribute(n)}.should raise_error(Sequel::Error)
1324
+ proc{a.remove_attribute(n)}.should raise_error(Sequel::Error)
1325
+ proc{a.remove_all_attributes}.should raise_error(Sequel::Error)
1326
+ end
1327
+
1328
+ it "should use :primary_key option if given" do
1329
+ @c1.one_to_many :nodes, :class => @c2, :primary_key => :node_id, :key=>:id
1330
+ n = @c1.load(:id => 1234, :node_id=>4321)
1331
+ n.nodes_dataset.sql.should == "SELECT * FROM nodes WHERE (nodes.id = 4321)"
1332
+ end
1333
+
1334
+ it "should support a select option" do
1335
+ @c2.one_to_many :attributes, :class => @c1, :select => [:id, :name]
1336
+
1337
+ n = @c2.new(:id => 1234)
1338
+ n.attributes_dataset.sql.should == "SELECT id, name FROM attributes WHERE (attributes.node_id = 1234)"
1339
+ end
1340
+
1341
+ it "should support a conditions option" do
1342
+ @c2.one_to_many :attributes, :class => @c1, :conditions => {:a=>32}
1343
+ n = @c2.new(:id => 1234)
1344
+ n.attributes_dataset.sql.should == "SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (a = 32))"
1345
+ @c2.one_to_many :attributes, :class => @c1, :conditions => ~:a
1346
+ n = @c2.new(:id => 1234)
1347
+ n.attributes_dataset.sql.should == "SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND NOT a)"
1348
+ end
1349
+
1350
+ it "should support an order option" do
1351
+ @c2.one_to_many :attributes, :class => @c1, :order => :kind
1352
+
1353
+ n = @c2.new(:id => 1234)
1354
+ n.attributes_dataset.sql.should == "SELECT * FROM attributes WHERE (attributes.node_id = 1234) ORDER BY kind"
1355
+ end
1356
+
1357
+ it "should support an array for the order option" do
1358
+ @c2.one_to_many :attributes, :class => @c1, :order => [:kind1, :kind2]
1359
+
1360
+ n = @c2.new(:id => 1234)
1361
+ n.attributes_dataset.sql.should == "SELECT * FROM attributes WHERE (attributes.node_id = 1234) ORDER BY kind1, kind2"
1362
+ end
1363
+
1364
+ it "should return array with all members of the association" do
1365
+ @c2.one_to_many :attributes, :class => @c1
1366
+
1367
+ n = @c2.new(:id => 1234)
1368
+ atts = n.attributes
1369
+ atts.should be_a_kind_of(Array)
1370
+ atts.size.should == 1
1371
+ atts.first.should be_a_kind_of(@c1)
1372
+ atts.first.values.should == {}
1373
+
1374
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
1375
+ end
1376
+
1377
+ it "should accept a block" do
1378
+ @c2.one_to_many :attributes, :class => @c1 do |ds|
1379
+ ds.filter(:xxx => @xxx)
1380
+ end
1381
+
1382
+ n = @c2.new(:id => 1234)
1383
+ atts = n.attributes
1384
+ atts.should be_a_kind_of(Array)
1385
+ atts.size.should == 1
1386
+ atts.first.should be_a_kind_of(@c1)
1387
+ atts.first.values.should == {}
1388
+
1389
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (xxx IS NULL))']
1390
+ end
1391
+
1392
+ it "should support :order option with block" do
1393
+ @c2.one_to_many :attributes, :class => @c1, :order => :kind do |ds|
1394
+ ds.filter(:xxx => @xxx)
1395
+ end
1396
+
1397
+ n = @c2.new(:id => 1234)
1398
+ atts = n.attributes
1399
+ atts.should be_a_kind_of(Array)
1400
+ atts.size.should == 1
1401
+ atts.first.should be_a_kind_of(@c1)
1402
+ atts.first.values.should == {}
1403
+
1404
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (xxx IS NULL)) ORDER BY kind']
1405
+ end
1406
+
1407
+ it "should have the block argument affect the _dataset method" do
1408
+ @c2.one_to_many :attributes, :class => @c1 do |ds|
1409
+ ds.filter(:xxx => 456)
1410
+ end
1411
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (xxx = 456))'
1412
+ end
1413
+
1414
+ it "should support a :dataset option that is used instead of the default" do
1415
+ c1 = @c1
1416
+ @c2.one_to_many :all_other_attributes, :class => @c1, :dataset=>proc{c1.filter(:nodeid=>pk).invert}, :order=>:a, :limit=>10 do |ds|
1417
+ ds.filter(:xxx => 5)
1418
+ end
1419
+
1420
+ @c2.new(:id => 1234).all_other_attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE ((nodeid != 1234) AND (xxx = 5)) ORDER BY a LIMIT 10'
1421
+ n = @c2.new(:id => 1234)
1422
+ atts = n.all_other_attributes
1423
+ atts.should be_a_kind_of(Array)
1424
+ atts.size.should == 1
1425
+ atts.first.should be_a_kind_of(@c1)
1426
+ atts.first.values.should == {}
1427
+
1428
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE ((nodeid != 1234) AND (xxx = 5)) ORDER BY a LIMIT 10']
1429
+ end
1430
+
1431
+ it "should support a :limit option" do
1432
+ @c2.one_to_many :attributes, :class => @c1 , :limit=>10
1433
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE (attributes.node_id = 1234) LIMIT 10'
1434
+ @c2.one_to_many :attributes, :class => @c1 , :limit=>[10,10]
1435
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE (attributes.node_id = 1234) LIMIT 10 OFFSET 10'
1436
+ end
1437
+
1438
+ it "should have the :eager option affect the _dataset method" do
1439
+ @c2.one_to_many :attributes, :class => @c2 , :eager=>:attributes
1440
+ @c2.new(:id => 1234).attributes_dataset.opts[:eager].should == {:attributes=>nil}
1441
+ end
1442
+
1443
+ it "should set cached instance variable when accessed" do
1444
+ @c2.one_to_many :attributes, :class => @c1
1445
+
1446
+ n = @c2.new(:id => 1234)
1447
+ MODEL_DB.reset
1448
+ n.associations.include?(:attributes).should == false
1449
+ atts = n.attributes
1450
+ atts.should == n.associations[:attributes]
1451
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
1452
+ end
1453
+
1454
+ it "should use cached instance variable if available" do
1455
+ @c2.one_to_many :attributes, :class => @c1
1456
+
1457
+ n = @c2.new(:id => 1234)
1458
+ MODEL_DB.reset
1459
+ n.associations[:attributes] = 42
1460
+ n.attributes.should == 42
1461
+ MODEL_DB.sqls.should == []
1462
+ end
1463
+
1464
+ it "should not use cached instance variable if asked to reload" do
1465
+ @c2.one_to_many :attributes, :class => @c1
1466
+
1467
+ n = @c2.new(:id => 1234)
1468
+ MODEL_DB.reset
1469
+ n.associations[:attributes] = 42
1470
+ n.attributes(true).should_not == 42
1471
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
1472
+ end
1473
+
1474
+ it "should add item to cached instance variable if it exists when calling add_" do
1475
+ @c2.one_to_many :attributes, :class => @c1
1476
+
1477
+ n = @c2.new(:id => 1234)
1478
+ att = @c1.new(:id => 345)
1479
+ MODEL_DB.reset
1480
+ a = []
1481
+ n.associations[:attributes] = a
1482
+ n.add_attribute(att)
1483
+ a.should == [att]
1484
+ end
1485
+
1486
+ it "should set object to item's reciprocal cached association variable when calling add_" do
1487
+ @c2.one_to_many :attributes, :class => @c1
1488
+ @c1.many_to_one :node, :class => @c2
1489
+
1490
+ n = @c2.new(:id => 1234)
1491
+ att = @c1.new(:id => 345)
1492
+ n.add_attribute(att)
1493
+ att.node.should == n
1494
+ end
1495
+
1496
+ it "should remove item from cached instance variable if it exists when calling remove_" do
1497
+ @c2.one_to_many :attributes, :class => @c1
1498
+
1499
+ n = @c2.load(:id => 1234)
1500
+ att = @c1.load(:id => 345)
1501
+ MODEL_DB.reset
1502
+ a = [att]
1503
+ n.associations[:attributes] = a
1504
+ n.remove_attribute(att)
1505
+ a.should == []
1506
+ end
1507
+
1508
+ it "should remove item's reciprocal cached association variable when calling remove_" do
1509
+ @c2.one_to_many :attributes, :class => @c1
1510
+ @c1.many_to_one :node, :class => @c2
1511
+
1512
+ n = @c2.new(:id => 1234)
1513
+ att = @c1.new(:id => 345)
1514
+ att.associations[:node] = n
1515
+ att.node.should == n
1516
+ n.remove_attribute(att)
1517
+ att.node.should == nil
1518
+ end
1519
+
1520
+ it "should not create the add_, remove_, or remove_all_ methods if :read_only option is used" do
1521
+ @c2.one_to_many :attributes, :class => @c1, :read_only=>true
1522
+ im = @c2.instance_methods.collect{|x| x.to_s}
1523
+ im.should(include('attributes'))
1524
+ im.should(include('attributes_dataset'))
1525
+ im.should_not(include('add_attribute'))
1526
+ im.should_not(include('remove_attribute'))
1527
+ im.should_not(include('remove_all_attributes'))
1528
+ end
1529
+
1530
+ it "should not add associations methods directly to class" do
1531
+ @c2.one_to_many :attributes, :class => @c1
1532
+ im = @c2.instance_methods.collect{|x| x.to_s}
1533
+ im.should(include('attributes'))
1534
+ im.should(include('attributes_dataset'))
1535
+ im.should(include('add_attribute'))
1536
+ im.should(include('remove_attribute'))
1537
+ im.should(include('remove_all_attributes'))
1538
+ im2 = @c2.instance_methods(false).collect{|x| x.to_s}
1539
+ im2.should_not(include('attributes'))
1540
+ im2.should_not(include('attributes_dataset'))
1541
+ im2.should_not(include('add_attribute'))
1542
+ im2.should_not(include('remove_attribute'))
1543
+ im2.should_not(include('remove_all_attributes'))
1544
+ end
1545
+
1546
+ it "should populate the reciprocal many_to_one instance variable when loading the one_to_many association" do
1547
+ @c2.one_to_many :attributes, :class => @c1, :key => :node_id
1548
+ @c1.many_to_one :node, :class => @c2, :key => :node_id
1549
+
1550
+ n = @c2.new(:id => 1234)
1551
+ atts = n.attributes
1552
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
1553
+ atts.should be_a_kind_of(Array)
1554
+ atts.size.should == 1
1555
+ atts.first.should be_a_kind_of(@c1)
1556
+ atts.first.values.should == {}
1557
+ atts.first.node.should == n
1558
+
1559
+ MODEL_DB.sqls.length.should == 1
1560
+ end
1561
+
1562
+ it "should use an explicit reciprocal instance variable if given" do
1563
+ @c2.one_to_many :attributes, :class => @c1, :key => :node_id, :reciprocal=>:wxyz
1564
+
1565
+ n = @c2.new(:id => 1234)
1566
+ atts = n.attributes
1567
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
1568
+ atts.should be_a_kind_of(Array)
1569
+ atts.size.should == 1
1570
+ atts.first.should be_a_kind_of(@c1)
1571
+ atts.first.values.should == {}
1572
+ atts.first.associations[:wxyz].should == n
1573
+
1574
+ MODEL_DB.sqls.length.should == 1
1575
+ end
1576
+
1577
+ it "should have an remove_all_ method that removes all associations" do
1578
+ @c2.one_to_many :attributes, :class => @c1
1579
+ @c2.new(:id => 1234).remove_all_attributes
1580
+ MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE (node_id = 1234)'
1581
+ end
1582
+
1583
+ it "should have remove_all method respect association filters" do
1584
+ @c2.one_to_many :attributes, :class => @c1, :conditions=>{:a=>1} do |ds|
1585
+ ds.filter(:b=>2)
1586
+ end
1587
+ @c2.new(:id => 1234).remove_all_attributes
1588
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (a = 1) AND (b = 2))']
1589
+ end
1590
+
1591
+ it "should have the remove_all_ method respect the :primary_key option" do
1592
+ @c2.one_to_many :attributes, :class => @c1, :primary_key=>:xxx
1593
+ @c2.new(:id => 1234, :xxx=>5).remove_all_attributes
1594
+ MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE (node_id = 5)'
1595
+ end
1596
+
1597
+ it "should have the remove_all_ method respect composite keys" do
1598
+ @c2.one_to_many :attributes, :class => @c1, :key=>[:node_id, :y], :primary_key=>[:id, :x]
1599
+ @c2.new(:id => 1234, :x=>5).remove_all_attributes
1600
+ MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id|y) = NULL, (node_id|y) = NULL WHERE \(\(node_id = 1234\) AND \(y = 5\)\)/
1601
+ end
1602
+
1603
+ it "remove_all should set the cached instance variable to []" do
1604
+ @c2.one_to_many :attributes, :class => @c1
1605
+ node = @c2.new(:id => 1234)
1606
+ node.remove_all_attributes
1607
+ node.associations[:attributes].should == []
1608
+ end
1609
+
1610
+ it "remove_all should return the array of previously associated items if the cached instance variable exists" do
1611
+ @c2.one_to_many :attributes, :class => @c1
1612
+ attrib = @c1.new(:id=>3)
1613
+ node = @c2.new(:id => 1234)
1614
+ d = @c1.dataset
1615
+ def d.fetch_rows(s); end
1616
+ node.attributes.should == []
1617
+ def attrib.save(*); self end
1618
+ node.add_attribute(attrib)
1619
+ node.associations[:attributes].should == [attrib]
1620
+ node.remove_all_attributes.should == [attrib]
1621
+ end
1622
+
1623
+ it "remove_all should return nil if the cached instance variable does not exist" do
1624
+ @c2.one_to_many :attributes, :class => @c1
1625
+ @c2.new(:id => 1234).remove_all_attributes.should == nil
1626
+ end
1627
+
1628
+ it "remove_all should remove the current item from all reciprocal instance varaibles if it cached instance variable exists" do
1629
+ @c2.one_to_many :attributes, :class => @c1
1630
+ @c1.many_to_one :node, :class => @c2
1631
+ d = @c1.dataset
1632
+ def d.fetch_rows(s); end
1633
+ d = @c2.dataset
1634
+ def d.fetch_rows(s); end
1635
+ attrib = @c1.new(:id=>3)
1636
+ node = @c2.new(:id => 1234)
1637
+ node.attributes.should == []
1638
+ attrib.node.should == nil
1639
+ def attrib.save(*); self end
1640
+ node.add_attribute(attrib)
1641
+ attrib.associations[:node].should == node
1642
+ node.remove_all_attributes
1643
+ attrib.associations.fetch(:node, 2).should == nil
1644
+ end
1645
+
1646
+ it "should call an _add_ method internally to add attributes" do
1647
+ @c2.one_to_many :attributes, :class => @c1
1648
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_add_attribute"))
1649
+ p = @c2.load(:id=>10)
1650
+ c = @c1.load(:id=>123)
1651
+ def p._add_attribute(x)
1652
+ @x = x
1653
+ end
1654
+ c.should_not_receive(:node_id=)
1655
+ p.add_attribute(c)
1656
+ p.instance_variable_get(:@x).should == c
1657
+ end
1658
+
1659
+ it "should allow additional arguments given to the add_ method and pass them onwards to the _add_ method" do
1660
+ @c2.one_to_many :attributes, :class => @c1
1661
+ p = @c2.load(:id=>10)
1662
+ c = @c1.load(:id=>123)
1663
+ def p._add_attribute(x,*y)
1664
+ @x = x
1665
+ @y = y
1666
+ end
1667
+ c.should_not_receive(:node_id=)
1668
+ p.add_attribute(c,:foo,:bar=>:baz)
1669
+ p.instance_variable_get(:@x).should == c
1670
+ p.instance_variable_get(:@y).should == [:foo,{:bar=>:baz}]
1671
+ end
1672
+
1673
+ it "should call a _remove_ method internally to remove attributes" do
1674
+ @c2.one_to_many :attributes, :class => @c1
1675
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_remove_attribute"))
1676
+ p = @c2.load(:id=>10)
1677
+ c = @c1.load(:id=>123)
1678
+ def p._remove_attribute(x)
1679
+ @x = x
1680
+ end
1681
+ c.should_not_receive(:node_id=)
1682
+ p.remove_attribute(c)
1683
+ p.instance_variable_get(:@x).should == c
1684
+ end
1685
+
1686
+ it "should allow additional arguments given to the remove_ method and pass them onwards to the _remove_ method" do
1687
+ @c2.one_to_many :attributes, :class => @c1
1688
+ p = @c2.load(:id=>10)
1689
+ c = @c1.load(:id=>123)
1690
+ def p._remove_attribute(x,*y)
1691
+ @x = x
1692
+ @y = y
1693
+ end
1694
+ c.should_not_receive(:node_id=)
1695
+ p.remove_attribute(c,:foo,:bar=>:baz)
1696
+ p.instance_variable_get(:@x).should == c
1697
+ p.instance_variable_get(:@y).should == [:foo,{:bar=>:baz}]
1698
+ end
1699
+
1700
+ it "should allow additional arguments given to the remove_all_ method and pass them onwards to the _remove_all_ method" do
1701
+ @c2.one_to_many :attributes, :class => @c1
1702
+ p = @c2.load(:id=>10)
1703
+ c = @c1.load(:id=>123)
1704
+ def p._remove_all_attributes(*y)
1705
+ @y = y
1706
+ end
1707
+ c.should_not_receive(:node_id=)
1708
+ p.remove_all_attributes(:foo,:bar=>:baz)
1709
+ p.instance_variable_get(:@y).should == [:foo,{:bar=>:baz}]
1710
+ end
1711
+
1712
+ it "should call a _remove_all_ method internally to remove attributes" do
1713
+ @c2.one_to_many :attributes, :class => @c1
1714
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_remove_all_attributes"))
1715
+ p = @c2.load(:id=>10)
1716
+ def p._remove_all_attributes
1717
+ @x = :foo
1718
+ end
1719
+ p.remove_all_attributes
1720
+ p.instance_variable_get(:@x).should == :foo
1721
+ end
1722
+
1723
+ it "should support (before|after)_(add|remove) callbacks" do
1724
+ h = []
1725
+ @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]
1726
+ @c2.class_eval do
1727
+ @@blah = h
1728
+ def _add_attribute(v)
1729
+ @@blah << 4
1730
+ end
1731
+ def _remove_attribute(v)
1732
+ @@blah << 5
1733
+ end
1734
+ def blah(x)
1735
+ @@blah << x.pk
1736
+ end
1737
+ def blahr(x)
1738
+ @@blah << 6
1739
+ end
1740
+ end
1741
+ p = @c2.load(:id=>10)
1742
+ c = @c1.load(:id=>123)
1743
+ h.should == []
1744
+ p.add_attribute(c)
1745
+ h.should == [10, -123, 123, 4, 3]
1746
+ p.remove_attribute(c)
1747
+ h.should == [10, -123, 123, 4, 3, 123, 5, 6]
1748
+ end
1749
+
1750
+ it "should support after_load association callback" do
1751
+ h = []
1752
+ @c2.one_to_many :attributes, :class => @c1, :after_load=>[proc{|x,y| h << [x.pk, y.collect{|z|z.pk}]}, :al]
1753
+ @c2.class_eval do
1754
+ @@blah = h
1755
+ def al(v)
1756
+ v.each{|x| @@blah << x.pk}
1757
+ end
1758
+ end
1759
+ @c1.class_eval do
1760
+ def @dataset.fetch_rows(sql)
1761
+ yield({:id=>20})
1762
+ yield({:id=>30})
1763
+ end
1764
+ end
1765
+ p = @c2.load(:id=>10, :parent_id=>20)
1766
+ attributes = p.attributes
1767
+ h.should == [[10, [20, 30]], 20, 30]
1768
+ attributes.collect{|a| a.pk}.should == [20, 30]
1769
+ end
1770
+
1771
+ it "should raise error and not call internal add or remove method if before callback returns false if raise_on_save_failure is true" do
1772
+ p = @c2.load(:id=>10)
1773
+ c = @c1.load(:id=>123)
1774
+ @c2.one_to_many :attributes, :class => @c1, :before_add=>:ba, :before_remove=>:br
1775
+ p.should_receive(:ba).once.with(c).and_return(false)
1776
+ p.should_not_receive(:_add_attribute)
1777
+ p.should_not_receive(:_remove_attribute)
1778
+ p.associations[:attributes] = []
1779
+ proc{p.add_attribute(c)}.should raise_error(Sequel::Error)
1780
+ p.attributes.should == []
1781
+ p.associations[:attributes] = [c]
1782
+ p.should_receive(:br).once.with(c).and_return(false)
1783
+ proc{p.remove_attribute(c)}.should raise_error(Sequel::Error)
1784
+ p.attributes.should == [c]
1785
+ end
1786
+
1787
+ it "should return nil and not call internal add or remove method if before callback returns false if raise_on_save_failure is false" do
1788
+ p = @c2.load(:id=>10)
1789
+ c = @c1.load(:id=>123)
1790
+ p.raise_on_save_failure = false
1791
+ @c2.one_to_many :attributes, :class => @c1, :before_add=>:ba, :before_remove=>:br
1792
+ p.should_receive(:ba).once.with(c).and_return(false)
1793
+ p.should_not_receive(:_add_attribute)
1794
+ p.should_not_receive(:_remove_attribute)
1795
+ p.associations[:attributes] = []
1796
+ p.add_attribute(c).should == nil
1797
+ p.attributes.should == []
1798
+ p.associations[:attributes] = [c]
1799
+ p.should_receive(:br).once.with(c).and_return(false)
1800
+ p.remove_attribute(c).should == nil
1801
+ p.attributes.should == [c]
1802
+ end
1803
+
1804
+ it "should raise an error if trying to use the :one_to_one option" do
1805
+ proc{@c2.one_to_many :attribute, :class => @c1, :one_to_one=>true}.should raise_error(Sequel::Error)
1806
+ proc{@c2.associate :one_to_many, :attribute, :class => @c1, :one_to_one=>true}.should raise_error(Sequel::Error)
1807
+ end
1808
+ end
1809
+
1810
+ describe Sequel::Model, "many_to_many" do
1811
+
1812
+ before do
1813
+ MODEL_DB.reset
1814
+
1815
+ @c1 = Class.new(Sequel::Model(:attributes)) do
1816
+ unrestrict_primary_key
1817
+ attr_accessor :yyy
1818
+ def self.name; 'Attribute'; end
1819
+ def self.to_s; 'Attribute'; end
1820
+ columns :id, :y
1821
+ def _refresh(ds)
1822
+ self.id = 1
1823
+ self
1824
+ end
1825
+ end
1826
+
1827
+ @c2 = Class.new(Sequel::Model(:nodes)) do
1828
+ unrestrict_primary_key
1829
+ attr_accessor :xxx
1830
+
1831
+ def self.name; 'Node'; end
1832
+ def self.to_s; 'Node'; end
1833
+ columns :id, :x
1834
+ end
1835
+ @dataset = @c2.dataset
1836
+
1837
+ [@c1, @c2].each do |c|
1838
+ c.dataset.extend(Module.new {
1839
+ def fetch_rows(sql)
1840
+ @db << sql
1841
+ yield Hash.new
1842
+ end
1843
+ })
1844
+ end
1845
+ end
1846
+
1847
+ it "should use implicit key values and join table if omitted" do
1848
+ @c2.many_to_many :attributes, :class => @c1
1849
+
1850
+ n = @c2.new(:id => 1234)
1851
+ a = n.attributes_dataset
1852
+ a.should be_a_kind_of(Sequel::Dataset)
1853
+ a.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))'
1854
+ end
1855
+
1856
+ it "should use implicit class if omitted" do
1857
+ class ::Tag < Sequel::Model
1858
+ end
1859
+
1860
+ @c2.many_to_many :tags
1861
+
1862
+ n = @c2.new(:id => 1234)
1863
+ a = n.tags_dataset
1864
+ a.should be_a_kind_of(Sequel::Dataset)
1865
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN nodes_tags ON ((nodes_tags.tag_id = tags.id) AND (nodes_tags.node_id = 1234))'
1866
+ end
1867
+
1868
+ it "should use class inside module if given as a string" do
1869
+ module ::Historical
1870
+ class Tag < Sequel::Model
1871
+ end
1872
+ end
1873
+
1874
+ @c2.many_to_many :tags, :class=>'::Historical::Tag'
1875
+
1876
+ n = @c2.new(:id => 1234)
1877
+ a = n.tags_dataset
1878
+ a.should be_a_kind_of(Sequel::Dataset)
1879
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN nodes_tags ON ((nodes_tags.tag_id = tags.id) AND (nodes_tags.node_id = 1234))'
1880
+ end
1881
+
1882
+ it "should use explicit key values and join table if given" do
1883
+ @c2.many_to_many :attributes, :class => @c1, :left_key => :nodeid, :right_key => :attributeid, :join_table => :attribute2node
1884
+
1885
+ n = @c2.new(:id => 1234)
1886
+ a = n.attributes_dataset
1887
+ a.should be_a_kind_of(Sequel::Dataset)
1888
+ a.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attribute2node ON ((attribute2node.attributeid = attributes.id) AND (attribute2node.nodeid = 1234))'
1889
+ end
1890
+
1891
+ it "should support a conditions option" do
1892
+ @c2.many_to_many :attributes, :class => @c1, :conditions => {:a=>32}
1893
+ n = @c2.new(:id => 1234)
1894
+ a = n.attributes_dataset
1895
+ a.should be_a_kind_of(Sequel::Dataset)
1896
+ 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)'
1897
+ @c2.many_to_many :attributes, :class => @c1, :conditions => ['a = ?', 32]
1898
+ n = @c2.new(:id => 1234)
1899
+ a = n.attributes_dataset
1900
+ a.should be_a_kind_of(Sequel::Dataset)
1901
+ 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)'
1902
+ n.attributes.should == [@c1.load({})]
1903
+ end
1904
+
1905
+ it "should support an order option" do
1906
+ @c2.many_to_many :attributes, :class => @c1, :order => :blah
1907
+
1908
+ n = @c2.new(:id => 1234)
1909
+ a = n.attributes_dataset
1910
+ a.should be_a_kind_of(Sequel::Dataset)
1911
+ 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'
1912
+ end
1913
+
1914
+ it "should support an array for the order option" do
1915
+ @c2.many_to_many :attributes, :class => @c1, :order => [:blah1, :blah2]
1916
+
1917
+ n = @c2.new(:id => 1234)
1918
+ a = n.attributes_dataset
1919
+ a.should be_a_kind_of(Sequel::Dataset)
1920
+ 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'
1921
+ end
1922
+
1923
+ it "should support :left_primary_key and :right_primary_key options" do
1924
+ @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy
1925
+ @c2.new(:id => 1234, :xxx=>5).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.yyy) AND (attributes_nodes.node_id = 5))'
1926
+ end
1927
+
1928
+ it "should support composite keys" do
1929
+ @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]
1930
+ @c2.load(:id => 1234, :x=>5).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.r1 = attributes.id) AND (attributes_nodes.r2 = attributes.y) AND (attributes_nodes.l1 = 1234) AND (attributes_nodes.l2 = 5))'
1931
+ end
1932
+
1933
+ it "should not issue query if not all keys have values" do
1934
+ @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]
1935
+ @c2.load(:id => 1234, :x=>nil).attributes.should == []
1936
+ MODEL_DB.sqls.should == []
1937
+ end
1938
+
1939
+ it "should raise an Error unless same number of composite keys used" do
1940
+ proc{@c2.many_to_many :attributes, :class => @c1, :left_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
1941
+ proc{@c2.many_to_many :attributes, :class => @c1, :left_primary_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
1942
+ proc{@c2.many_to_many :attributes, :class => @c1, :left_key=>[:node_id, :id], :left_primary_key=>:id}.should raise_error(Sequel::Error)
1943
+ proc{@c2.many_to_many :attributes, :class => @c1, :left_key=>:id, :left_primary_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
1944
+ proc{@c2.many_to_many :attributes, :class => @c1, :left_key=>[:node_id, :id, :x], :left_primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
1945
+
1946
+ proc{@c2.many_to_many :attributes, :class => @c1, :right_primary_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
1947
+ proc{@c2.many_to_many :attributes, :class => @c1, :right_key=>[:node_id, :id], :right_primary_key=>:id}.should raise_error(Sequel::Error)
1948
+ proc{@c2.many_to_many :attributes, :class => @c1, :right_key=>:id, :left_primary_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
1949
+ proc{@c2.many_to_many :attributes, :class => @c1, :right_key=>[:node_id, :id, :x], :right_primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
1950
+ end
1951
+
1952
+ it "should support a select option" do
1953
+ @c2.many_to_many :attributes, :class => @c1, :select => :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 blah FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))'
1959
+ end
1960
+
1961
+ it "should support an array for the select option" do
1962
+ @c2.many_to_many :attributes, :class => @c1, :select => [:attributes.*, :attribute_nodes__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.*, attribute_nodes.blah2 FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))'
1968
+ end
1969
+
1970
+ it "should accept a block" do
1971
+ @c2.many_to_many :attributes, :class => @c1 do |ds|
1972
+ ds.filter(:xxx => @xxx)
1973
+ end
1974
+
1975
+ n = @c2.new(:id => 1234)
1976
+ n.xxx = 555
1977
+ a = n.attributes
1978
+ a.should be_a_kind_of(Array)
1979
+ a.size.should == 1
1980
+ a.first.should be_a_kind_of(@c1)
1981
+ 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)'
1982
+ end
1983
+
1984
+ it "should allow the :order option while accepting a block" do
1985
+ @c2.many_to_many :attributes, :class => @c1, :order=>[:blah1, :blah2] do |ds|
1986
+ ds.filter(:xxx => @xxx)
1987
+ end
1988
+
1989
+ n = @c2.new(:id => 1234)
1990
+ n.xxx = 555
1991
+ a = n.attributes
1992
+ a.should be_a_kind_of(Array)
1993
+ a.size.should == 1
1994
+ a.first.should be_a_kind_of(@c1)
1995
+ 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'
1996
+ end
1997
+
1998
+ it "should have the block argument affect the _dataset method" do
1999
+ @c2.many_to_many :attributes, :class => @c1 do |ds|
2000
+ ds.filter(:xxx => 456)
2001
+ end
2002
+ @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)'
2003
+ end
2004
+
2005
+ it "should support a :dataset option that is used instead of the default" do
2006
+ c1 = @c1
2007
+ @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|
2008
+ ds.filter(:xxx => @xxx)
2009
+ end
2010
+
2011
+ n = @c2.new(:id => 1234)
2012
+ n.xxx = 555
2013
+ n.attributes_dataset.sql.should == 'SELECT * FROM attributes NATURAL JOIN an WHERE ((an.nodeid = 1234) AND (xxx = 555)) ORDER BY a LIMIT 10'
2014
+ a = n.attributes
2015
+ a.should be_a_kind_of(Array)
2016
+ a.size.should == 1
2017
+ a.first.should be_a_kind_of(@c1)
2018
+ MODEL_DB.sqls.first.should == 'SELECT * FROM attributes NATURAL JOIN an WHERE ((an.nodeid = 1234) AND (xxx = 555)) ORDER BY a LIMIT 10'
2019
+ end
2020
+
2021
+ it "should support a :limit option" do
2022
+ @c2.many_to_many :attributes, :class => @c1 , :limit=>10
2023
+ @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)) LIMIT 10'
2024
+ @c2.many_to_many :attributes, :class => @c1 , :limit=>[10, 10]
2025
+ @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)) LIMIT 10 OFFSET 10'
2026
+ end
2027
+
2028
+ it "should have the :eager option affect the _dataset method" do
2029
+ @c2.many_to_many :attributes, :class => @c2 , :eager=>:attributes
2030
+ @c2.new(:id => 1234).attributes_dataset.opts[:eager].should == {:attributes=>nil}
2031
+ end
2032
+
2033
+ it "should define an add_ method that works on existing records" do
2034
+ @c2.many_to_many :attributes, :class => @c1
2035
+
2036
+ n = @c2.load(:id => 1234)
2037
+ a = @c1.load(:id => 2345)
2038
+ a.should == n.add_attribute(a)
2039
+ ['INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 2345)',
2040
+ 'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (2345, 1234)'
2041
+ ].should(include(MODEL_DB.sqls.first))
2042
+ end
2043
+
2044
+ it "should allow passing a hash to the add_ method which creates a new record" do
2045
+ @c2.many_to_many :attributes, :class => @c1
2046
+
2047
+ n = @c2.load(:id => 1234)
2048
+ @c1.load(:id => 1).should == n.add_attribute(:id => 1)
2049
+ MODEL_DB.sqls.first.should == 'INSERT INTO attributes (id) VALUES (1)'
2050
+ ['INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 1)',
2051
+ 'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (1, 1234)'
2052
+ ].should(include(MODEL_DB.sqls.last))
2053
+ end
2054
+
2055
+ it "should define a remove_ method that works on existing records" do
2056
+ @c2.many_to_many :attributes, :class => @c1
2057
+
2058
+ n = @c2.new(:id => 1234)
2059
+ a = @c1.new(:id => 2345)
2060
+ a.should == n.remove_attribute(a)
2061
+ MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 2345))'
2062
+ end
2063
+
2064
+ it "should raise an error in the add_ method if the passed associated object is not of the correct type" do
2065
+ @c2.many_to_many :attributes, :class => @c1
2066
+ proc{@c2.new(:id => 1234).add_attribute(@c2.new)}.should raise_error(Sequel::Error)
2067
+ end
2068
+
2069
+ it "should accept a primary key for the remove_ method and remove an existing record" do
2070
+ @c2.many_to_many :attributes, :class => @c1
2071
+ n = @c2.new(:id => 1234)
2072
+ ds = @c1.dataset
2073
+ def ds.fetch_rows(sql)
2074
+ db << sql
2075
+ yield({:id=>234})
2076
+ end
2077
+ MODEL_DB.reset
2078
+ @c1.load(:id => 234).should == n.remove_attribute(234)
2079
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (id = 234) LIMIT 1',
2080
+ 'DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 234))']
2081
+ end
2082
+
2083
+ it "should raise an error in the remove_ method if the passed associated object is not of the correct type" do
2084
+ @c2.many_to_many :attributes, :class => @c1
2085
+ proc{@c2.new(:id => 1234).remove_attribute(@c2.new)}.should raise_error(Sequel::Error)
2086
+ end
2087
+
2088
+ it "should have the add_ method respect the :left_primary_key and :right_primary_key options" do
2089
+ @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy
2090
+
2091
+ n = @c2.load(:id => 1234).set(:xxx=>5)
2092
+ a = @c1.load(:id => 2345).set(:yyy=>8)
2093
+ a.should == n.add_attribute(a)
2094
+ ['INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (5, 8)',
2095
+ 'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (8, 5)'
2096
+ ].should(include(MODEL_DB.sqls.first))
2097
+ end
2098
+
2099
+ it "should have add_ method not add the same object to the cached association array if the object is already in the array" do
2100
+ @c2.many_to_many :attributes, :class => @c1
2101
+
2102
+ n = @c2.load(:id => 1234).set(:xxx=>5)
2103
+ a = @c1.load(:id => 2345).set(:yyy=>8)
2104
+ n.associations[:attributes] = []
2105
+ a.should == n.add_attribute(a)
2106
+ a.should == n.add_attribute(a)
2107
+ n.attributes.should == [a]
2108
+ end
2109
+
2110
+ it "should have the add_ method respect composite keys" do
2111
+ @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]
2112
+ n = @c2.load(:id => 1234, :x=>5)
2113
+ a = @c1.load(:id => 2345, :y=>8)
2114
+ a.should == n.add_attribute(a)
2115
+ m = /INSERT INTO attributes_nodes \((\w+), (\w+), (\w+), (\w+)\) VALUES \((\d+), (\d+), (\d+), (\d+)\)/.match(MODEL_DB.sqls.first)
2116
+ m.should_not == nil
2117
+ map = {'l1'=>1234, 'l2'=>5, 'r1'=>2345, 'r2'=>8}
2118
+ %w[l1 l2 r1 r2].each do |x|
2119
+ v = false
2120
+ 4.times do |i| i += 1
2121
+ if m[i] == x
2122
+ m[i+4].should == map[x].to_s
2123
+ v = true
2124
+ end
2125
+ end
2126
+ v.should == true
2127
+ end
2128
+ end
2129
+
2130
+ it "should have the remove_ method respect the :left_primary_key and :right_primary_key options" do
2131
+ @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy
2132
+
2133
+ n = @c2.new(:id => 1234, :xxx=>5)
2134
+ a = @c1.new(:id => 2345, :yyy=>8)
2135
+ a.should == n.remove_attribute(a)
2136
+ MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 5) AND (attribute_id = 8))'
2137
+ end
2138
+
2139
+ it "should have the remove_ method respect composite keys" do
2140
+ @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]
2141
+ n = @c2.load(:id => 1234, :x=>5)
2142
+ a = @c1.load(:id => 2345, :y=>8)
2143
+ a.should == n.remove_attribute(a)
2144
+ MODEL_DB.sqls.should == ["DELETE FROM attributes_nodes WHERE ((l1 = 1234) AND (l2 = 5) AND (r1 = 2345) AND (r2 = 8))"]
2145
+ end
2146
+
2147
+ it "should accept a array of composite primary key values for the remove_ method and remove an existing record" do
2148
+ @c1.set_primary_key [:id, :y]
2149
+ @c2.many_to_many :attributes, :class => @c1
2150
+ n = @c2.new(:id => 1234)
2151
+ ds = @c1.dataset
2152
+ def ds.fetch_rows(sql)
2153
+ db << sql
2154
+ yield({:id=>234, :y=>8})
2155
+ end
2156
+ MODEL_DB.reset
2157
+ @c1.load(:id => 234, :y=>8).should == n.remove_attribute([234, 8])
2158
+ MODEL_DB.sqls.length.should == 2
2159
+ MODEL_DB.sqls.first.should =~ /SELECT \* FROM attributes WHERE \(\((id|y) = (234|8)\) AND \((id|y) = (234|8)\)\) LIMIT 1/
2160
+ MODEL_DB.sqls.last.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 234))'
2161
+ end
2162
+
2163
+ it "should raise an error if the model object doesn't have a valid primary key" do
2164
+ @c2.many_to_many :attributes, :class => @c1
2165
+ a = @c2.new
2166
+ n = @c1.load(:id=>123)
2167
+ proc{a.attributes_dataset}.should raise_error(Sequel::Error)
2168
+ proc{a.add_attribute(n)}.should raise_error(Sequel::Error)
2169
+ proc{a.remove_attribute(n)}.should raise_error(Sequel::Error)
2170
+ proc{a.remove_all_attributes}.should raise_error(Sequel::Error)
2171
+ end
2172
+
2173
+ it "should save the associated object first in add_ if passed a new model object" do
2174
+ @c2.many_to_many :attributes, :class => @c1
2175
+ n = @c1.new
2176
+ a = @c2.load(:id=>123)
2177
+ n.new?.should == true
2178
+ a.add_attribute(n)
2179
+ n.new?.should == false
2180
+ end
2181
+
2182
+ it "should raise a ValidationFailed in add_ if the associated object is new and invalid" do
2183
+ @c2.many_to_many :attributes, :class => @c1
2184
+ n = @c1.new
2185
+ a = @c2.load(:id=>123)
2186
+ def n.valid?; false; end
2187
+ proc{a.add_attribute(n)}.should raise_error(Sequel::ValidationFailed)
2188
+ end
2189
+
2190
+ it "should raise an Error in add_ if the associated object is new and invalid and raise_on_save_failure is false" do
2191
+ @c2.many_to_many :attributes, :class => @c1
2192
+ n = @c1.new
2193
+ n.raise_on_save_failure = false
2194
+ a = @c2.load(:id=>123)
2195
+ def n.valid?; false; end
2196
+ proc{a.add_attribute(n)}.should raise_error(Sequel::Error)
2197
+ end
2198
+
2199
+ it "should not attempt to validate the associated object in add_ if the :validate=>false option is used" do
2200
+ @c2.many_to_many :attributes, :class => @c1, :validate=>false
2201
+ n = @c1.new
2202
+ a = @c2.load(:id=>123)
2203
+ def n.valid?; false; end
2204
+ a.add_attribute(n)
2205
+ n.new?.should == false
2206
+ end
2207
+
2208
+ it "should raise an error if trying to remove a model object that doesn't have a valid primary key" do
2209
+ @c2.many_to_many :attributes, :class => @c1
2210
+ n = @c1.new
2211
+ a = @c2.load(:id=>123)
2212
+ proc{a.remove_attribute(n)}.should raise_error(Sequel::Error)
2213
+ end
2214
+
2215
+ it "should provide an array with all members of the association" do
2216
+ @c2.many_to_many :attributes, :class => @c1
2217
+
2218
+ n = @c2.new(:id => 1234)
2219
+ atts = n.attributes
2220
+ atts.should be_a_kind_of(Array)
2221
+ atts.size.should == 1
2222
+ atts.first.should be_a_kind_of(@c1)
2223
+
2224
+ 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))'
2225
+ end
2226
+
2227
+ it "should set cached instance variable when accessed" do
2228
+ @c2.many_to_many :attributes, :class => @c1
2229
+
2230
+ n = @c2.new(:id => 1234)
2231
+ MODEL_DB.reset
2232
+ n.associations.include?(:attributes).should == false
2233
+ atts = n.attributes
2234
+ atts.should == n.associations[:attributes]
2235
+ MODEL_DB.sqls.length.should == 1
2236
+ end
2237
+
2238
+ it "should use cached instance variable if available" do
2239
+ @c2.many_to_many :attributes, :class => @c1
2240
+
2241
+ n = @c2.new(:id => 1234)
2242
+ MODEL_DB.reset
2243
+ n.associations[:attributes] = 42
2244
+ n.attributes.should == 42
2245
+ MODEL_DB.sqls.should == []
2246
+ end
2247
+
2248
+ it "should not use cached instance variable if asked to reload" do
2249
+ @c2.many_to_many :attributes, :class => @c1
2250
+
2251
+ n = @c2.new(:id => 1234)
2252
+ MODEL_DB.reset
2253
+ n.associations[:attributes] = 42
2254
+ n.attributes(true).should_not == 42
2255
+ MODEL_DB.sqls.length.should == 1
2256
+ end
2257
+
2258
+ it "should add item to cached instance variable if it exists when calling add_" do
2259
+ @c2.many_to_many :attributes, :class => @c1
2260
+
2261
+ n = @c2.new(:id => 1234)
2262
+ att = @c1.new(:id => 345)
2263
+ MODEL_DB.reset
2264
+ a = []
2265
+ n.associations[:attributes] = a
2266
+ n.add_attribute(att)
2267
+ a.should == [att]
2268
+ end
2269
+
2270
+ it "should add item to reciprocal cached instance variable if it exists when calling add_" do
2271
+ @c2.many_to_many :attributes, :class => @c1
2272
+ @c1.many_to_many :nodes, :class => @c2
2273
+
2274
+ n = @c2.new(:id => 1234)
2275
+ att = @c1.new(:id => 345)
2276
+ att.associations[:nodes] = []
2277
+ n.add_attribute(att)
2278
+ att.nodes.should == [n]
2279
+ end
2280
+
2281
+ it "should remove item from cached instance variable if it exists when calling remove_" do
2282
+ @c2.many_to_many :attributes, :class => @c1
2283
+
2284
+ n = @c2.new(:id => 1234)
2285
+ att = @c1.new(:id => 345)
2286
+ MODEL_DB.reset
2287
+ a = [att]
2288
+ n.associations[:attributes] = a
2289
+ n.remove_attribute(att)
2290
+ a.should == []
2291
+ end
2292
+
2293
+ it "should remove item from reciprocal cached instance variable if it exists when calling remove_" do
2294
+ @c2.many_to_many :attributes, :class => @c1
2295
+ @c1.many_to_many :nodes, :class => @c2
2296
+
2297
+ n = @c2.new(:id => 1234)
2298
+ att = @c1.new(:id => 345)
2299
+ att.associations[:nodes] = [n]
2300
+ n.remove_attribute(att)
2301
+ att.nodes.should == []
2302
+ end
2303
+
2304
+ it "should not create the add_, remove_, or remove_all_ methods if :read_only option is used" do
2305
+ @c2.many_to_many :attributes, :class => @c1, :read_only=>true
2306
+ im = @c2.instance_methods.collect{|x| x.to_s}
2307
+ im.should(include('attributes'))
2308
+ im.should(include('attributes_dataset'))
2309
+ im.should_not(include('add_attribute'))
2310
+ im.should_not(include('remove_attribute'))
2311
+ im.should_not(include('remove_all_attributes'))
2312
+ end
2313
+
2314
+ it "should not add associations methods directly to class" do
2315
+ @c2.many_to_many :attributes, :class => @c1
2316
+ im = @c2.instance_methods.collect{|x| x.to_s}
2317
+ im.should(include('attributes'))
2318
+ im.should(include('attributes_dataset'))
2319
+ im.should(include('add_attribute'))
2320
+ im.should(include('remove_attribute'))
2321
+ im.should(include('remove_all_attributes'))
2322
+ im2 = @c2.instance_methods(false).collect{|x| x.to_s}
2323
+ im2.should_not(include('attributes'))
2324
+ im2.should_not(include('attributes_dataset'))
2325
+ im2.should_not(include('add_attribute'))
2326
+ im2.should_not(include('remove_attribute'))
2327
+ im2.should_not(include('remove_all_attributes'))
2328
+ end
2329
+
2330
+ it "should have an remove_all_ method that removes all associations" do
2331
+ @c2.many_to_many :attributes, :class => @c1
2332
+ @c2.new(:id => 1234).remove_all_attributes
2333
+ MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE (node_id = 1234)'
2334
+ end
2335
+
2336
+ it "should have remove_all method respect association filters" do
2337
+ @c2.many_to_many :attributes, :class => @c1, :conditions=>{:a=>1} do |ds|
2338
+ ds.filter(:b=>2)
2339
+ end
2340
+ @c2.new(:id => 1234).remove_all_attributes
2341
+ MODEL_DB.sqls.should == ['DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (a = 1) AND (b = 2))']
2342
+ end
2343
+
2344
+ it "should have the remove_all_ method respect the :left_primary_key option" do
2345
+ @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx
2346
+ @c2.new(:id => 1234, :xxx=>5).remove_all_attributes
2347
+ MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE (node_id = 5)'
2348
+ end
2349
+
2350
+ it "should have the remove_all_ method respect composite keys" do
2351
+ @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>[:id, :x], :left_key=>[:l1, :l2]
2352
+ @c2.load(:id => 1234, :x=>5).remove_all_attributes
2353
+ MODEL_DB.sqls.should == ['DELETE FROM attributes_nodes WHERE ((l1 = 1234) AND (l2 = 5))']
2354
+ end
2355
+
2356
+ it "remove_all should set the cached instance variable to []" do
2357
+ @c2.many_to_many :attributes, :class => @c1
2358
+ node = @c2.new(:id => 1234)
2359
+ node.remove_all_attributes
2360
+ node.associations[:attributes].should == []
2361
+ end
2362
+
2363
+ it "remove_all should return the array of previously associated items if the cached instance variable exists" do
2364
+ @c2.many_to_many :attributes, :class => @c1
2365
+ attrib = @c1.new(:id=>3)
2366
+ node = @c2.new(:id => 1234)
2367
+ d = @c1.dataset
2368
+ def d.fetch_rows(s); end
2369
+ node.attributes.should == []
2370
+ node.add_attribute(attrib)
2371
+ node.associations[:attributes].should == [attrib]
2372
+ node.remove_all_attributes.should == [attrib]
2373
+ end
2374
+
2375
+ it "remove_all should return nil if the cached instance variable does not exist" do
2376
+ @c2.many_to_many :attributes, :class => @c1
2377
+ @c2.new(:id => 1234).remove_all_attributes.should == nil
2378
+ end
2379
+
2380
+ it "remove_all should remove the current item from all reciprocal instance varaibles if it cached instance variable exists" do
2381
+ @c2.many_to_many :attributes, :class => @c1
2382
+ @c1.many_to_many :nodes, :class => @c2
2383
+ d = @c1.dataset
2384
+ def d.fetch_rows(s); end
2385
+ d = @c2.dataset
2386
+ def d.fetch_rows(s); end
2387
+ attrib = @c1.new(:id=>3)
2388
+ node = @c2.new(:id => 1234)
2389
+ node.attributes.should == []
2390
+ attrib.nodes.should == []
2391
+ node.add_attribute(attrib)
2392
+ attrib.associations[:nodes].should == [node]
2393
+ node.remove_all_attributes
2394
+ attrib.associations[:nodes].should == []
2395
+ end
2396
+
2397
+ it "should call an _add_ method internally to add attributes" do
2398
+ @c2.many_to_many :attributes, :class => @c1
2399
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_add_attribute"))
2400
+ p = @c2.load(:id=>10)
2401
+ c = @c1.load(:id=>123)
2402
+ def p._add_attribute(x)
2403
+ @x = x
2404
+ end
2405
+ p.add_attribute(c)
2406
+ p.instance_variable_get(:@x).should == c
2407
+ MODEL_DB.sqls.should == []
2408
+ end
2409
+
2410
+ it "should allow additional arguments given to the add_ method and pass them onwards to the _add_ method" do
2411
+ @c2.many_to_many :attributes, :class => @c1
2412
+ p = @c2.load(:id=>10)
2413
+ c = @c1.load(:id=>123)
2414
+ def p._add_attribute(x,*y)
2415
+ @x = x
2416
+ @y = y
2417
+ end
2418
+ p.add_attribute(c,:foo,:bar=>:baz)
2419
+ p.instance_variable_get(:@x).should == c
2420
+ p.instance_variable_get(:@y).should == [:foo,{:bar=>:baz}]
2421
+ end
2422
+
2423
+ it "should call a _remove_ method internally to remove attributes" do
2424
+ @c2.many_to_many :attributes, :class => @c1
2425
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_remove_attribute"))
2426
+ p = @c2.load(:id=>10)
2427
+ c = @c1.load(:id=>123)
2428
+ def p._remove_attribute(x)
2429
+ @x = x
2430
+ end
2431
+ p.remove_attribute(c)
2432
+ p.instance_variable_get(:@x).should == c
2433
+ MODEL_DB.sqls.should == []
2434
+ end
2435
+
2436
+ it "should allow additional arguments given to the remove_ method and pass them onwards to the _remove_ method" do
2437
+ @c2.many_to_many :attributes, :class => @c1
2438
+ p = @c2.load(:id=>10)
2439
+ c = @c1.load(:id=>123)
2440
+ def p._remove_attribute(x,*y)
2441
+ @x = x
2442
+ @y = y
2443
+ end
2444
+ p.remove_attribute(c,:foo,:bar=>:baz)
2445
+ p.instance_variable_get(:@x).should == c
2446
+ p.instance_variable_get(:@y).should == [:foo,{:bar=>:baz}]
2447
+ end
2448
+
2449
+ it "should allow additional arguments given to the remove_all_ method and pass them onwards to the _remove_all_ method" do
2450
+ @c2.many_to_many :attributes, :class => @c1
2451
+ p = @c2.load(:id=>10)
2452
+ c = @c1.load(:id=>123)
2453
+ def p._remove_all_attributes(*y)
2454
+ @y = y
2455
+ end
2456
+ p.remove_all_attributes(:foo,:bar=>:baz)
2457
+ p.instance_variable_get(:@y).should == [:foo,{:bar=>:baz}]
2458
+ end
2459
+
2460
+ it "should call a _remove_all_ method internally to remove attributes" do
2461
+ @c2.many_to_many :attributes, :class => @c1
2462
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_remove_all_attributes"))
2463
+ p = @c2.load(:id=>10)
2464
+ def p._remove_all_attributes
2465
+ @x = :foo
2466
+ end
2467
+ p.remove_all_attributes
2468
+ p.instance_variable_get(:@x).should == :foo
2469
+ MODEL_DB.sqls.should == []
2470
+ end
2471
+
2472
+ it "should support (before|after)_(add|remove) callbacks" do
2473
+ h = []
2474
+ @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]
2475
+ @c2.class_eval do
2476
+ @@blah = h
2477
+ def _add_attribute(v)
2478
+ @@blah << 4
2479
+ end
2480
+ def _remove_attribute(v)
2481
+ @@blah << 5
2482
+ end
2483
+ def blah(x)
2484
+ @@blah << x.pk
2485
+ end
2486
+ def blahr(x)
2487
+ @@blah << 6
2488
+ end
2489
+ end
2490
+ p = @c2.load(:id=>10)
2491
+ c = @c1.load(:id=>123)
2492
+ h.should == []
2493
+ p.add_attribute(c)
2494
+ h.should == [10, -123, 123, 4, 3]
2495
+ p.remove_attribute(c)
2496
+ h.should == [10, -123, 123, 4, 3, 123, 5, 6]
2497
+ end
2498
+
2499
+ it "should support after_load association callback" do
2500
+ h = []
2501
+ @c2.many_to_many :attributes, :class => @c1, :after_load=>[proc{|x,y| h << [x.pk, y.collect{|z|z.pk}]}, :al]
2502
+ @c2.class_eval do
2503
+ @@blah = h
2504
+ def al(v)
2505
+ v.each{|x| @@blah << x.pk}
2506
+ end
2507
+ end
2508
+ @c1.class_eval do
2509
+ def @dataset.fetch_rows(sql)
2510
+ yield({:id=>20})
2511
+ yield({:id=>30})
2512
+ end
2513
+ end
2514
+ p = @c2.load(:id=>10, :parent_id=>20)
2515
+ attributes = p.attributes
2516
+ h.should == [[10, [20, 30]], 20, 30]
2517
+ attributes.collect{|a| a.pk}.should == [20, 30]
2518
+ end
2519
+
2520
+ it "should raise error and not call internal add or remove method if before callback returns false if raise_on_save_failure is true" do
2521
+ p = @c2.load(:id=>10)
2522
+ c = @c1.load(:id=>123)
2523
+ @c2.many_to_many :attributes, :class => @c1, :before_add=>:ba, :before_remove=>:br
2524
+ p.should_receive(:ba).once.with(c).and_return(false)
2525
+ p.should_not_receive(:_add_attribute)
2526
+ p.should_not_receive(:_remove_attribute)
2527
+ p.associations[:attributes] = []
2528
+ p.raise_on_save_failure = true
2529
+ proc{p.add_attribute(c)}.should raise_error(Sequel::Error)
2530
+ p.attributes.should == []
2531
+ p.associations[:attributes] = [c]
2532
+ p.should_receive(:br).once.with(c).and_return(false)
2533
+ proc{p.remove_attribute(c)}.should raise_error(Sequel::Error)
2534
+ p.attributes.should == [c]
2535
+ end
2536
+
2537
+ it "should return nil and not call internal add or remove method if before callback returns false if raise_on_save_failure is false" do
2538
+ p = @c2.load(:id=>10)
2539
+ c = @c1.load(:id=>123)
2540
+ p.raise_on_save_failure = false
2541
+ @c2.many_to_many :attributes, :class => @c1, :before_add=>:ba, :before_remove=>:br
2542
+ p.should_receive(:ba).once.with(c).and_return(false)
2543
+ p.should_not_receive(:_add_attribute)
2544
+ p.should_not_receive(:_remove_attribute)
2545
+ p.associations[:attributes] = []
2546
+ p.add_attribute(c).should == nil
2547
+ p.attributes.should == []
2548
+ p.associations[:attributes] = [c]
2549
+ p.should_receive(:br).once.with(c).and_return(false)
2550
+ p.remove_attribute(c).should == nil
2551
+ p.attributes.should == [c]
2552
+ end
2553
+
2554
+ it "should support a :uniq option that removes duplicates from the association" do
2555
+ @c2.many_to_many :attributes, :class => @c1, :uniq=>true
2556
+ @c1.class_eval do
2557
+ def @dataset.fetch_rows(sql)
2558
+ yield({:id=>20})
2559
+ yield({:id=>30})
2560
+ yield({:id=>20})
2561
+ yield({:id=>30})
2562
+ end
2563
+ end
2564
+ @c2.load(:id=>10, :parent_id=>20).attributes.should == [@c1.load(:id=>20), @c1.load(:id=>30)]
2565
+ end
2566
+
2567
+ it "should support a :distinct option that uses the DISTINCT clause" do
2568
+ @c2.many_to_many :attributes, :class => @c1, :distinct=>true
2569
+ @c2.load(:id=>10).attributes_dataset.sql.should == "SELECT DISTINCT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 10))"
2570
+ end
2571
+ end
2572
+
2573
+ describe Sequel::Model, " association reflection methods" do
2574
+ before do
2575
+ MODEL_DB.reset
2576
+ @c1 = Class.new(Sequel::Model(:nodes)) do
2577
+ def self.name; 'Node'; end
2578
+ def self.to_s; 'Node'; end
2579
+ end
2580
+ end
2581
+
2582
+ it "#all_association_reflections should include all association reflection hashes" do
2583
+ @c1.all_association_reflections.should == []
2584
+
2585
+ @c1.associate :many_to_one, :parent, :class => @c1
2586
+ @c1.all_association_reflections.collect{|v| v[:name]}.should == [:parent]
2587
+ @c1.all_association_reflections.collect{|v| v[:type]}.should == [:many_to_one]
2588
+ @c1.all_association_reflections.collect{|v| v[:class]}.should == [@c1]
2589
+
2590
+ @c1.associate :one_to_many, :children, :class => @c1
2591
+ @c1.all_association_reflections.sort_by{|x|x[:name].to_s}
2592
+ @c1.all_association_reflections.sort_by{|x|x[:name].to_s}.collect{|v| v[:name]}.should == [:children, :parent]
2593
+ @c1.all_association_reflections.sort_by{|x|x[:name].to_s}.collect{|v| v[:type]}.should == [:one_to_many, :many_to_one]
2594
+ @c1.all_association_reflections.sort_by{|x|x[:name].to_s}.collect{|v| v[:class]}.should == [@c1, @c1]
2595
+ end
2596
+
2597
+ it "#association_reflection should return nil for nonexistent association" do
2598
+ @c1.association_reflection(:blah).should == nil
2599
+ end
2600
+
2601
+ it "#association_reflection should return association reflection hash if association exists" do
2602
+ @c1.associate :many_to_one, :parent, :class => @c1
2603
+ @c1.association_reflection(:parent).should be_a_kind_of(Sequel::Model::Associations::AssociationReflection)
2604
+ @c1.association_reflection(:parent)[:name].should == :parent
2605
+ @c1.association_reflection(:parent)[:type].should == :many_to_one
2606
+ @c1.association_reflection(:parent)[:class].should == @c1
2607
+
2608
+ @c1.associate :one_to_many, :children, :class => @c1
2609
+ @c1.association_reflection(:children).should be_a_kind_of(Sequel::Model::Associations::AssociationReflection)
2610
+ @c1.association_reflection(:children)[:name].should == :children
2611
+ @c1.association_reflection(:children)[:type].should == :one_to_many
2612
+ @c1.association_reflection(:children)[:class].should == @c1
2613
+ end
2614
+
2615
+ it "#associations should include all association names" do
2616
+ @c1.associations.should == []
2617
+ @c1.associate :many_to_one, :parent, :class => @c1
2618
+ @c1.associations.should == [:parent]
2619
+ @c1.associate :one_to_many, :children, :class => @c1
2620
+ @c1.associations.sort_by{|x|x.to_s}.should == [:children, :parent]
2621
+ end
2622
+
2623
+ it "association reflections should be copied upon subclasing" do
2624
+ @c1.associate :many_to_one, :parent, :class => @c1
2625
+ c = Class.new(@c1)
2626
+ @c1.associations.should == [:parent]
2627
+ c.associations.should == [:parent]
2628
+ c.associate :many_to_one, :parent2, :class => @c1
2629
+ @c1.associations.should == [:parent]
2630
+ c.associations.sort_by{|x| x.to_s}.should == [:parent, :parent2]
2631
+ c.instance_methods.map{|x| x.to_s}.should include('parent')
2632
+ end
2633
+ end