epugh-sequel 0.0.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 (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