viking-sequel 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. metadata +346 -0
@@ -0,0 +1,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