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