epugh-sequel 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. data/README.rdoc +652 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/sequel +104 -0
  4. data/lib/sequel.rb +1 -0
  5. data/lib/sequel/adapters/ado.rb +85 -0
  6. data/lib/sequel/adapters/db2.rb +132 -0
  7. data/lib/sequel/adapters/dbi.rb +101 -0
  8. data/lib/sequel/adapters/do.rb +197 -0
  9. data/lib/sequel/adapters/do/mysql.rb +38 -0
  10. data/lib/sequel/adapters/do/postgres.rb +92 -0
  11. data/lib/sequel/adapters/do/sqlite.rb +31 -0
  12. data/lib/sequel/adapters/firebird.rb +307 -0
  13. data/lib/sequel/adapters/informix.rb +75 -0
  14. data/lib/sequel/adapters/jdbc.rb +485 -0
  15. data/lib/sequel/adapters/jdbc/h2.rb +62 -0
  16. data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
  18. data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
  19. data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
  20. data/lib/sequel/adapters/mysql.rb +370 -0
  21. data/lib/sequel/adapters/odbc.rb +184 -0
  22. data/lib/sequel/adapters/openbase.rb +57 -0
  23. data/lib/sequel/adapters/oracle.rb +140 -0
  24. data/lib/sequel/adapters/postgres.rb +453 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +93 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +341 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +62 -0
  28. data/lib/sequel/adapters/shared/postgres.rb +743 -0
  29. data/lib/sequel/adapters/shared/progress.rb +34 -0
  30. data/lib/sequel/adapters/shared/sqlite.rb +263 -0
  31. data/lib/sequel/adapters/sqlite.rb +243 -0
  32. data/lib/sequel/adapters/utils/date_format.rb +21 -0
  33. data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
  34. data/lib/sequel/adapters/utils/unsupported.rb +62 -0
  35. data/lib/sequel/connection_pool.rb +258 -0
  36. data/lib/sequel/core.rb +204 -0
  37. data/lib/sequel/core_sql.rb +185 -0
  38. data/lib/sequel/database.rb +687 -0
  39. data/lib/sequel/database/schema_generator.rb +324 -0
  40. data/lib/sequel/database/schema_methods.rb +164 -0
  41. data/lib/sequel/database/schema_sql.rb +324 -0
  42. data/lib/sequel/dataset.rb +422 -0
  43. data/lib/sequel/dataset/convenience.rb +237 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +220 -0
  45. data/lib/sequel/dataset/sql.rb +1105 -0
  46. data/lib/sequel/deprecated.rb +529 -0
  47. data/lib/sequel/exceptions.rb +44 -0
  48. data/lib/sequel/extensions/blank.rb +42 -0
  49. data/lib/sequel/extensions/inflector.rb +288 -0
  50. data/lib/sequel/extensions/pagination.rb +96 -0
  51. data/lib/sequel/extensions/pretty_table.rb +78 -0
  52. data/lib/sequel/extensions/query.rb +48 -0
  53. data/lib/sequel/extensions/string_date_time.rb +47 -0
  54. data/lib/sequel/metaprogramming.rb +44 -0
  55. data/lib/sequel/migration.rb +212 -0
  56. data/lib/sequel/model.rb +142 -0
  57. data/lib/sequel/model/association_reflection.rb +263 -0
  58. data/lib/sequel/model/associations.rb +1024 -0
  59. data/lib/sequel/model/base.rb +911 -0
  60. data/lib/sequel/model/deprecated.rb +188 -0
  61. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  62. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  63. data/lib/sequel/model/deprecated_validations.rb +384 -0
  64. data/lib/sequel/model/errors.rb +37 -0
  65. data/lib/sequel/model/exceptions.rb +7 -0
  66. data/lib/sequel/model/inflections.rb +230 -0
  67. data/lib/sequel/model/plugins.rb +74 -0
  68. data/lib/sequel/object_graph.rb +230 -0
  69. data/lib/sequel/plugins/caching.rb +122 -0
  70. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  71. data/lib/sequel/plugins/schema.rb +53 -0
  72. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  73. data/lib/sequel/plugins/validation_class_methods.rb +373 -0
  74. data/lib/sequel/sql.rb +854 -0
  75. data/lib/sequel/version.rb +11 -0
  76. data/lib/sequel_core.rb +1 -0
  77. data/lib/sequel_model.rb +1 -0
  78. data/spec/adapters/ado_spec.rb +46 -0
  79. data/spec/adapters/firebird_spec.rb +376 -0
  80. data/spec/adapters/informix_spec.rb +96 -0
  81. data/spec/adapters/mysql_spec.rb +875 -0
  82. data/spec/adapters/oracle_spec.rb +272 -0
  83. data/spec/adapters/postgres_spec.rb +692 -0
  84. data/spec/adapters/spec_helper.rb +10 -0
  85. data/spec/adapters/sqlite_spec.rb +550 -0
  86. data/spec/core/connection_pool_spec.rb +526 -0
  87. data/spec/core/core_ext_spec.rb +156 -0
  88. data/spec/core/core_sql_spec.rb +528 -0
  89. data/spec/core/database_spec.rb +1214 -0
  90. data/spec/core/dataset_spec.rb +3513 -0
  91. data/spec/core/expression_filters_spec.rb +363 -0
  92. data/spec/core/migration_spec.rb +261 -0
  93. data/spec/core/object_graph_spec.rb +280 -0
  94. data/spec/core/pretty_table_spec.rb +58 -0
  95. data/spec/core/schema_generator_spec.rb +167 -0
  96. data/spec/core/schema_spec.rb +778 -0
  97. data/spec/core/spec_helper.rb +82 -0
  98. data/spec/core/version_spec.rb +7 -0
  99. data/spec/extensions/blank_spec.rb +67 -0
  100. data/spec/extensions/caching_spec.rb +201 -0
  101. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  102. data/spec/extensions/inflector_spec.rb +122 -0
  103. data/spec/extensions/pagination_spec.rb +99 -0
  104. data/spec/extensions/pretty_table_spec.rb +91 -0
  105. data/spec/extensions/query_spec.rb +85 -0
  106. data/spec/extensions/schema_spec.rb +111 -0
  107. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  108. data/spec/extensions/spec_helper.rb +90 -0
  109. data/spec/extensions/string_date_time_spec.rb +93 -0
  110. data/spec/extensions/validation_class_methods_spec.rb +1054 -0
  111. data/spec/integration/dataset_test.rb +160 -0
  112. data/spec/integration/eager_loader_test.rb +683 -0
  113. data/spec/integration/prepared_statement_test.rb +130 -0
  114. data/spec/integration/schema_test.rb +183 -0
  115. data/spec/integration/spec_helper.rb +75 -0
  116. data/spec/integration/type_test.rb +96 -0
  117. data/spec/model/association_reflection_spec.rb +93 -0
  118. data/spec/model/associations_spec.rb +1780 -0
  119. data/spec/model/base_spec.rb +494 -0
  120. data/spec/model/caching_spec.rb +217 -0
  121. data/spec/model/dataset_methods_spec.rb +78 -0
  122. data/spec/model/eager_loading_spec.rb +1165 -0
  123. data/spec/model/hooks_spec.rb +472 -0
  124. data/spec/model/inflector_spec.rb +126 -0
  125. data/spec/model/model_spec.rb +588 -0
  126. data/spec/model/plugins_spec.rb +142 -0
  127. data/spec/model/record_spec.rb +1243 -0
  128. data/spec/model/schema_spec.rb +92 -0
  129. data/spec/model/spec_helper.rb +124 -0
  130. data/spec/model/validations_spec.rb +1080 -0
  131. data/spec/rcov.opts +6 -0
  132. data/spec/spec.opts +0 -0
  133. data/spec/spec_config.rb.example +10 -0
  134. metadata +202 -0
@@ -0,0 +1,93 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe Sequel::Model::Associations::AssociationReflection, "#associated_class" do
4
+ before do
5
+ @c = Class.new(Sequel::Model)
6
+ class ::ParParent < Sequel::Model; end
7
+ end
8
+
9
+ it "should use the :class value if present" do
10
+ @c.many_to_one :c, :class=>ParParent
11
+ @c.association_reflection(:c).keys.should include(:class)
12
+ @c.association_reflection(:c).associated_class.should == ParParent
13
+ end
14
+ it "should figure out the class if the :class value is not present" do
15
+ @c.many_to_one :c, :class=>'ParParent'
16
+ @c.association_reflection(:c).keys.should_not include(:class)
17
+ @c.association_reflection(:c).associated_class.should == ParParent
18
+ end
19
+ end
20
+
21
+ describe Sequel::Model::Associations::AssociationReflection, "#primary_key" do
22
+ before do
23
+ @c = Class.new(Sequel::Model)
24
+ class ::ParParent < Sequel::Model; end
25
+ end
26
+
27
+ it "should use the :primary_key value if present" do
28
+ @c.many_to_one :c, :class=>ParParent, :primary_key=>:blah__blah
29
+ @c.association_reflection(:c).keys.should include(:primary_key)
30
+ @c.association_reflection(:c).primary_key.should == :blah__blah
31
+ end
32
+ it "should use the associated table's primary key if :primary_key is not present" do
33
+ @c.many_to_one :c, :class=>'ParParent'
34
+ @c.association_reflection(:c).keys.should_not include(:primary_key)
35
+ @c.association_reflection(:c).primary_key.should == :id
36
+ end
37
+ end
38
+
39
+ describe Sequel::Model::Associations::AssociationReflection, "#reciprocal" do
40
+ it "should use the :reciprocal value if present" do
41
+ @c = Class.new(Sequel::Model)
42
+ @d = Class.new(Sequel::Model)
43
+ @c.many_to_one :c, :class=>@d, :reciprocal=>:xx
44
+ @c.association_reflection(:c).keys.should include(:reciprocal)
45
+ @c.association_reflection(:c).reciprocal.should == :xx
46
+ end
47
+
48
+ it "should figure out the reciprocal if the :reciprocal value is not present" do
49
+ class ::ParParent < Sequel::Model; end
50
+ class ::ParParentTwo < Sequel::Model; end
51
+ class ::ParParentThree < Sequel::Model; end
52
+ ParParent.many_to_one :par_parent_two
53
+ ParParentTwo.one_to_many :par_parents
54
+ ParParent.many_to_many :par_parent_threes
55
+ ParParentThree.many_to_many :par_parents
56
+
57
+ ParParent.association_reflection(:par_parent_two).keys.should_not include(:reciprocal)
58
+ ParParent.association_reflection(:par_parent_two).reciprocal.should == :par_parents
59
+ ParParentTwo.association_reflection(:par_parents).keys.should_not include(:reciprocal)
60
+ ParParentTwo.association_reflection(:par_parents).reciprocal.should == :par_parent_two
61
+ ParParent.association_reflection(:par_parent_threes).keys.should_not include(:reciprocal)
62
+ ParParent.association_reflection(:par_parent_threes).reciprocal.should == :par_parents
63
+ ParParentThree.association_reflection(:par_parents).keys.should_not include(:reciprocal)
64
+ ParParentThree.association_reflection(:par_parents).reciprocal.should == :par_parent_threes
65
+ end
66
+ end
67
+
68
+ describe Sequel::Model::Associations::AssociationReflection, "#select" do
69
+ before do
70
+ @c = Class.new(Sequel::Model)
71
+ class ::ParParent < Sequel::Model; end
72
+ end
73
+
74
+ it "should use the :select value if present" do
75
+ @c.many_to_one :c, :class=>ParParent, :select=>[:par_parents__id]
76
+ @c.association_reflection(:c).keys.should include(:select)
77
+ @c.association_reflection(:c).select.should == [:par_parents__id]
78
+ end
79
+ it "should be the associated_table.* if :select is not present for a many_to_many associaiton" do
80
+ @c.many_to_many :cs, :class=>'ParParent'
81
+ @c.association_reflection(:cs).keys.should_not include(:select)
82
+ @c.association_reflection(:cs).select.should == :par_parents.*
83
+ end
84
+ it "should be if :select is not present for a many_to_one and one_to_many associaiton" do
85
+ @c.one_to_many :cs, :class=>'ParParent'
86
+ @c.association_reflection(:cs).keys.should_not include(:select)
87
+ @c.association_reflection(:cs).select.should == nil
88
+ @c.many_to_one :c, :class=>'ParParent'
89
+ @c.association_reflection(:c).keys.should_not include(:select)
90
+ @c.association_reflection(:c).select.should == nil
91
+ end
92
+ end
93
+
@@ -0,0 +1,1780 @@
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 add a model_object and association_reflection accessors to the dataset, and return it with the current model object" do
20
+ MODEL_DB.reset
21
+ klass = Class.new(Sequel::Model(:nodes)) do
22
+ columns :id, :a_id
23
+ end
24
+ mod = Module.new do
25
+ def blah
26
+ filter{|o| o.__send__(association_reflection[:key]) > model_object.id*2}
27
+ end
28
+ end
29
+
30
+ klass.associate :many_to_one, :a, :class=>klass
31
+ klass.associate :one_to_many, :bs, :key=>:b_id, :class=>klass, :extend=>mod
32
+ klass.associate :many_to_many, :cs, :class=>klass
33
+
34
+ node = klass.load(:id=>1)
35
+ node.a_dataset.model_object.should == node
36
+ node.bs_dataset.model_object.should == node
37
+ node.cs_dataset.model_object.should == node
38
+
39
+ node.a_dataset.association_reflection.should == klass.association_reflection(:a)
40
+ node.bs_dataset.association_reflection.should == klass.association_reflection(:bs)
41
+ node.cs_dataset.association_reflection.should == klass.association_reflection(:cs)
42
+
43
+ node.bs_dataset.blah.sql.should == 'SELECT * FROM nodes WHERE ((nodes.b_id = 1) AND (b_id > 2))'
44
+ end
45
+
46
+ it "should allow extending the dataset with :extend option" do
47
+ MODEL_DB.reset
48
+ klass = Class.new(Sequel::Model(:nodes)) do
49
+ columns :id, :a_id
50
+ end
51
+ mod = Module.new do
52
+ def blah
53
+ 1
54
+ end
55
+ end
56
+ mod2 = Module.new do
57
+ def blar
58
+ 2
59
+ end
60
+ end
61
+
62
+ klass.associate :many_to_one, :a, :class=>klass, :extend=>mod
63
+ klass.associate :one_to_many, :bs, :class=>klass, :extend=>[mod]
64
+ klass.associate :many_to_many, :cs, :class=>klass, :extend=>[mod, mod2]
65
+
66
+ node = klass.load(:id=>1)
67
+ node.a_dataset.blah.should == 1
68
+ node.bs_dataset.blah.should == 1
69
+ node.cs_dataset.blah.should == 1
70
+ node.cs_dataset.blar.should == 2
71
+ end
72
+
73
+ it "should clone an existing association with the :clone option" do
74
+ MODEL_DB.reset
75
+ klass = Class.new(Sequel::Model(:nodes))
76
+
77
+ klass.many_to_one(:par_parent, :order=>:a){1}
78
+ klass.one_to_many(:par_parent1s, :class=>'ParParent', :limit=>12){4}
79
+ klass.many_to_many(:par_parent2s, :class=>:ParParent, :uniq=>true){2}
80
+
81
+ klass.many_to_one :par, :clone=>:par_parent, :select=>:b
82
+ klass.one_to_many :par1s, :clone=>:par_parent1s, :order=>:b, :limit=>10, :block=>nil
83
+ klass.many_to_many(:par2s, :clone=>:par_parent2s, :order=>:c){3}
84
+
85
+ klass.association_reflection(:par).associated_class.should == ParParent
86
+ klass.association_reflection(:par1s).associated_class.should == ParParent
87
+ klass.association_reflection(:par2s).associated_class.should == ParParent
88
+
89
+ klass.association_reflection(:par)[:order].should == :a
90
+ klass.association_reflection(:par).select.should == :b
91
+ klass.association_reflection(:par)[:block].call.should == 1
92
+ klass.association_reflection(:par1s)[:limit].should == 10
93
+ klass.association_reflection(:par1s)[:order].should == :b
94
+ klass.association_reflection(:par1s)[:block].should == nil
95
+ klass.association_reflection(:par2s)[:after_load].length.should == 1
96
+ klass.association_reflection(:par2s)[:order].should == :c
97
+ klass.association_reflection(:par2s)[:block].call.should == 3
98
+ end
99
+
100
+ end
101
+
102
+ describe Sequel::Model, "many_to_one" do
103
+ before do
104
+ MODEL_DB.reset
105
+
106
+ @c2 = Class.new(Sequel::Model(:nodes)) do
107
+ unrestrict_primary_key
108
+ columns :id, :parent_id, :par_parent_id, :blah
109
+ end
110
+
111
+ @dataset = @c2.dataset
112
+ end
113
+
114
+ it "should use implicit key if omitted" do
115
+ @c2.many_to_one :parent, :class => @c2
116
+
117
+ d = @c2.new(:id => 1, :parent_id => 234)
118
+ p = d.parent
119
+ p.class.should == @c2
120
+ p.values.should == {:x => 1, :id => 1}
121
+
122
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 234) LIMIT 1"]
123
+ end
124
+
125
+ it "should use implicit class if omitted" do
126
+ class ::ParParent < Sequel::Model
127
+ end
128
+
129
+ @c2.many_to_one :par_parent
130
+
131
+ d = @c2.new(:id => 1, :par_parent_id => 234)
132
+ p = d.par_parent
133
+ p.class.should == ParParent
134
+
135
+ MODEL_DB.sqls.should == ["SELECT * FROM par_parents WHERE (par_parents.id = 234) LIMIT 1"]
136
+ end
137
+
138
+ it "should use class inside module if given as a string" do
139
+ module ::Par
140
+ class Parent < Sequel::Model
141
+ end
142
+ end
143
+
144
+ @c2.many_to_one :par_parent, :class=>"Par::Parent"
145
+
146
+ d = @c2.new(:id => 1, :par_parent_id => 234)
147
+ p = d.par_parent
148
+ p.class.should == Par::Parent
149
+
150
+ MODEL_DB.sqls.should == ["SELECT * FROM parents WHERE (parents.id = 234) LIMIT 1"]
151
+ end
152
+
153
+ it "should use explicit key if given" do
154
+ @c2.many_to_one :parent, :class => @c2, :key => :blah
155
+
156
+ d = @c2.new(:id => 1, :blah => 567)
157
+ p = d.parent
158
+ p.class.should == @c2
159
+ p.values.should == {:x => 1, :id => 1}
160
+
161
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 567) LIMIT 1"]
162
+ end
163
+
164
+ it "should use :primary_key option if given" do
165
+ @c2.many_to_one :parent, :class => @c2, :key => :blah, :primary_key => :pk
166
+ @c2.new(:id => 1, :blah => 567).parent
167
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.pk = 567) LIMIT 1"]
168
+ end
169
+
170
+ it "should use :select option if given" do
171
+ @c2.many_to_one :parent, :class => @c2, :key => :blah, :select=>[:id, :name]
172
+ @c2.new(:id => 1, :blah => 567).parent
173
+ MODEL_DB.sqls.should == ["SELECT id, name FROM nodes WHERE (nodes.id = 567) LIMIT 1"]
174
+ end
175
+
176
+ it "should use :conditions option if given" do
177
+ @c2.many_to_one :parent, :class => @c2, :key => :blah, :conditions=>{:a=>32}
178
+ @c2.new(:id => 1, :blah => 567).parent
179
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.id = 567) AND (a = 32)) LIMIT 1"]
180
+
181
+ @c2.many_to_one :parent, :class => @c2, :key => :blah, :conditions=>:a
182
+ MODEL_DB.sqls.clear
183
+ @c2.new(:id => 1, :blah => 567).parent
184
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.id = 567) AND a) LIMIT 1"]
185
+ end
186
+
187
+ it "should support :order, :limit (only for offset), and :dataset options, as well as a block" do
188
+ c2 = @c2
189
+ @c2.many_to_one :child_20, :class => @c2, :key=>:id, :dataset=>proc{c2.filter(:parent_id=>pk)}, :limit=>[10,20], :order=>:name do |ds|
190
+ ds.filter(:x.sql_number > 1)
191
+ end
192
+ @c2.load(:id => 100).child_20
193
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((parent_id = 100) AND (x > 1)) ORDER BY name LIMIT 1 OFFSET 20"]
194
+ end
195
+
196
+ it "should return nil if key value is nil" do
197
+ @c2.many_to_one :parent, :class => @c2
198
+
199
+ d = @c2.new(:id => 1)
200
+ d.parent.should == nil
201
+ end
202
+
203
+ it "should cache negative lookup" do
204
+ @c2.many_to_one :parent, :class => @c2
205
+ ds = @c2.dataset
206
+ def ds.fetch_rows(sql, &block)
207
+ MODEL_DB.sqls << sql
208
+ end
209
+
210
+ d = @c2.new(:id => 1, :parent_id=>555)
211
+ MODEL_DB.sqls.should == []
212
+ d.parent.should == nil
213
+ MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.id = 555) LIMIT 1']
214
+ d.parent.should == nil
215
+ MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.id = 555) LIMIT 1']
216
+ end
217
+
218
+ it "should define a setter method" do
219
+ @c2.many_to_one :parent, :class => @c2
220
+
221
+ d = @c2.new(:id => 1)
222
+ d.parent = @c2.new(:id => 4321)
223
+ d.values.should == {:id => 1, :parent_id => 4321}
224
+
225
+ d.parent = nil
226
+ d.values.should == {:id => 1, :parent_id => nil}
227
+
228
+ e = @c2.new(:id => 6677)
229
+ d.parent = e
230
+ d.values.should == {:id => 1, :parent_id => 6677}
231
+ end
232
+
233
+ it "should have the setter method respect the :primary_key option" do
234
+ @c2.many_to_one :parent, :class => @c2, :primary_key=>:blah
235
+
236
+ d = @c2.new(:id => 1)
237
+ d.parent = @c2.new(:id => 4321, :blah=>444)
238
+ d.values.should == {:id => 1, :parent_id => 444}
239
+
240
+ d.parent = nil
241
+ d.values.should == {:id => 1, :parent_id => nil}
242
+
243
+ e = @c2.new(:id => 6677, :blah=>8)
244
+ d.parent = e
245
+ d.values.should == {:id => 1, :parent_id => 8}
246
+ end
247
+
248
+ it "should not persist changes until saved" do
249
+ @c2.many_to_one :parent, :class => @c2
250
+
251
+ d = @c2.load(:id => 1)
252
+ MODEL_DB.reset
253
+ d.parent = @c2.new(:id => 345)
254
+ MODEL_DB.sqls.should == []
255
+ d.save_changes
256
+ MODEL_DB.sqls.should == ['UPDATE nodes SET parent_id = 345 WHERE (id = 1)']
257
+ end
258
+
259
+ it "should set cached instance variable when accessed" do
260
+ @c2.many_to_one :parent, :class => @c2
261
+
262
+ d = @c2.load(:id => 1)
263
+ MODEL_DB.reset
264
+ d.parent_id = 234
265
+ d.associations[:parent].should == nil
266
+ ds = @c2.dataset
267
+ def ds.fetch_rows(sql, &block); MODEL_DB.sqls << sql; yield({:id=>234}) end
268
+ e = d.parent
269
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 234) LIMIT 1"]
270
+ d.associations[:parent].should == e
271
+ end
272
+
273
+ it "should set cached instance variable when assigned" do
274
+ @c2.many_to_one :parent, :class => @c2
275
+
276
+ d = @c2.create(:id => 1)
277
+ MODEL_DB.reset
278
+ d.associations[:parent].should == nil
279
+ d.parent = @c2.new(:id => 234)
280
+ e = d.parent
281
+ d.associations[:parent].should == e
282
+ MODEL_DB.sqls.should == []
283
+ end
284
+
285
+ it "should use cached instance variable if available" do
286
+ @c2.many_to_one :parent, :class => @c2
287
+
288
+ d = @c2.create(:id => 1, :parent_id => 234)
289
+ MODEL_DB.reset
290
+ d.associations[:parent] = 42
291
+ d.parent.should == 42
292
+ MODEL_DB.sqls.should == []
293
+ end
294
+
295
+ it "should not use cached instance variable if asked to reload" do
296
+ @c2.many_to_one :parent, :class => @c2
297
+
298
+ d = @c2.create(:id => 1)
299
+ MODEL_DB.reset
300
+ d.parent_id = 234
301
+ d.associations[:parent] = 42
302
+ d.parent(true).should_not == 42
303
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 234) LIMIT 1"]
304
+ end
305
+
306
+ it "should have the setter add to the reciprocal one_to_many cached association list if it exists" do
307
+ @c2.many_to_one :parent, :class => @c2
308
+ @c2.one_to_many :children, :class => @c2, :key=>:parent_id
309
+ ds = @c2.dataset
310
+ def ds.fetch_rows(sql, &block)
311
+ MODEL_DB.sqls << sql
312
+ end
313
+
314
+ d = @c2.new(:id => 1)
315
+ e = @c2.new(:id => 2)
316
+ MODEL_DB.sqls.should == []
317
+ d.parent = e
318
+ e.children.should_not(include(d))
319
+ MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.parent_id = 2)']
320
+
321
+ MODEL_DB.reset
322
+ d = @c2.new(:id => 1)
323
+ e = @c2.new(:id => 2)
324
+ e.children.should_not(include(d))
325
+ MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.parent_id = 2)']
326
+ d.parent = e
327
+ e.children.should(include(d))
328
+ MODEL_DB.sqls.should == ['SELECT * FROM nodes WHERE (nodes.parent_id = 2)']
329
+ end
330
+
331
+ 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
332
+ @c2.many_to_one :parent, :class => @c2
333
+ @c2.one_to_many :children, :class => @c2, :key=>:parent_id
334
+ ds = @c2.dataset
335
+ def ds.fetch_rows(sql, &block)
336
+ MODEL_DB.sqls << sql
337
+ end
338
+
339
+ d = @c2.new(:id => 1)
340
+ e = @c2.new(:id => 2)
341
+ f = @c2.new(:id => 3)
342
+ e.children.should_not(include(d))
343
+ f.children.should_not(include(d))
344
+ MODEL_DB.reset
345
+ d.parent = e
346
+ e.children.should(include(d))
347
+ d.parent = f
348
+ f.children.should(include(d))
349
+ e.children.should_not(include(d))
350
+ d.parent = nil
351
+ f.children.should_not(include(d))
352
+ MODEL_DB.sqls.should == []
353
+ end
354
+
355
+ it "should get all matching records and only return the first if :key option is set to nil" do
356
+ c2 = @c2
357
+ @c2.one_to_many :children, :class => @c2, :key=>:parent_id
358
+ @c2.many_to_one :first_grand_parent, :class => @c2, :key=>nil, :eager_graph=>:children, :dataset=>proc{c2.filter(:children_id=>parent_id)}
359
+ ds = @c2.dataset
360
+ def ds.columns
361
+ [:id, :parent_id, :par_parent_id, :blah]
362
+ end
363
+ def ds.fetch_rows(sql, &block)
364
+ MODEL_DB.sqls << sql
365
+ 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})
366
+ end
367
+ p = @c2.new(:parent_id=>2)
368
+ fgp = p.first_grand_parent
369
+ 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)"]
370
+ fgp.values.should == {:id=>1, :parent_id=>0, :par_parent_id=>3, :blah=>4}
371
+ fgp.children.first.values.should == {:id=>2, :parent_id=>1, :par_parent_id=>5, :blah=>6}
372
+ end
373
+
374
+ it "should not create the setter method if :read_only option is used" do
375
+ @c2.many_to_one :parent, :class => @c2, :read_only=>true
376
+ @c2.instance_methods.collect{|x| x.to_s}.should(include('parent'))
377
+ @c2.instance_methods.collect{|x| x.to_s}.should_not(include('parent='))
378
+ end
379
+
380
+ it "should not add associations methods directly to class" do
381
+ @c2.many_to_one :parent, :class => @c2
382
+ @c2.instance_methods.collect{|x| x.to_s}.should(include('parent'))
383
+ @c2.instance_methods.collect{|x| x.to_s}.should(include('parent='))
384
+ @c2.instance_methods(false).collect{|x| x.to_s}.should_not(include('parent'))
385
+ @c2.instance_methods(false).collect{|x| x.to_s}.should_not(include('parent='))
386
+ end
387
+
388
+ it "should raise an error if trying to set a model object that doesn't have a valid primary key" do
389
+ @c2.many_to_one :parent, :class => @c2
390
+ p = @c2.new
391
+ c = @c2.load(:id=>123)
392
+ proc{c.parent = p}.should raise_error(Sequel::Error)
393
+ end
394
+
395
+ deprec_specify "should have belongs_to alias" do
396
+ @c2.belongs_to :parent, :class => @c2
397
+
398
+ d = @c2.load(:id => 1)
399
+ MODEL_DB.reset
400
+ d.parent_id = 234
401
+ d.associations[:parent].should == nil
402
+ ds = @c2.dataset
403
+ def ds.fetch_rows(sql, &block); MODEL_DB.sqls << sql; yield({:id=>234}) end
404
+ e = d.parent
405
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 234) LIMIT 1"]
406
+ d.associations[:parent].should == e
407
+ end
408
+
409
+ it "should make the change to the foreign_key value inside a _association= method" do
410
+ @c2.many_to_one :parent, :class => @c2
411
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_parent="))
412
+ p = @c2.new
413
+ c = @c2.load(:id=>123)
414
+ def p._parent=(x)
415
+ @x = x
416
+ end
417
+ p.should_not_receive(:parent_id=)
418
+ p.parent = c
419
+ p.instance_variable_get(:@x).should == c
420
+ end
421
+
422
+ it "should support (before|after)_(add|remove) callbacks" do
423
+ h = []
424
+ @c2.many_to_one :parent, :class => @c2, :before_add=>[proc{|x,y| h << x.pk; h << -y.pk}, :blah], :after_add=>proc{h << 3}, :before_remove=>:blah, :after_remove=>[:blahr]
425
+ @c2.class_eval do
426
+ @@blah = h
427
+ def []=(a, v)
428
+ a == :parent_id ? (@@blah << (v ? 4 : 5)) : super
429
+ end
430
+ def blah(x)
431
+ @@blah << x.pk
432
+ end
433
+ def blahr(x)
434
+ @@blah << 6
435
+ end
436
+ end
437
+ p = @c2.load(:id=>10)
438
+ c = @c2.load(:id=>123)
439
+ h.should == []
440
+ p.parent = c
441
+ h.should == [10, -123, 123, 4, 3]
442
+ p.parent = nil
443
+ h.should == [10, -123, 123, 4, 3, 123, 5, 6]
444
+ end
445
+
446
+ it "should support after_load association callback" do
447
+ h = []
448
+ @c2.many_to_one :parent, :class => @c2, :after_load=>[proc{|x,y| h << [x.pk, y.pk]}, :al]
449
+ @c2.class_eval do
450
+ @@blah = h
451
+ def al(v)
452
+ @@blah << v.pk
453
+ end
454
+ def @dataset.fetch_rows(sql)
455
+ yield({:id=>20})
456
+ end
457
+ end
458
+ p = @c2.load(:id=>10, :parent_id=>20)
459
+ parent = p.parent
460
+ h.should == [[10, 20], 20]
461
+ parent.pk.should == 20
462
+ end
463
+
464
+ 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
465
+ # The reason for this is that assignment in ruby always returns the argument instead of the result
466
+ # of the method, so we can't return nil to signal that the association callback prevented the modification
467
+ p = @c2.new
468
+ c = @c2.load(:id=>123)
469
+ p.raise_on_save_failure = false
470
+ @c2.many_to_one :parent, :class => @c2, :before_add=>:ba, :before_remove=>:br
471
+ p.should_receive(:ba).once.with(c).and_return(false)
472
+ p.should_not_receive(:_parent=)
473
+ proc{p.parent = c}.should raise_error(Sequel::Error)
474
+ p.parent.should == nil
475
+ p.associations[:parent] = c
476
+ p.parent.should == c
477
+ p.should_receive(:br).once.with(c).and_return(false)
478
+ proc{p.parent = nil}.should raise_error(Sequel::Error)
479
+ end
480
+
481
+ it "should raise an error if a callback is not a proc or symbol" do
482
+ @c2.many_to_one :parent, :class => @c2, :before_add=>Object.new
483
+ proc{@c2.new.parent = @c2.load(:id=>1)}.should raise_error(Sequel::Error)
484
+ end
485
+
486
+ it "should call the remove callbacks for the previous object and the add callbacks for the new object" do
487
+ c = @c2.load(:id=>123)
488
+ d = @c2.load(:id=>321)
489
+ p = @c2.new
490
+ p.associations[:parent] = d
491
+ h = []
492
+ @c2.many_to_one :parent, :class => @c2, :before_add=>:ba, :before_remove=>:br, :after_add=>:aa, :after_remove=>:ar
493
+ @c2.class_eval do
494
+ @@blah = h
495
+ def []=(a, v)
496
+ a == :parent_id ? (@@blah << 5) : super
497
+ end
498
+ def ba(x)
499
+ @@blah << x.pk
500
+ end
501
+ def br(x)
502
+ @@blah << x.pk * -1
503
+ end
504
+ def aa(x)
505
+ @@blah << x.pk * 2
506
+ end
507
+ def ar(x)
508
+ @@blah << x.pk * -2
509
+ end
510
+ end
511
+ p.parent = c
512
+ h.should == [-321, 123, 5, 246, -642]
513
+ end
514
+ end
515
+
516
+ describe Sequel::Model, "one_to_many" do
517
+
518
+ before(:each) do
519
+ MODEL_DB.reset
520
+
521
+ @c1 = Class.new(Sequel::Model(:attributes)) do
522
+ unrestrict_primary_key
523
+ columns :id, :node_id
524
+ end
525
+
526
+ @c2 = Class.new(Sequel::Model(:nodes)) do
527
+ unrestrict_primary_key
528
+ attr_accessor :xxx
529
+
530
+ def self.name; 'Node'; end
531
+ def self.to_s; 'Node'; end
532
+ columns :id
533
+ end
534
+ @dataset = @c2.dataset
535
+
536
+ @c2.dataset.extend(Module.new {
537
+ def fetch_rows(sql)
538
+ @db << sql
539
+ yield Hash.new
540
+ end
541
+ })
542
+
543
+ @c1.dataset.extend(Module.new {
544
+ def fetch_rows(sql)
545
+ @db << sql
546
+ yield Hash.new
547
+ end
548
+ })
549
+ end
550
+
551
+ it "should use implicit key if omitted" do
552
+ @c2.one_to_many :attributes, :class => @c1
553
+
554
+ n = @c2.new(:id => 1234)
555
+ a = n.attributes_dataset
556
+ a.should be_a_kind_of(Sequel::Dataset)
557
+ a.sql.should == 'SELECT * FROM attributes WHERE (attributes.node_id = 1234)'
558
+ end
559
+
560
+ it "should use implicit class if omitted" do
561
+ class ::HistoricalValue < Sequel::Model
562
+ end
563
+
564
+ @c2.one_to_many :historical_values
565
+
566
+ n = @c2.new(:id => 1234)
567
+ v = n.historical_values_dataset
568
+ v.should be_a_kind_of(Sequel::Dataset)
569
+ v.sql.should == 'SELECT * FROM historical_values WHERE (historical_values.node_id = 1234)'
570
+ v.model.should == HistoricalValue
571
+ end
572
+
573
+ it "should use class inside a module if given as a string" do
574
+ module ::Historical
575
+ class Value < Sequel::Model
576
+ end
577
+ end
578
+
579
+ @c2.one_to_many :historical_values, :class=>'Historical::Value'
580
+
581
+ n = @c2.new(:id => 1234)
582
+ v = n.historical_values_dataset
583
+ v.should be_a_kind_of(Sequel::Dataset)
584
+ v.sql.should == 'SELECT * FROM values WHERE (values.node_id = 1234)'
585
+ v.model.should == Historical::Value
586
+ end
587
+
588
+ it "should use explicit key if given" do
589
+ @c2.one_to_many :attributes, :class => @c1, :key => :nodeid
590
+
591
+ n = @c2.new(:id => 1234)
592
+ a = n.attributes_dataset
593
+ a.should be_a_kind_of(Sequel::Dataset)
594
+ a.sql.should == 'SELECT * FROM attributes WHERE (attributes.nodeid = 1234)'
595
+ end
596
+
597
+ it "should define an add_ method" do
598
+ @c2.one_to_many :attributes, :class => @c1
599
+
600
+ n = @c2.new(:id => 1234)
601
+ a = @c1.new(:id => 2345)
602
+ a.save
603
+ MODEL_DB.reset
604
+ a.should == n.add_attribute(a)
605
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 1234 WHERE (id = 2345)']
606
+ end
607
+
608
+ it "should define a remove_ method" do
609
+ @c2.one_to_many :attributes, :class => @c1
610
+
611
+ n = @c2.new(:id => 1234)
612
+ a = @c1.new(:id => 2345)
613
+ a.save
614
+ MODEL_DB.reset
615
+ a.should == n.remove_attribute(a)
616
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE (id = 2345)']
617
+ end
618
+
619
+ it "should have add_ method respect the :primary_key option" do
620
+ @c2.one_to_many :attributes, :class => @c1, :primary_key=>:xxx
621
+
622
+ n = @c2.new(:id => 1234, :xxx=>5)
623
+ a = @c1.new(:id => 2345)
624
+ a.save
625
+ MODEL_DB.reset
626
+ a.should == n.add_attribute(a)
627
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 5 WHERE (id = 2345)']
628
+ end
629
+
630
+
631
+ it "should raise an error in add_ and remove_ if the passed object returns false to save (is not valid)" do
632
+ @c2.one_to_many :attributes, :class => @c1
633
+ n = @c2.new(:id => 1234)
634
+ a = @c1.new(:id => 2345)
635
+ def a.valid?; false; end
636
+ proc{n.add_attribute(a)}.should raise_error(Sequel::Error)
637
+ proc{n.remove_attribute(a)}.should raise_error(Sequel::Error)
638
+ end
639
+
640
+ it "should raise an error if the model object doesn't have a valid primary key" do
641
+ @c2.one_to_many :attributes, :class => @c1
642
+ a = @c2.new
643
+ n = @c1.load(:id=>123)
644
+ proc{a.attributes_dataset}.should raise_error(Sequel::Error)
645
+ proc{a.attributes}.should raise_error(Sequel::Error)
646
+ proc{a.add_attribute(n)}.should raise_error(Sequel::Error)
647
+ proc{a.remove_attribute(n)}.should raise_error(Sequel::Error)
648
+ proc{a.remove_all_attributes}.should raise_error(Sequel::Error)
649
+ end
650
+
651
+ it "should use :primary_key option if given" do
652
+ @c1.one_to_many :nodes, :class => @c2, :primary_key => :node_id, :key=>:id
653
+ n = @c1.load(:id => 1234, :node_id=>4321)
654
+ n.nodes_dataset.sql.should == "SELECT * FROM nodes WHERE (nodes.id = 4321)"
655
+ end
656
+
657
+ it "should support a select option" do
658
+ @c2.one_to_many :attributes, :class => @c1, :select => [:id, :name]
659
+
660
+ n = @c2.new(:id => 1234)
661
+ n.attributes_dataset.sql.should == "SELECT id, name FROM attributes WHERE (attributes.node_id = 1234)"
662
+ end
663
+
664
+ it "should support a conditions option" do
665
+ @c2.one_to_many :attributes, :class => @c1, :conditions => {:a=>32}
666
+ n = @c2.new(:id => 1234)
667
+ n.attributes_dataset.sql.should == "SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (a = 32))"
668
+ @c2.one_to_many :attributes, :class => @c1, :conditions => ~:a
669
+ n = @c2.new(:id => 1234)
670
+ n.attributes_dataset.sql.should == "SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND NOT a)"
671
+ end
672
+
673
+ it "should support an order option" do
674
+ @c2.one_to_many :attributes, :class => @c1, :order => :kind
675
+
676
+ n = @c2.new(:id => 1234)
677
+ n.attributes_dataset.sql.should == "SELECT * FROM attributes WHERE (attributes.node_id = 1234) ORDER BY kind"
678
+ end
679
+
680
+ it "should support an array for the order option" do
681
+ @c2.one_to_many :attributes, :class => @c1, :order => [:kind1, :kind2]
682
+
683
+ n = @c2.new(:id => 1234)
684
+ n.attributes_dataset.sql.should == "SELECT * FROM attributes WHERE (attributes.node_id = 1234) ORDER BY kind1, kind2"
685
+ end
686
+
687
+ it "should return array with all members of the association" do
688
+ @c2.one_to_many :attributes, :class => @c1
689
+
690
+ n = @c2.new(:id => 1234)
691
+ atts = n.attributes
692
+ atts.should be_a_kind_of(Array)
693
+ atts.size.should == 1
694
+ atts.first.should be_a_kind_of(@c1)
695
+ atts.first.values.should == {}
696
+
697
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
698
+ end
699
+
700
+ it "should accept a block" do
701
+ @c2.one_to_many :attributes, :class => @c1 do |ds|
702
+ ds.filter(:xxx => @xxx)
703
+ end
704
+
705
+ n = @c2.new(:id => 1234)
706
+ atts = n.attributes
707
+ atts.should be_a_kind_of(Array)
708
+ atts.size.should == 1
709
+ atts.first.should be_a_kind_of(@c1)
710
+ atts.first.values.should == {}
711
+
712
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (xxx IS NULL))']
713
+ end
714
+
715
+ it "should support :order option with block" do
716
+ @c2.one_to_many :attributes, :class => @c1, :order => :kind do |ds|
717
+ ds.filter(:xxx => @xxx)
718
+ end
719
+
720
+ n = @c2.new(:id => 1234)
721
+ atts = n.attributes
722
+ atts.should be_a_kind_of(Array)
723
+ atts.size.should == 1
724
+ atts.first.should be_a_kind_of(@c1)
725
+ atts.first.values.should == {}
726
+
727
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (xxx IS NULL)) ORDER BY kind']
728
+ end
729
+
730
+ it "should have the block argument affect the _dataset method" do
731
+ @c2.one_to_many :attributes, :class => @c1 do |ds|
732
+ ds.filter(:xxx => 456)
733
+ end
734
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (xxx = 456))'
735
+ end
736
+
737
+ it "should support a :dataset option that is used instead of the default" do
738
+ c1 = @c1
739
+ @c2.one_to_many :all_other_attributes, :class => @c1, :dataset=>proc{c1.filter(:nodeid=>pk).invert}, :order=>:a, :limit=>10 do |ds|
740
+ ds.filter(:xxx => 5)
741
+ end
742
+
743
+ @c2.new(:id => 1234).all_other_attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE ((nodeid != 1234) AND (xxx = 5)) ORDER BY a LIMIT 10'
744
+ n = @c2.new(:id => 1234)
745
+ atts = n.all_other_attributes
746
+ atts.should be_a_kind_of(Array)
747
+ atts.size.should == 1
748
+ atts.first.should be_a_kind_of(@c1)
749
+ atts.first.values.should == {}
750
+
751
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE ((nodeid != 1234) AND (xxx = 5)) ORDER BY a LIMIT 10']
752
+ end
753
+
754
+ it "should support a :limit option" do
755
+ @c2.one_to_many :attributes, :class => @c1 , :limit=>10
756
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE (attributes.node_id = 1234) LIMIT 10'
757
+ @c2.one_to_many :attributes, :class => @c1 , :limit=>[10,10]
758
+ @c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE (attributes.node_id = 1234) LIMIT 10 OFFSET 10'
759
+ end
760
+
761
+ it "should have the :eager option affect the _dataset method" do
762
+ @c2.one_to_many :attributes, :class => @c2 , :eager=>:attributes
763
+ @c2.new(:id => 1234).attributes_dataset.opts[:eager].should == {:attributes=>nil}
764
+ end
765
+
766
+ it "should set cached instance variable when accessed" do
767
+ @c2.one_to_many :attributes, :class => @c1
768
+
769
+ n = @c2.new(:id => 1234)
770
+ MODEL_DB.reset
771
+ n.associations.include?(:attributes).should == false
772
+ atts = n.attributes
773
+ atts.should == n.associations[:attributes]
774
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
775
+ end
776
+
777
+ it "should use cached instance variable if available" do
778
+ @c2.one_to_many :attributes, :class => @c1
779
+
780
+ n = @c2.new(:id => 1234)
781
+ MODEL_DB.reset
782
+ n.associations[:attributes] = 42
783
+ n.attributes.should == 42
784
+ MODEL_DB.sqls.should == []
785
+ end
786
+
787
+ it "should not use cached instance variable if asked to reload" do
788
+ @c2.one_to_many :attributes, :class => @c1
789
+
790
+ n = @c2.new(:id => 1234)
791
+ MODEL_DB.reset
792
+ n.associations[:attributes] = 42
793
+ n.attributes(true).should_not == 42
794
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
795
+ end
796
+
797
+ it "should add item to cached instance variable if it exists when calling add_" do
798
+ @c2.one_to_many :attributes, :class => @c1
799
+
800
+ n = @c2.new(:id => 1234)
801
+ att = @c1.new(:id => 345)
802
+ MODEL_DB.reset
803
+ a = []
804
+ n.associations[:attributes] = a
805
+ n.add_attribute(att)
806
+ a.should == [att]
807
+ end
808
+
809
+ it "should set object to item's reciprocal cached association variable when calling add_" do
810
+ @c2.one_to_many :attributes, :class => @c1
811
+ @c1.many_to_one :node, :class => @c2
812
+
813
+ n = @c2.new(:id => 1234)
814
+ att = @c1.new(:id => 345)
815
+ n.add_attribute(att)
816
+ att.node.should == n
817
+ end
818
+
819
+ it "should remove item from cached instance variable if it exists when calling remove_" do
820
+ @c2.one_to_many :attributes, :class => @c1
821
+
822
+ n = @c2.load(:id => 1234)
823
+ att = @c1.load(:id => 345)
824
+ MODEL_DB.reset
825
+ a = [att]
826
+ n.associations[:attributes] = a
827
+ n.remove_attribute(att)
828
+ a.should == []
829
+ end
830
+
831
+ it "should remove item's reciprocal cached association variable when calling remove_" do
832
+ @c2.one_to_many :attributes, :class => @c1
833
+ @c1.many_to_one :node, :class => @c2
834
+
835
+ n = @c2.new(:id => 1234)
836
+ att = @c1.new(:id => 345)
837
+ att.associations[:node] = n
838
+ att.node.should == n
839
+ n.remove_attribute(att)
840
+ att.node.should == nil
841
+ end
842
+
843
+ it "should not create the add_, remove_, or remove_all_ methods if :read_only option is used" do
844
+ @c2.one_to_many :attributes, :class => @c1, :read_only=>true
845
+ im = @c2.instance_methods.collect{|x| x.to_s}
846
+ im.should(include('attributes'))
847
+ im.should(include('attributes_dataset'))
848
+ im.should_not(include('add_attribute'))
849
+ im.should_not(include('remove_attribute'))
850
+ im.should_not(include('remove_all_attributes'))
851
+ end
852
+
853
+ it "should not add associations methods directly to class" do
854
+ @c2.one_to_many :attributes, :class => @c1
855
+ im = @c2.instance_methods.collect{|x| x.to_s}
856
+ im.should(include('attributes'))
857
+ im.should(include('attributes_dataset'))
858
+ im.should(include('add_attribute'))
859
+ im.should(include('remove_attribute'))
860
+ im.should(include('remove_all_attributes'))
861
+ im2 = @c2.instance_methods(false).collect{|x| x.to_s}
862
+ im2.should_not(include('attributes'))
863
+ im2.should_not(include('attributes_dataset'))
864
+ im2.should_not(include('add_attribute'))
865
+ im2.should_not(include('remove_attribute'))
866
+ im2.should_not(include('remove_all_attributes'))
867
+ end
868
+
869
+ deprec_specify "should have has_many alias" do
870
+ @c2.has_many :attributes, :class => @c1
871
+
872
+ n = @c2.new(:id => 1234)
873
+ atts = n.attributes
874
+ atts.should be_a_kind_of(Array)
875
+ atts.size.should == 1
876
+ atts.first.should be_a_kind_of(@c1)
877
+ atts.first.values.should == {}
878
+
879
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
880
+ end
881
+
882
+ it "should populate the reciprocal many_to_one instance variable when loading the one_to_many association" do
883
+ @c2.one_to_many :attributes, :class => @c1, :key => :node_id
884
+ @c1.many_to_one :node, :class => @c2, :key => :node_id
885
+
886
+ n = @c2.new(:id => 1234)
887
+ atts = n.attributes
888
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
889
+ atts.should be_a_kind_of(Array)
890
+ atts.size.should == 1
891
+ atts.first.should be_a_kind_of(@c1)
892
+ atts.first.values.should == {}
893
+ atts.first.node.should == n
894
+
895
+ MODEL_DB.sqls.length.should == 1
896
+ end
897
+
898
+ it "should use an explicit reciprocal instance variable if given" do
899
+ @c2.one_to_many :attributes, :class => @c1, :key => :node_id, :reciprocal=>:wxyz
900
+
901
+ n = @c2.new(:id => 1234)
902
+ atts = n.attributes
903
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
904
+ atts.should be_a_kind_of(Array)
905
+ atts.size.should == 1
906
+ atts.first.should be_a_kind_of(@c1)
907
+ atts.first.values.should == {}
908
+ atts.first.associations[:wxyz].should == n
909
+
910
+ MODEL_DB.sqls.length.should == 1
911
+ end
912
+
913
+ it "should have an remove_all_ method that removes all associations" do
914
+ @c2.one_to_many :attributes, :class => @c1
915
+ @c2.new(:id => 1234).remove_all_attributes
916
+ MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE (node_id = 1234)'
917
+ end
918
+
919
+ it "should have the remove_all_ method respect the :primary_key option" do
920
+ @c2.one_to_many :attributes, :class => @c1, :primary_key=>:xxx
921
+ @c2.new(:id => 1234, :xxx=>5).remove_all_attributes
922
+ MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE (node_id = 5)'
923
+ end
924
+
925
+ it "remove_all should set the cached instance variable to []" do
926
+ @c2.one_to_many :attributes, :class => @c1
927
+ node = @c2.new(:id => 1234)
928
+ node.remove_all_attributes
929
+ node.associations[:attributes].should == []
930
+ end
931
+
932
+ it "remove_all should return the array of previously associated items if the cached instance variable exists" do
933
+ @c2.one_to_many :attributes, :class => @c1
934
+ attrib = @c1.new(:id=>3)
935
+ node = @c2.new(:id => 1234)
936
+ d = @c1.dataset
937
+ def d.fetch_rows(s); end
938
+ node.attributes.should == []
939
+ def attrib.save; self end
940
+ node.add_attribute(attrib)
941
+ node.associations[:attributes].should == [attrib]
942
+ node.remove_all_attributes.should == [attrib]
943
+ end
944
+
945
+ it "remove_all should return nil if the cached instance variable does not exist" do
946
+ @c2.one_to_many :attributes, :class => @c1
947
+ @c2.new(:id => 1234).remove_all_attributes.should == nil
948
+ end
949
+
950
+ it "remove_all should remove the current item from all reciprocal instance varaibles if it cached instance variable exists" do
951
+ @c2.one_to_many :attributes, :class => @c1
952
+ @c1.many_to_one :node, :class => @c2
953
+ d = @c1.dataset
954
+ def d.fetch_rows(s); end
955
+ d = @c2.dataset
956
+ def d.fetch_rows(s); end
957
+ attrib = @c1.new(:id=>3)
958
+ node = @c2.new(:id => 1234)
959
+ node.attributes.should == []
960
+ attrib.node.should == nil
961
+ def attrib.save; self end
962
+ node.add_attribute(attrib)
963
+ attrib.associations[:node].should == node
964
+ node.remove_all_attributes
965
+ attrib.associations.fetch(:node, 2).should == nil
966
+ end
967
+
968
+ it "should add a getter method if the :one_to_one option is true" do
969
+ @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
970
+ att = @c2.new(:id => 1234).attribute
971
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)']
972
+ att.should be_a_kind_of(@c1)
973
+ att.values.should == {}
974
+ end
975
+
976
+ it "should not add a setter method if the :one_to_one option is true and :read_only option is true" do
977
+ @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true, :read_only=>true
978
+ im = @c2.instance_methods.collect{|x| x.to_s}
979
+ im.should(include('attribute'))
980
+ im.should_not(include('attribute='))
981
+ end
982
+
983
+ it "should have the getter method raise an error if more than one record is found" do
984
+ @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
985
+ d = @c1.dataset
986
+ def d.fetch_rows(s); 2.times{yield Hash.new} end
987
+ proc{@c2.new(:id => 1234).attribute}.should raise_error(Sequel::Error)
988
+ end
989
+
990
+ it "should add a setter method if the :one_to_one option is true" do
991
+ @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
992
+ attrib = @c1.new(:id=>3)
993
+ d = @c1.dataset
994
+ def d.fetch_rows(s); yield({:id=>3}) end
995
+ @c2.new(:id => 1234).attribute = attrib
996
+ ['INSERT INTO attributes (node_id, id) VALUES (1234, 3)',
997
+ 'INSERT INTO attributes (id, node_id) VALUES (3, 1234)'].should(include(MODEL_DB.sqls.first))
998
+ MODEL_DB.sqls.last.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))'
999
+ MODEL_DB.sqls.length.should == 2
1000
+ @c2.new(:id => 1234).attribute.should == attrib
1001
+ MODEL_DB.sqls.clear
1002
+ attrib = @c1.load(:id=>3)
1003
+ @c2.new(:id => 1234).attribute = attrib
1004
+ MODEL_DB.sqls.length.should == 2
1005
+ MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id = 1234, id = 3|id = 3, node_id = 1234) WHERE \(id = 3\)/
1006
+ MODEL_DB.sqls.last.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))'
1007
+ end
1008
+
1009
+ it "should use a transaction in the setter method if the :one_to_one option is true" do
1010
+ @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
1011
+ @c2.use_transactions = true
1012
+ MODEL_DB.sqls.clear
1013
+ attrib = @c1.load(:id=>3)
1014
+ @c2.new(:id => 1234).attribute = attrib
1015
+ MODEL_DB.sqls.length.should == 4
1016
+ MODEL_DB.sqls.first.should == 'BEGIN'
1017
+ MODEL_DB.sqls[1].should =~ /UPDATE attributes SET (node_id = 1234, id = 3|id = 3, node_id = 1234) WHERE \(id = 3\)/
1018
+ MODEL_DB.sqls[2].should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))'
1019
+ MODEL_DB.sqls.last.should == 'COMMIT'
1020
+ end
1021
+
1022
+ it "should have the setter method for the :one_to_one option respect the :primary_key option" do
1023
+ @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true, :primary_key=>:xxx
1024
+ attrib = @c1.new(:id=>3)
1025
+ d = @c1.dataset
1026
+ def d.fetch_rows(s); yield({:id=>3}) end
1027
+ @c2.new(:id => 1234, :xxx=>5).attribute = attrib
1028
+ ['INSERT INTO attributes (node_id, id) VALUES (5, 3)',
1029
+ 'INSERT INTO attributes (id, node_id) VALUES (3, 5)'].should(include(MODEL_DB.sqls.first))
1030
+ MODEL_DB.sqls.last.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))'
1031
+ MODEL_DB.sqls.length.should == 2
1032
+ @c2.new(:id => 321, :xxx=>5).attribute.should == attrib
1033
+ MODEL_DB.sqls.clear
1034
+ attrib = @c1.load(:id=>3)
1035
+ @c2.new(:id => 621, :xxx=>5).attribute = attrib
1036
+ MODEL_DB.sqls.length.should == 2
1037
+ MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id = 5, id = 3|id = 3, node_id = 5) WHERE \(id = 3\)/
1038
+ MODEL_DB.sqls.last.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))'
1039
+ end
1040
+
1041
+ it "should raise an error if the one_to_one getter would be the same as the association name" do
1042
+ proc{@c2.one_to_many :song, :class => @c1, :one_to_one=>true}.should raise_error(Sequel::Error)
1043
+ end
1044
+
1045
+ it "should not create remove_ and remove_all methods if :one_to_one option is used" do
1046
+ @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
1047
+ @c2.new.should_not(respond_to(:remove_attribute))
1048
+ @c2.new.should_not(respond_to(:remove_all_attributes))
1049
+ end
1050
+
1051
+ it "should make non getter and setter methods private if :one_to_one option is used" do
1052
+ @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true do |ds| end
1053
+ meths = @c2.private_instance_methods.collect{|x| x.to_s}
1054
+ meths.should(include("attributes"))
1055
+ meths.should(include("add_attribute"))
1056
+ meths.should(include("attributes_dataset"))
1057
+ end
1058
+
1059
+ it "should call an _add_ method internally to add attributes" do
1060
+ @c2.one_to_many :attributes, :class => @c1
1061
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_add_attribute"))
1062
+ p = @c2.load(:id=>10)
1063
+ c = @c1.load(:id=>123)
1064
+ def p._add_attribute(x)
1065
+ @x = x
1066
+ end
1067
+ c.should_not_receive(:node_id=)
1068
+ p.add_attribute(c)
1069
+ p.instance_variable_get(:@x).should == c
1070
+ end
1071
+
1072
+ it "should call a _remove_ method internally to remove attributes" do
1073
+ @c2.one_to_many :attributes, :class => @c1
1074
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_remove_attribute"))
1075
+ p = @c2.load(:id=>10)
1076
+ c = @c1.load(:id=>123)
1077
+ def p._remove_attribute(x)
1078
+ @x = x
1079
+ end
1080
+ c.should_not_receive(:node_id=)
1081
+ p.remove_attribute(c)
1082
+ p.instance_variable_get(:@x).should == c
1083
+ end
1084
+
1085
+ it "should support (before|after)_(add|remove) callbacks" do
1086
+ h = []
1087
+ @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]
1088
+ @c2.class_eval do
1089
+ @@blah = h
1090
+ def _add_attribute(v)
1091
+ @@blah << 4
1092
+ end
1093
+ def _remove_attribute(v)
1094
+ @@blah << 5
1095
+ end
1096
+ def blah(x)
1097
+ @@blah << x.pk
1098
+ end
1099
+ def blahr(x)
1100
+ @@blah << 6
1101
+ end
1102
+ end
1103
+ p = @c2.load(:id=>10)
1104
+ c = @c1.load(:id=>123)
1105
+ h.should == []
1106
+ p.add_attribute(c)
1107
+ h.should == [10, -123, 123, 4, 3]
1108
+ p.remove_attribute(c)
1109
+ h.should == [10, -123, 123, 4, 3, 123, 5, 6]
1110
+ end
1111
+
1112
+ it "should support after_load association callback" do
1113
+ h = []
1114
+ @c2.one_to_many :attributes, :class => @c1, :after_load=>[proc{|x,y| h << [x.pk, y.collect{|z|z.pk}]}, :al]
1115
+ @c2.class_eval do
1116
+ @@blah = h
1117
+ def al(v)
1118
+ v.each{|x| @@blah << x.pk}
1119
+ end
1120
+ end
1121
+ @c1.class_eval do
1122
+ def @dataset.fetch_rows(sql)
1123
+ yield({:id=>20})
1124
+ yield({:id=>30})
1125
+ end
1126
+ end
1127
+ p = @c2.load(:id=>10, :parent_id=>20)
1128
+ attributes = p.attributes
1129
+ h.should == [[10, [20, 30]], 20, 30]
1130
+ attributes.collect{|a| a.pk}.should == [20, 30]
1131
+ end
1132
+
1133
+ 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
1134
+ p = @c2.load(:id=>10)
1135
+ c = @c1.load(:id=>123)
1136
+ @c2.one_to_many :attributes, :class => @c1, :before_add=>:ba, :before_remove=>:br
1137
+ p.should_receive(:ba).once.with(c).and_return(false)
1138
+ p.should_not_receive(:_add_attribute)
1139
+ p.should_not_receive(:_remove_attribute)
1140
+ p.associations[:attributes] = []
1141
+ proc{p.add_attribute(c)}.should raise_error(Sequel::Error)
1142
+ p.attributes.should == []
1143
+ p.associations[:attributes] = [c]
1144
+ p.should_receive(:br).once.with(c).and_return(false)
1145
+ proc{p.remove_attribute(c)}.should raise_error(Sequel::Error)
1146
+ p.attributes.should == [c]
1147
+ end
1148
+
1149
+ 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
1150
+ p = @c2.load(:id=>10)
1151
+ c = @c1.load(:id=>123)
1152
+ p.raise_on_save_failure = false
1153
+ @c2.one_to_many :attributes, :class => @c1, :before_add=>:ba, :before_remove=>:br
1154
+ p.should_receive(:ba).once.with(c).and_return(false)
1155
+ p.should_not_receive(:_add_attribute)
1156
+ p.should_not_receive(:_remove_attribute)
1157
+ p.associations[:attributes] = []
1158
+ p.add_attribute(c).should == nil
1159
+ p.attributes.should == []
1160
+ p.associations[:attributes] = [c]
1161
+ p.should_receive(:br).once.with(c).and_return(false)
1162
+ p.remove_attribute(c).should == nil
1163
+ p.attributes.should == [c]
1164
+ end
1165
+ end
1166
+
1167
+ describe Sequel::Model, "many_to_many" do
1168
+
1169
+ before(:each) do
1170
+ MODEL_DB.reset
1171
+
1172
+ @c1 = Class.new(Sequel::Model(:attributes)) do
1173
+ unrestrict_primary_key
1174
+ attr_accessor :yyy
1175
+ def self.name; 'Attribute'; end
1176
+ def self.to_s; 'Attribute'; end
1177
+ columns :id
1178
+ end
1179
+
1180
+ @c2 = Class.new(Sequel::Model(:nodes)) do
1181
+ unrestrict_primary_key
1182
+ attr_accessor :xxx
1183
+
1184
+ def self.name; 'Node'; end
1185
+ def self.to_s; 'Node'; end
1186
+ columns :id
1187
+ end
1188
+ @dataset = @c2.dataset
1189
+
1190
+ [@c1, @c2].each do |c|
1191
+ c.dataset.extend(Module.new {
1192
+ def fetch_rows(sql)
1193
+ @db << sql
1194
+ yield Hash.new
1195
+ end
1196
+ })
1197
+ end
1198
+ end
1199
+
1200
+ it "should use implicit key values and join table if omitted" do
1201
+ @c2.many_to_many :attributes, :class => @c1
1202
+
1203
+ n = @c2.new(:id => 1234)
1204
+ a = n.attributes_dataset
1205
+ a.should be_a_kind_of(Sequel::Dataset)
1206
+ a.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))'
1207
+ end
1208
+
1209
+ it "should use implicit class if omitted" do
1210
+ class ::Tag < Sequel::Model
1211
+ end
1212
+
1213
+ @c2.many_to_many :tags
1214
+
1215
+ n = @c2.new(:id => 1234)
1216
+ a = n.tags_dataset
1217
+ a.should be_a_kind_of(Sequel::Dataset)
1218
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN nodes_tags ON ((nodes_tags.tag_id = tags.id) AND (nodes_tags.node_id = 1234))'
1219
+ end
1220
+
1221
+ it "should use class inside module if given as a string" do
1222
+ module ::Historical
1223
+ class Tag < Sequel::Model
1224
+ end
1225
+ end
1226
+
1227
+ @c2.many_to_many :tags, :class=>'::Historical::Tag'
1228
+
1229
+ n = @c2.new(:id => 1234)
1230
+ a = n.tags_dataset
1231
+ a.should be_a_kind_of(Sequel::Dataset)
1232
+ a.sql.should == 'SELECT tags.* FROM tags INNER JOIN nodes_tags ON ((nodes_tags.tag_id = tags.id) AND (nodes_tags.node_id = 1234))'
1233
+ end
1234
+
1235
+ it "should use explicit key values and join table if given" do
1236
+ @c2.many_to_many :attributes, :class => @c1, :left_key => :nodeid, :right_key => :attributeid, :join_table => :attribute2node
1237
+
1238
+ n = @c2.new(:id => 1234)
1239
+ a = n.attributes_dataset
1240
+ a.should be_a_kind_of(Sequel::Dataset)
1241
+ a.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attribute2node ON ((attribute2node.attributeid = attributes.id) AND (attribute2node.nodeid = 1234))'
1242
+ end
1243
+
1244
+ it "should support a conditions option" do
1245
+ @c2.many_to_many :attributes, :class => @c1, :conditions => {:a=>32}
1246
+ n = @c2.new(:id => 1234)
1247
+ a = n.attributes_dataset
1248
+ a.should be_a_kind_of(Sequel::Dataset)
1249
+ 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)'
1250
+ @c2.many_to_many :attributes, :class => @c1, :conditions => ['a = ?', 32]
1251
+ n = @c2.new(:id => 1234)
1252
+ a = n.attributes_dataset
1253
+ a.should be_a_kind_of(Sequel::Dataset)
1254
+ 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)'
1255
+ end
1256
+
1257
+ it "should support an order option" do
1258
+ @c2.many_to_many :attributes, :class => @c1, :order => :blah
1259
+
1260
+ n = @c2.new(:id => 1234)
1261
+ a = n.attributes_dataset
1262
+ a.should be_a_kind_of(Sequel::Dataset)
1263
+ 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'
1264
+ end
1265
+
1266
+ it "should support an array for the order option" do
1267
+ @c2.many_to_many :attributes, :class => @c1, :order => [:blah1, :blah2]
1268
+
1269
+ n = @c2.new(:id => 1234)
1270
+ a = n.attributes_dataset
1271
+ a.should be_a_kind_of(Sequel::Dataset)
1272
+ 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'
1273
+ end
1274
+
1275
+ it "should support :left_primary_key and :right_primary_key options" do
1276
+ @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy
1277
+ @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))'
1278
+ end
1279
+
1280
+ it "should support a select option" do
1281
+ @c2.many_to_many :attributes, :class => @c1, :select => :blah
1282
+
1283
+ n = @c2.new(:id => 1234)
1284
+ a = n.attributes_dataset
1285
+ a.should be_a_kind_of(Sequel::Dataset)
1286
+ a.sql.should == 'SELECT blah FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))'
1287
+ end
1288
+
1289
+ it "should support an array for the select option" do
1290
+ @c2.many_to_many :attributes, :class => @c1, :select => [:attributes.*, :attribute_nodes__blah2]
1291
+
1292
+ n = @c2.new(:id => 1234)
1293
+ a = n.attributes_dataset
1294
+ a.should be_a_kind_of(Sequel::Dataset)
1295
+ 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))'
1296
+ end
1297
+
1298
+ it "should accept a block" do
1299
+ @c2.many_to_many :attributes, :class => @c1 do |ds|
1300
+ ds.filter(:xxx => @xxx)
1301
+ end
1302
+
1303
+ n = @c2.new(:id => 1234)
1304
+ n.xxx = 555
1305
+ a = n.attributes
1306
+ a.should be_a_kind_of(Array)
1307
+ a.size.should == 1
1308
+ a.first.should be_a_kind_of(@c1)
1309
+ 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)'
1310
+ end
1311
+
1312
+ it "should allow the :order option while accepting a block" do
1313
+ @c2.many_to_many :attributes, :class => @c1, :order=>[:blah1, :blah2] do |ds|
1314
+ ds.filter(:xxx => @xxx)
1315
+ end
1316
+
1317
+ n = @c2.new(:id => 1234)
1318
+ n.xxx = 555
1319
+ a = n.attributes
1320
+ a.should be_a_kind_of(Array)
1321
+ a.size.should == 1
1322
+ a.first.should be_a_kind_of(@c1)
1323
+ 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'
1324
+ end
1325
+
1326
+ it "should have the block argument affect the _dataset method" do
1327
+ @c2.many_to_many :attributes, :class => @c1 do |ds|
1328
+ ds.filter(:xxx => 456)
1329
+ end
1330
+ @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)'
1331
+ end
1332
+
1333
+ it "should support a :dataset option that is used instead of the default" do
1334
+ c1 = @c1
1335
+ @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|
1336
+ ds.filter(:xxx => @xxx)
1337
+ end
1338
+
1339
+ n = @c2.new(:id => 1234)
1340
+ n.xxx = 555
1341
+ n.attributes_dataset.sql.should == 'SELECT * FROM attributes NATURAL JOIN an WHERE ((an.nodeid = 1234) AND (xxx = 555)) ORDER BY a LIMIT 10'
1342
+ a = n.attributes
1343
+ a.should be_a_kind_of(Array)
1344
+ a.size.should == 1
1345
+ a.first.should be_a_kind_of(@c1)
1346
+ MODEL_DB.sqls.first.should == 'SELECT * FROM attributes NATURAL JOIN an WHERE ((an.nodeid = 1234) AND (xxx = 555)) ORDER BY a LIMIT 10'
1347
+ end
1348
+
1349
+ it "should support a :limit option" do
1350
+ @c2.many_to_many :attributes, :class => @c1 , :limit=>10
1351
+ @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'
1352
+ @c2.many_to_many :attributes, :class => @c1 , :limit=>[10, 10]
1353
+ @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'
1354
+ end
1355
+
1356
+ it "should have the :eager option affect the _dataset method" do
1357
+ @c2.many_to_many :attributes, :class => @c2 , :eager=>:attributes
1358
+ @c2.new(:id => 1234).attributes_dataset.opts[:eager].should == {:attributes=>nil}
1359
+ end
1360
+
1361
+ it "should define an add_ method" do
1362
+ @c2.many_to_many :attributes, :class => @c1
1363
+
1364
+ n = @c2.new(:id => 1234)
1365
+ a = @c1.new(:id => 2345)
1366
+ a.should == n.add_attribute(a)
1367
+ ['INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 2345)',
1368
+ 'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (2345, 1234)'
1369
+ ].should(include(MODEL_DB.sqls.first))
1370
+ end
1371
+
1372
+ it "should define a remove_ method" do
1373
+ @c2.many_to_many :attributes, :class => @c1
1374
+
1375
+ n = @c2.new(:id => 1234)
1376
+ a = @c1.new(:id => 2345)
1377
+ a.should == n.remove_attribute(a)
1378
+ MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 2345))'
1379
+ end
1380
+
1381
+ it "should have the add_ method respect the :left_primary_key and :right_primary_key options" do
1382
+ @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy
1383
+
1384
+ n = @c2.new(:id => 1234, :xxx=>5)
1385
+ a = @c1.new(:id => 2345, :yyy=>8)
1386
+ a.should == n.add_attribute(a)
1387
+ ['INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (5, 8)',
1388
+ 'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (8, 5)'
1389
+ ].should(include(MODEL_DB.sqls.first))
1390
+ end
1391
+
1392
+ it "should have the remove_ method respect the :left_primary_key and :right_primary_key options" do
1393
+ @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy
1394
+
1395
+ n = @c2.new(:id => 1234, :xxx=>5)
1396
+ a = @c1.new(:id => 2345, :yyy=>8)
1397
+ a.should == n.remove_attribute(a)
1398
+ MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 5) AND (attribute_id = 8))'
1399
+ end
1400
+
1401
+ it "should raise an error if the model object doesn't have a valid primary key" do
1402
+ @c2.many_to_many :attributes, :class => @c1
1403
+ a = @c2.new
1404
+ n = @c1.load(:id=>123)
1405
+ proc{a.attributes_dataset}.should raise_error(Sequel::Error)
1406
+ proc{a.attributes}.should raise_error(Sequel::Error)
1407
+ proc{a.add_attribute(n)}.should raise_error(Sequel::Error)
1408
+ proc{a.remove_attribute(n)}.should raise_error(Sequel::Error)
1409
+ proc{a.remove_all_attributes}.should raise_error(Sequel::Error)
1410
+ end
1411
+
1412
+ it "should raise an error if trying to add/remove a model object that doesn't have a valid primary key" do
1413
+ @c2.many_to_many :attributes, :class => @c1
1414
+ n = @c1.new
1415
+ a = @c2.load(:id=>123)
1416
+ proc{a.add_attribute(n)}.should raise_error(Sequel::Error)
1417
+ proc{a.remove_attribute(n)}.should raise_error(Sequel::Error)
1418
+ end
1419
+
1420
+ it "should provide an array with all members of the association" do
1421
+ @c2.many_to_many :attributes, :class => @c1
1422
+
1423
+ n = @c2.new(:id => 1234)
1424
+ atts = n.attributes
1425
+ atts.should be_a_kind_of(Array)
1426
+ atts.size.should == 1
1427
+ atts.first.should be_a_kind_of(@c1)
1428
+
1429
+ 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))'
1430
+ end
1431
+
1432
+ it "should set cached instance variable when accessed" do
1433
+ @c2.many_to_many :attributes, :class => @c1
1434
+
1435
+ n = @c2.new(:id => 1234)
1436
+ MODEL_DB.reset
1437
+ n.associations.include?(:attributes).should == false
1438
+ atts = n.attributes
1439
+ atts.should == n.associations[:attributes]
1440
+ MODEL_DB.sqls.length.should == 1
1441
+ end
1442
+
1443
+ it "should use cached instance variable if available" do
1444
+ @c2.many_to_many :attributes, :class => @c1
1445
+
1446
+ n = @c2.new(:id => 1234)
1447
+ MODEL_DB.reset
1448
+ n.associations[:attributes] = 42
1449
+ n.attributes.should == 42
1450
+ MODEL_DB.sqls.should == []
1451
+ end
1452
+
1453
+ it "should not use cached instance variable if asked to reload" do
1454
+ @c2.many_to_many :attributes, :class => @c1
1455
+
1456
+ n = @c2.new(:id => 1234)
1457
+ MODEL_DB.reset
1458
+ n.associations[:attributes] = 42
1459
+ n.attributes(true).should_not == 42
1460
+ MODEL_DB.sqls.length.should == 1
1461
+ end
1462
+
1463
+ it "should add item to cached instance variable if it exists when calling add_" do
1464
+ @c2.many_to_many :attributes, :class => @c1
1465
+
1466
+ n = @c2.new(:id => 1234)
1467
+ att = @c1.new(:id => 345)
1468
+ MODEL_DB.reset
1469
+ a = []
1470
+ n.associations[:attributes] = a
1471
+ n.add_attribute(att)
1472
+ a.should == [att]
1473
+ end
1474
+
1475
+ it "should add item to reciprocal cached instance variable if it exists when calling add_" do
1476
+ @c2.many_to_many :attributes, :class => @c1
1477
+ @c1.many_to_many :nodes, :class => @c2
1478
+
1479
+ n = @c2.new(:id => 1234)
1480
+ att = @c1.new(:id => 345)
1481
+ att.associations[:nodes] = []
1482
+ n.add_attribute(att)
1483
+ att.nodes.should == [n]
1484
+ end
1485
+
1486
+ it "should remove item from cached instance variable if it exists when calling remove_" do
1487
+ @c2.many_to_many :attributes, :class => @c1
1488
+
1489
+ n = @c2.new(:id => 1234)
1490
+ att = @c1.new(:id => 345)
1491
+ MODEL_DB.reset
1492
+ a = [att]
1493
+ n.associations[:attributes] = a
1494
+ n.remove_attribute(att)
1495
+ a.should == []
1496
+ end
1497
+
1498
+ it "should remove item from reciprocal cached instance variable if it exists when calling remove_" do
1499
+ @c2.many_to_many :attributes, :class => @c1
1500
+ @c1.many_to_many :nodes, :class => @c2
1501
+
1502
+ n = @c2.new(:id => 1234)
1503
+ att = @c1.new(:id => 345)
1504
+ att.associations[:nodes] = [n]
1505
+ n.remove_attribute(att)
1506
+ att.nodes.should == []
1507
+ end
1508
+
1509
+ it "should not create the add_, remove_, or remove_all_ methods if :read_only option is used" do
1510
+ @c2.many_to_many :attributes, :class => @c1, :read_only=>true
1511
+ im = @c2.instance_methods.collect{|x| x.to_s}
1512
+ im.should(include('attributes'))
1513
+ im.should(include('attributes_dataset'))
1514
+ im.should_not(include('add_attribute'))
1515
+ im.should_not(include('remove_attribute'))
1516
+ im.should_not(include('remove_all_attributes'))
1517
+ end
1518
+
1519
+ it "should not add associations methods directly to class" do
1520
+ @c2.many_to_many :attributes, :class => @c1
1521
+ im = @c2.instance_methods.collect{|x| x.to_s}
1522
+ im.should(include('attributes'))
1523
+ im.should(include('attributes_dataset'))
1524
+ im.should(include('add_attribute'))
1525
+ im.should(include('remove_attribute'))
1526
+ im.should(include('remove_all_attributes'))
1527
+ im2 = @c2.instance_methods(false).collect{|x| x.to_s}
1528
+ im2.should_not(include('attributes'))
1529
+ im2.should_not(include('attributes_dataset'))
1530
+ im2.should_not(include('add_attribute'))
1531
+ im2.should_not(include('remove_attribute'))
1532
+ im2.should_not(include('remove_all_attributes'))
1533
+ end
1534
+
1535
+ deprec_specify "should have has_and_belongs_to_many alias" do
1536
+ @c2.has_and_belongs_to_many :attributes, :class => @c1
1537
+
1538
+ n = @c2.new(:id => 1234)
1539
+ a = n.attributes_dataset
1540
+ a.should be_a_kind_of(Sequel::Dataset)
1541
+ a.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234))'
1542
+ end
1543
+
1544
+ it "should have an remove_all_ method that removes all associations" do
1545
+ @c2.many_to_many :attributes, :class => @c1
1546
+ @c2.new(:id => 1234).remove_all_attributes
1547
+ MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE (node_id = 1234)'
1548
+ end
1549
+
1550
+ it "should have the remove_all_ method respect the :left_primary_key option" do
1551
+ @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx
1552
+ @c2.new(:id => 1234, :xxx=>5).remove_all_attributes
1553
+ MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE (node_id = 5)'
1554
+ end
1555
+
1556
+ it "remove_all should set the cached instance variable to []" do
1557
+ @c2.many_to_many :attributes, :class => @c1
1558
+ node = @c2.new(:id => 1234)
1559
+ node.remove_all_attributes
1560
+ node.associations[:attributes].should == []
1561
+ end
1562
+
1563
+ it "remove_all should return the array of previously associated items if the cached instance variable exists" do
1564
+ @c2.many_to_many :attributes, :class => @c1
1565
+ attrib = @c1.new(:id=>3)
1566
+ node = @c2.new(:id => 1234)
1567
+ d = @c1.dataset
1568
+ def d.fetch_rows(s); end
1569
+ node.attributes.should == []
1570
+ node.add_attribute(attrib)
1571
+ node.associations[:attributes].should == [attrib]
1572
+ node.remove_all_attributes.should == [attrib]
1573
+ end
1574
+
1575
+ it "remove_all should return nil if the cached instance variable does not exist" do
1576
+ @c2.many_to_many :attributes, :class => @c1
1577
+ @c2.new(:id => 1234).remove_all_attributes.should == nil
1578
+ end
1579
+
1580
+ it "remove_all should remove the current item from all reciprocal instance varaibles if it cached instance variable exists" do
1581
+ @c2.many_to_many :attributes, :class => @c1
1582
+ @c1.many_to_many :nodes, :class => @c2
1583
+ d = @c1.dataset
1584
+ def d.fetch_rows(s); end
1585
+ d = @c2.dataset
1586
+ def d.fetch_rows(s); end
1587
+ attrib = @c1.new(:id=>3)
1588
+ node = @c2.new(:id => 1234)
1589
+ node.attributes.should == []
1590
+ attrib.nodes.should == []
1591
+ node.add_attribute(attrib)
1592
+ attrib.associations[:nodes].should == [node]
1593
+ node.remove_all_attributes
1594
+ attrib.associations[:nodes].should == []
1595
+ end
1596
+
1597
+ it "should call an _add_ method internally to add attributes" do
1598
+ @c2.many_to_many :attributes, :class => @c1
1599
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_add_attribute"))
1600
+ p = @c2.load(:id=>10)
1601
+ c = @c1.load(:id=>123)
1602
+ def p._add_attribute(x)
1603
+ @x = x
1604
+ end
1605
+ p.add_attribute(c)
1606
+ p.instance_variable_get(:@x).should == c
1607
+ MODEL_DB.sqls.should == []
1608
+ end
1609
+
1610
+ it "should call a _remove_ method internally to remove attributes" do
1611
+ @c2.many_to_many :attributes, :class => @c1
1612
+ @c2.private_instance_methods.collect{|x| x.to_s}.sort.should(include("_remove_attribute"))
1613
+ p = @c2.load(:id=>10)
1614
+ c = @c1.load(:id=>123)
1615
+ def p._remove_attribute(x)
1616
+ @x = x
1617
+ end
1618
+ p.remove_attribute(c)
1619
+ p.instance_variable_get(:@x).should == c
1620
+ MODEL_DB.sqls.should == []
1621
+ end
1622
+
1623
+ it "should support (before|after)_(add|remove) callbacks" do
1624
+ h = []
1625
+ @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]
1626
+ @c2.class_eval do
1627
+ @@blah = h
1628
+ def _add_attribute(v)
1629
+ @@blah << 4
1630
+ end
1631
+ def _remove_attribute(v)
1632
+ @@blah << 5
1633
+ end
1634
+ def blah(x)
1635
+ @@blah << x.pk
1636
+ end
1637
+ def blahr(x)
1638
+ @@blah << 6
1639
+ end
1640
+ end
1641
+ p = @c2.load(:id=>10)
1642
+ c = @c1.load(:id=>123)
1643
+ h.should == []
1644
+ p.add_attribute(c)
1645
+ h.should == [10, -123, 123, 4, 3]
1646
+ p.remove_attribute(c)
1647
+ h.should == [10, -123, 123, 4, 3, 123, 5, 6]
1648
+ end
1649
+
1650
+ it "should support after_load association callback" do
1651
+ h = []
1652
+ @c2.many_to_many :attributes, :class => @c1, :after_load=>[proc{|x,y| h << [x.pk, y.collect{|z|z.pk}]}, :al]
1653
+ @c2.class_eval do
1654
+ @@blah = h
1655
+ def al(v)
1656
+ v.each{|x| @@blah << x.pk}
1657
+ end
1658
+ end
1659
+ @c1.class_eval do
1660
+ def @dataset.fetch_rows(sql)
1661
+ yield({:id=>20})
1662
+ yield({:id=>30})
1663
+ end
1664
+ end
1665
+ p = @c2.load(:id=>10, :parent_id=>20)
1666
+ attributes = p.attributes
1667
+ h.should == [[10, [20, 30]], 20, 30]
1668
+ attributes.collect{|a| a.pk}.should == [20, 30]
1669
+ end
1670
+
1671
+ 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
1672
+ p = @c2.load(:id=>10)
1673
+ c = @c1.load(:id=>123)
1674
+ @c2.many_to_many :attributes, :class => @c1, :before_add=>:ba, :before_remove=>:br
1675
+ p.should_receive(:ba).once.with(c).and_return(false)
1676
+ p.should_not_receive(:_add_attribute)
1677
+ p.should_not_receive(:_remove_attribute)
1678
+ p.associations[:attributes] = []
1679
+ p.raise_on_save_failure = true
1680
+ proc{p.add_attribute(c)}.should raise_error(Sequel::Error)
1681
+ p.attributes.should == []
1682
+ p.associations[:attributes] = [c]
1683
+ p.should_receive(:br).once.with(c).and_return(false)
1684
+ proc{p.remove_attribute(c)}.should raise_error(Sequel::Error)
1685
+ p.attributes.should == [c]
1686
+ end
1687
+
1688
+ 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
1689
+ p = @c2.load(:id=>10)
1690
+ c = @c1.load(:id=>123)
1691
+ p.raise_on_save_failure = false
1692
+ @c2.many_to_many :attributes, :class => @c1, :before_add=>:ba, :before_remove=>:br
1693
+ p.should_receive(:ba).once.with(c).and_return(false)
1694
+ p.should_not_receive(:_add_attribute)
1695
+ p.should_not_receive(:_remove_attribute)
1696
+ p.associations[:attributes] = []
1697
+ p.add_attribute(c).should == nil
1698
+ p.attributes.should == []
1699
+ p.associations[:attributes] = [c]
1700
+ p.should_receive(:br).once.with(c).and_return(false)
1701
+ p.remove_attribute(c).should == nil
1702
+ p.attributes.should == [c]
1703
+ end
1704
+
1705
+ it "should support a :uniq option that removes duplicates from the association" do
1706
+ h = []
1707
+ @c2.many_to_many :attributes, :class => @c1, :uniq=>true
1708
+ @c1.class_eval do
1709
+ def @dataset.fetch_rows(sql)
1710
+ yield({:id=>20})
1711
+ yield({:id=>30})
1712
+ yield({:id=>20})
1713
+ yield({:id=>30})
1714
+ end
1715
+ end
1716
+ @c2.load(:id=>10, :parent_id=>20).attributes.should == [@c1.load(:id=>20), @c1.load(:id=>30)]
1717
+ end
1718
+ end
1719
+
1720
+ describe Sequel::Model, " association reflection methods" do
1721
+ before do
1722
+ MODEL_DB.reset
1723
+ @c1 = Class.new(Sequel::Model(:nodes)) do
1724
+ def self.name; 'Node'; end
1725
+ def self.to_s; 'Node'; end
1726
+ end
1727
+ end
1728
+
1729
+ it "#all_association_reflections should include all association reflection hashes" do
1730
+ @c1.all_association_reflections.should == []
1731
+
1732
+ @c1.associate :many_to_one, :parent, :class => @c1
1733
+ @c1.all_association_reflections.collect{|v| v[:name]}.should == [:parent]
1734
+ @c1.all_association_reflections.collect{|v| v[:type]}.should == [:many_to_one]
1735
+ @c1.all_association_reflections.collect{|v| v[:class]}.should == [@c1]
1736
+
1737
+ @c1.associate :one_to_many, :children, :class => @c1
1738
+ @c1.all_association_reflections.sort_by{|x|x[:name].to_s}
1739
+ @c1.all_association_reflections.sort_by{|x|x[:name].to_s}.collect{|v| v[:name]}.should == [:children, :parent]
1740
+ @c1.all_association_reflections.sort_by{|x|x[:name].to_s}.collect{|v| v[:type]}.should == [:one_to_many, :many_to_one]
1741
+ @c1.all_association_reflections.sort_by{|x|x[:name].to_s}.collect{|v| v[:class]}.should == [@c1, @c1]
1742
+ end
1743
+
1744
+ it "#association_reflection should return nil for nonexistent association" do
1745
+ @c1.association_reflection(:blah).should == nil
1746
+ end
1747
+
1748
+ it "#association_reflection should return association reflection hash if association exists" do
1749
+ @c1.associate :many_to_one, :parent, :class => @c1
1750
+ @c1.association_reflection(:parent).should be_a_kind_of(Sequel::Model::Associations::AssociationReflection)
1751
+ @c1.association_reflection(:parent)[:name].should == :parent
1752
+ @c1.association_reflection(:parent)[:type].should == :many_to_one
1753
+ @c1.association_reflection(:parent)[:class].should == @c1
1754
+
1755
+ @c1.associate :one_to_many, :children, :class => @c1
1756
+ @c1.association_reflection(:children).should be_a_kind_of(Sequel::Model::Associations::AssociationReflection)
1757
+ @c1.association_reflection(:children)[:name].should == :children
1758
+ @c1.association_reflection(:children)[:type].should == :one_to_many
1759
+ @c1.association_reflection(:children)[:class].should == @c1
1760
+ end
1761
+
1762
+ it "#associations should include all association names" do
1763
+ @c1.associations.should == []
1764
+ @c1.associate :many_to_one, :parent, :class => @c1
1765
+ @c1.associations.should == [:parent]
1766
+ @c1.associate :one_to_many, :children, :class => @c1
1767
+ @c1.associations.sort_by{|x|x.to_s}.should == [:children, :parent]
1768
+ end
1769
+
1770
+ it "association reflections should be copied upon subclasing" do
1771
+ @c1.associate :many_to_one, :parent, :class => @c1
1772
+ c = Class.new(@c1)
1773
+ @c1.associations.should == [:parent]
1774
+ c.associations.should == [:parent]
1775
+ c.associate :many_to_one, :parent2, :class => @c1
1776
+ @c1.associations.should == [:parent]
1777
+ c.associations.sort_by{|x| x.to_s}.should == [:parent, :parent2]
1778
+ c.instance_methods.map{|x| x.to_s}.should include('parent')
1779
+ end
1780
+ end