sequel 3.9.0 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/CHANGELOG +56 -0
  2. data/README.rdoc +1 -1
  3. data/Rakefile +1 -1
  4. data/doc/advanced_associations.rdoc +7 -10
  5. data/doc/release_notes/3.10.0.txt +286 -0
  6. data/lib/sequel/adapters/do/mysql.rb +4 -0
  7. data/lib/sequel/adapters/jdbc.rb +5 -0
  8. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  9. data/lib/sequel/adapters/jdbc/oracle.rb +30 -0
  10. data/lib/sequel/adapters/shared/mssql.rb +23 -9
  11. data/lib/sequel/adapters/shared/mysql.rb +12 -1
  12. data/lib/sequel/adapters/shared/postgres.rb +7 -18
  13. data/lib/sequel/adapters/shared/sqlite.rb +5 -0
  14. data/lib/sequel/adapters/sqlite.rb +5 -0
  15. data/lib/sequel/connection_pool/single.rb +3 -3
  16. data/lib/sequel/database.rb +3 -2
  17. data/lib/sequel/dataset.rb +6 -5
  18. data/lib/sequel/dataset/convenience.rb +3 -3
  19. data/lib/sequel/dataset/query.rb +13 -0
  20. data/lib/sequel/dataset/sql.rb +31 -1
  21. data/lib/sequel/extensions/schema_dumper.rb +3 -3
  22. data/lib/sequel/model.rb +8 -6
  23. data/lib/sequel/model/associations.rb +144 -102
  24. data/lib/sequel/model/base.rb +21 -1
  25. data/lib/sequel/model/plugins.rb +3 -1
  26. data/lib/sequel/plugins/association_dependencies.rb +14 -7
  27. data/lib/sequel/plugins/caching.rb +4 -0
  28. data/lib/sequel/plugins/composition.rb +138 -0
  29. data/lib/sequel/plugins/identity_map.rb +2 -2
  30. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  31. data/lib/sequel/plugins/nested_attributes.rb +3 -2
  32. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  33. data/lib/sequel/plugins/typecast_on_load.rb +16 -5
  34. data/lib/sequel/sql.rb +18 -1
  35. data/lib/sequel/version.rb +1 -1
  36. data/spec/adapters/mssql_spec.rb +4 -0
  37. data/spec/adapters/mysql_spec.rb +4 -0
  38. data/spec/adapters/postgres_spec.rb +55 -5
  39. data/spec/core/database_spec.rb +5 -3
  40. data/spec/core/dataset_spec.rb +86 -15
  41. data/spec/core/expression_filters_spec.rb +23 -6
  42. data/spec/extensions/association_dependencies_spec.rb +24 -5
  43. data/spec/extensions/association_proxies_spec.rb +3 -0
  44. data/spec/extensions/composition_spec.rb +194 -0
  45. data/spec/extensions/identity_map_spec.rb +16 -0
  46. data/spec/extensions/nested_attributes_spec.rb +44 -1
  47. data/spec/extensions/rcte_tree_spec.rb +205 -0
  48. data/spec/extensions/schema_dumper_spec.rb +6 -0
  49. data/spec/extensions/spec_helper.rb +6 -0
  50. data/spec/extensions/typecast_on_load_spec.rb +9 -0
  51. data/spec/extensions/validation_helpers_spec.rb +5 -5
  52. data/spec/integration/dataset_test.rb +13 -9
  53. data/spec/integration/eager_loader_test.rb +56 -1
  54. data/spec/integration/model_test.rb +8 -0
  55. data/spec/integration/plugin_test.rb +270 -0
  56. data/spec/integration/schema_test.rb +1 -1
  57. data/spec/model/associations_spec.rb +541 -118
  58. data/spec/model/eager_loading_spec.rb +24 -3
  59. data/spec/model/record_spec.rb +34 -0
  60. metadata +9 -2
@@ -43,5 +43,8 @@ describe "Sequel::Plugins::AssociationProxies" do
43
43
  it "should not return a proxy object for associations that do not return an array" do
44
44
  Item.many_to_one :tag
45
45
  proc{@i.tag.filter(:a=>1)}.should raise_error(NoMethodError)
46
+
47
+ Tag.one_to_one :item
48
+ proc{Tag.load(:id=>1, :item_id=>2).item.filter(:a=>1)}.should raise_error(NoMethodError)
46
49
  end
47
50
  end
@@ -0,0 +1,194 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ require 'yaml'
4
+ require 'json'
5
+
6
+ describe "Serialization plugin" do
7
+ before do
8
+ @c = Class.new(Sequel::Model(:items))
9
+ @c.plugin :composition
10
+ @c.columns :id, :year, :month, :day
11
+ @o = @c.load(:id=>1, :year=>1, :month=>2, :day=>3)
12
+ MODEL_DB.reset
13
+ end
14
+
15
+ it ".composition should add compositions" do
16
+ @o.should_not respond_to(:date)
17
+ @c.composition :date, :mapping=>[:year, :month, :day]
18
+ @o.date.should == Date.new(1, 2, 3)
19
+ end
20
+
21
+ it "loading the plugin twice should not remove existing compositions" do
22
+ @c.composition :date, :mapping=>[:year, :month, :day]
23
+ @c.plugin :composition
24
+ @c.compositions.keys.should == [:date]
25
+ end
26
+
27
+ it ".composition should raise an error if :composer and :decomposer options are not present and :mapping option is not provided" do
28
+ proc{@c.composition :date}.should raise_error(Sequel::Error)
29
+ proc{@c.composition :date, :composer=>proc{}, :decomposer=>proc{}}.should_not raise_error
30
+ proc{@c.composition :date, :mapping=>[]}.should_not raise_error
31
+ end
32
+
33
+ it ".compositions should return the reflection hash of compositions" do
34
+ @c.compositions.should == {}
35
+ @c.composition :date, :mapping=>[:year, :month, :day]
36
+ @c.compositions.keys.should == [:date]
37
+ r = @c.compositions.values.first
38
+ r[:mapping].should == [:year, :month, :day]
39
+ r[:composer].should be_a_kind_of(Proc)
40
+ r[:decomposer].should be_a_kind_of(Proc)
41
+ end
42
+
43
+ it "#compositions should be a hash of cached values of compositions" do
44
+ @o.compositions.should == {}
45
+ @c.composition :date, :mapping=>[:year, :month, :day]
46
+ @o.date
47
+ @o.compositions.should == {:date=>Date.new(1, 2, 3)}
48
+ end
49
+
50
+ it "should work with custom :composer and :decomposer options" do
51
+ @c.composition :date, :composer=>proc{Date.new(year+1, month+2, day+3)}, :decomposer=>proc{[:year, :month, :day].each{|s| self.send("#{s}=", date.send(s) * 2)}}
52
+ @o.date.should == Date.new(2, 4, 6)
53
+ @o.save
54
+ MODEL_DB.sqls.last.should include("year = 4")
55
+ MODEL_DB.sqls.last.should include("month = 8")
56
+ MODEL_DB.sqls.last.should include("day = 12")
57
+ end
58
+
59
+ it "should allow call super in composition getter and setter method definition in class" do
60
+ @c.composition :date, :mapping=>[:year, :month, :day]
61
+ @c.class_eval do
62
+ def date
63
+ super + 1
64
+ end
65
+ def date=(v)
66
+ super(v - 3)
67
+ end
68
+ end
69
+ @o.date.should == Date.new(1, 2, 4)
70
+ @o.compositions[:date].should == Date.new(1, 2, 3)
71
+ @o.date = Date.new(1, 3, 5)
72
+ @o.compositions[:date].should == Date.new(1, 3, 2)
73
+ @o.date.should == Date.new(1, 3, 3)
74
+ end
75
+
76
+ it "should mark the object as modified whenever the composition is set" do
77
+ @c.composition :date, :mapping=>[:year, :month, :day]
78
+ @o.modified?.should == false
79
+ @o.date = Date.new(3, 4, 5)
80
+ @o.modified?.should == true
81
+ end
82
+
83
+ it "should only decompose existing compositions" do
84
+ called = false
85
+ @c.composition :date, :composer=>proc{}, :decomposer=>proc{called = true}
86
+ called.should == false
87
+ @o.save
88
+ called.should == false
89
+ @o.date = Date.new(1,2,3)
90
+ called.should == false
91
+ @o.save_changes
92
+ called.should == true
93
+ end
94
+
95
+ it "should clear compositions cache when reloading" do
96
+ @c.composition :date, :composer=>proc{}, :decomposer=>proc{called = true}
97
+ @o.date = Date.new(3, 4, 5)
98
+ @o.reload
99
+ @o.compositions.should == {}
100
+ end
101
+
102
+ it "should instantiate compositions lazily" do
103
+ @c.composition :date, :mapping=>[:year, :month, :day]
104
+ @o.compositions.should == {}
105
+ @o.date
106
+ @o.compositions.should == {:date=>Date.new(1,2,3)}
107
+ end
108
+
109
+ it "should cache value of composition" do
110
+ times = 0
111
+ @c.composition :date, :composer=>proc{times+=1}, :decomposer=>proc{called = true}
112
+ times.should == 0
113
+ @o.date
114
+ times.should == 1
115
+ @o.date
116
+ times.should == 1
117
+ end
118
+
119
+ it ":class option should take an string, symbol, or class" do
120
+ @c.composition :date1, :class=>'Date', :mapping=>[:year, :month, :day]
121
+ @c.composition :date2, :class=>:Date, :mapping=>[:year, :month, :day]
122
+ @c.composition :date3, :class=>Date, :mapping=>[:year, :month, :day]
123
+ @o.date1.should == Date.new(1, 2, 3)
124
+ @o.date2.should == Date.new(1, 2, 3)
125
+ @o.date3.should == Date.new(1, 2, 3)
126
+ end
127
+
128
+ it ":mapping option should work with a single array of symbols" do
129
+ c = Class.new do
130
+ def initialize(y, m)
131
+ @y, @m = y, m
132
+ end
133
+ def year
134
+ @y * 2
135
+ end
136
+ def month
137
+ @m * 3
138
+ end
139
+ end
140
+ @c.composition :date, :class=>c, :mapping=>[:year, :month]
141
+ @o.date.year.should == 2
142
+ @o.date.month.should == 6
143
+ @o.date = c.new(3, 4)
144
+ @o.save
145
+ MODEL_DB.sqls.last.should include("year = 6")
146
+ MODEL_DB.sqls.last.should include("month = 12")
147
+ end
148
+
149
+ it ":mapping option should work with an array of two pairs of symbols" do
150
+ c = Class.new do
151
+ def initialize(y, m)
152
+ @y, @m = y, m
153
+ end
154
+ def y
155
+ @y * 2
156
+ end
157
+ def m
158
+ @m * 3
159
+ end
160
+ end
161
+ @c.composition :date, :class=>c, :mapping=>[[:year, :y], [:month, :m]]
162
+ @o.date.y.should == 2
163
+ @o.date.m.should == 6
164
+ @o.date = c.new(3, 4)
165
+ @o.save
166
+ MODEL_DB.sqls.last.should include("year = 6")
167
+ MODEL_DB.sqls.last.should include("month = 12")
168
+ end
169
+
170
+ it ":mapping option :composer should return nil if all values are nil" do
171
+ @c.composition :date, :mapping=>[:year, :month, :day]
172
+ @c.new.date.should == nil
173
+ end
174
+
175
+ it ":mapping option :decomposer should set all related fields to nil if nil" do
176
+ @c.composition :date, :mapping=>[:year, :month, :day]
177
+ @o.date = nil
178
+ @o.save
179
+ MODEL_DB.sqls.last.should include("year = NULL")
180
+ MODEL_DB.sqls.last.should include("month = NULL")
181
+ MODEL_DB.sqls.last.should include("day = NULL")
182
+ end
183
+
184
+ it "should work correctly with subclasses" do
185
+ @c.composition :date, :mapping=>[:year, :month, :day]
186
+ c = Class.new(@c)
187
+ o = c.load(:id=>1, :year=>1, :month=>2, :day=>3)
188
+ o.date.should == Date.new(1, 2, 3)
189
+ o.save
190
+ MODEL_DB.sqls.last.should include("year = 1")
191
+ MODEL_DB.sqls.last.should include("month = 2")
192
+ MODEL_DB.sqls.last.should include("day = 3")
193
+ end
194
+ end
@@ -155,6 +155,22 @@ describe "Sequel::Plugins::IdentityMap" do
155
155
  end
156
156
  end
157
157
 
158
+ it "should not use the identity map as a lookup cache for a one_to_one association" do
159
+ c = @c2
160
+ @c2.one_to_one :artist, :class=>@c1, :key=>:artist_id
161
+ @c.with_identity_map do
162
+ MODEL_DB.sqls.length.should == 0
163
+ o = @c2.load(:id=>2)
164
+ a = o.artist
165
+ a.should be_a_kind_of(@c1)
166
+ MODEL_DB.sqls.length.should == 1
167
+ o.reload
168
+ MODEL_DB.sqls.length.should == 2
169
+ o.artist.should == a
170
+ MODEL_DB.sqls.length.should == 3
171
+ end
172
+ end
173
+
158
174
  it "should not use the identity map as a lookup cache if the assocation has a nil :key option" do
159
175
  c = @c2
160
176
  @c1.many_to_one :artist, :class=>@c2, :key=>nil, :dataset=>proc{c.filter(:artist_id=>artist_id)}
@@ -45,9 +45,10 @@ describe "NestedAttributes plugin" do
45
45
  @Album.columns :id, :name, :artist_id
46
46
  @Tag.columns :id, :name
47
47
  @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
48
+ @Artist.one_to_one :first_album, :class=>@Album, :key=>:artist_id
48
49
  @Album.many_to_one :artist, :class=>@Artist
49
50
  @Album.many_to_many :tags, :class=>@Tag, :left_key=>:album_id, :right_key=>:tag_id, :join_table=>:at
50
- @Artist.nested_attributes :albums, :destroy=>true, :remove=>true
51
+ @Artist.nested_attributes :albums, :first_album, :destroy=>true, :remove=>true
51
52
  @Album.nested_attributes :artist, :tags, :destroy=>true, :remove=>true
52
53
  end
53
54
 
@@ -58,6 +59,15 @@ describe "NestedAttributes plugin" do
58
59
  @mods.should == [[:is, :artists, {:name=>"Ar"}, 1], [:is, :albums, {:name=>"Al", :artist_id=>1}, 2]]
59
60
  end
60
61
 
62
+ it "should support creating new one_to_one objects" do
63
+ a = @Artist.new(:name=>'Ar')
64
+ a.id = 1
65
+ a.first_album_attributes = {:name=>'Al'}
66
+ @mods.should == []
67
+ a.save
68
+ @mods.should == [[:is, :artists, {:name=>"Ar", :id=>1}, 1], [:is, :albums, {:name=>"Al"}, 2], [:u, :albums, {:artist_id=>nil}, "((artist_id = 1) AND (id != 2))"], [:u, :albums, {:name=>"Al", :artist_id=>1}, "(id = 2)"]]
69
+ end
70
+
61
71
  it "should support creating new one_to_many objects" do
62
72
  a = @Artist.new({:name=>'Ar', :albums_attributes=>[{:name=>'Al'}]})
63
73
  @mods.should == []
@@ -88,6 +98,16 @@ describe "NestedAttributes plugin" do
88
98
  @mods.should == [[:u, :albums, {:name=>"Al"}, '(id = 10)'], [:u, :artists, {:name=>"Ar2"}, '(id = 20)']]
89
99
  end
90
100
 
101
+ it "should support updating one_to_one objects" do
102
+ al = @Album.load(:id=>10, :name=>'Al')
103
+ ar = @Artist.load(:id=>20, :name=>'Ar')
104
+ ar.associations[:first_album] = al
105
+ ar.set(:first_album_attributes=>{:id=>10, :name=>'Al2'})
106
+ @mods.should == []
107
+ ar.save
108
+ @mods.should == [[:u, :artists, {:name=>"Ar"}, '(id = 20)'], [:u, :albums, {:name=>"Al2"}, '(id = 10)']]
109
+ end
110
+
91
111
  it "should support updating one_to_many objects" do
92
112
  al = @Album.load(:id=>10, :name=>'Al')
93
113
  ar = @Artist.load(:id=>20, :name=>'Ar')
@@ -118,6 +138,17 @@ describe "NestedAttributes plugin" do
118
138
  @mods.should == [[:u, :albums, {:artist_id=>nil, :name=>'Al'}, '(id = 10)']]
119
139
  end
120
140
 
141
+ it "should support removing one_to_one objects" do
142
+ al = @Album.load(:id=>10, :name=>'Al')
143
+ ar = @Artist.load(:id=>20, :name=>'Ar')
144
+ ar.associations[:first_album] = al
145
+ ar.set(:first_album_attributes=>{:id=>10, :_remove=>'t'})
146
+ @mods.should == []
147
+ ar.save
148
+ @mods.should == [[:u, :albums, {:artist_id=>nil}, "(artist_id = 20)"], [:u, :artists, {:name=>"Ar"}, "(id = 20)"]]
149
+
150
+ end
151
+
121
152
  it "should support removing one_to_many objects" do
122
153
  al = @Album.load(:id=>10, :name=>'Al')
123
154
  ar = @Artist.load(:id=>20, :name=>'Ar')
@@ -148,6 +179,16 @@ describe "NestedAttributes plugin" do
148
179
  @mods.should == [[:u, :albums, {:artist_id=>nil, :name=>'Al'}, '(id = 10)'], [:d, :artists, '(id = 20)']]
149
180
  end
150
181
 
182
+ it "should support destroying one_to_one objects" do
183
+ al = @Album.load(:id=>10, :name=>'Al')
184
+ ar = @Artist.load(:id=>20, :name=>'Ar')
185
+ ar.associations[:first_album] = al
186
+ ar.set(:first_album_attributes=>{:id=>10, :_delete=>'t'})
187
+ @mods.should == []
188
+ ar.save
189
+ @mods.should == [[:u, :albums, {:artist_id=>nil}, "(artist_id = 20)"], [:u, :artists, {:name=>"Ar"}, "(id = 20)"], [:d, :albums, "(id = 10)"]]
190
+ end
191
+
151
192
  it "should support destroying one_to_many objects" do
152
193
  al = @Album.load(:id=>10, :name=>'Al')
153
194
  ar = @Artist.load(:id=>20, :name=>'Ar')
@@ -239,6 +280,8 @@ describe "NestedAttributes plugin" do
239
280
  proc{a.save}.should raise_error(Sequel::ValidationFailed)
240
281
  a.errors.full_messages.should == ['artist name cannot be Ar']
241
282
  @mods.should == []
283
+ # Should preserve attributes
284
+ a.artist.name.should == 'Ar'
242
285
  end
243
286
 
244
287
  it "should not attempt to validate nested attributes if the :validate=>false association option is used" do
@@ -0,0 +1,205 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe Sequel::Model, "rcte_tree" do
4
+ before do
5
+ @c = Class.new(Sequel::Model(MODEL_DB[:nodes]))
6
+ @c.class_eval do
7
+ def self.name; 'Node'; end
8
+ columns :id, :name, :parent_id, :i, :pi
9
+ end
10
+ @ds = @c.dataset
11
+ class << @ds
12
+ attr_accessor :row_sets
13
+ def fetch_rows(sql)
14
+ @db << sql
15
+ row_sets.shift.each{|row| yield row}
16
+ end
17
+ end
18
+ @o = @c.load(:id=>2, :parent_id=>1, :name=>'AA', :i=>3, :pi=>4)
19
+ MODEL_DB.reset
20
+ end
21
+
22
+ it "should define the correct associations" do
23
+ @c.plugin :rcte_tree
24
+ @c.associations.sort_by{|x| x.to_s}.should == [:ancestors, :children, :descendants, :parent]
25
+ end
26
+
27
+ it "should define the correct associations when giving options" do
28
+ @c.plugin :rcte_tree, :ancestors=>{:name=>:as}, :children=>{:name=>:cs}, :descendants=>{:name=>:ds}, :parent=>{:name=>:p}
29
+ @c.associations.sort_by{|x| x.to_s}.should == [:as, :cs, :ds, :p]
30
+ end
31
+
32
+ it "should use the correct SQL for lazy associations" do
33
+ @c.plugin :rcte_tree
34
+ @o.parent_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.id = 1) LIMIT 1'
35
+ @o.children_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.parent_id = 2)'
36
+ @o.ancestors_dataset.sql.should == 'WITH t AS (SELECT * FROM nodes WHERE (id = 1) UNION ALL SELECT nodes.* FROM nodes INNER JOIN t ON (t.parent_id = nodes.id)) SELECT * FROM t'
37
+ @o.descendants_dataset.sql.should == 'WITH t AS (SELECT * FROM nodes WHERE (parent_id = 2) UNION ALL SELECT nodes.* FROM nodes INNER JOIN t ON (t.id = nodes.parent_id)) SELECT * FROM t'
38
+ end
39
+
40
+ it "should use the correct SQL for lazy associations when giving options" do
41
+ @c.plugin :rcte_tree, :primary_key=>:i, :key=>:pi, :cte_name=>:cte, :order=>:name, :ancestors=>{:name=>:as}, :children=>{:name=>:cs}, :descendants=>{:name=>:ds}, :parent=>{:name=>:p}
42
+ @o.p_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.i = 4) ORDER BY name LIMIT 1'
43
+ @o.cs_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.pi = 3) ORDER BY name'
44
+ @o.as_dataset.sql.should == 'WITH cte AS (SELECT * FROM nodes WHERE (i = 4) UNION ALL SELECT nodes.* FROM nodes INNER JOIN cte ON (cte.pi = nodes.i)) SELECT * FROM cte ORDER BY name'
45
+ @o.ds_dataset.sql.should == 'WITH cte AS (SELECT * FROM nodes WHERE (pi = 3) UNION ALL SELECT nodes.* FROM nodes INNER JOIN cte ON (cte.i = nodes.pi)) SELECT * FROM cte ORDER BY name'
46
+ end
47
+
48
+ it "should add all parent associations when lazily loading ancestors" do
49
+ @c.plugin :rcte_tree
50
+ @ds.row_sets = [[{:id=>1, :name=>'A', :parent_id=>3}, {:id=>4, :name=>'B', :parent_id=>nil}, {:id=>3, :name=>'?', :parent_id=>4}]]
51
+ @o.ancestors.should == [@c.load(:id=>1, :name=>'A', :parent_id=>3), @c.load(:id=>4, :name=>'B', :parent_id=>nil), @c.load(:id=>3, :name=>'?', :parent_id=>4)]
52
+ @o.associations[:parent].should == @c.load(:id=>1, :name=>'A', :parent_id=>3)
53
+ @o.associations[:parent].associations[:parent].should == @c.load(:id=>3, :name=>'?', :parent_id=>4)
54
+ @o.associations[:parent].associations[:parent].associations[:parent].should == @c.load(:id=>4, :name=>'B', :parent_id=>nil)
55
+ @o.associations[:parent].associations[:parent].associations[:parent].associations.fetch(:parent, 1).should == nil
56
+ end
57
+
58
+ it "should add all parent associations when lazily loading ancestors and giving options" do
59
+ @c.plugin :rcte_tree, :primary_key=>:i, :key=>:pi, :ancestors=>{:name=>:as}, :parent=>{:name=>:p}
60
+ @ds.row_sets = [[{:i=>4, :name=>'A', :pi=>5}, {:i=>6, :name=>'B', :pi=>nil}, {:i=>5, :name=>'?', :pi=>6}]]
61
+ @o.as.should == [@c.load(:i=>4, :name=>'A', :pi=>5), @c.load(:i=>6, :name=>'B', :pi=>nil), @c.load(:i=>5, :name=>'?', :pi=>6)]
62
+ @o.associations[:p].should == @c.load(:i=>4, :name=>'A', :pi=>5)
63
+ @o.associations[:p].associations[:p].should == @c.load(:i=>5, :name=>'?', :pi=>6)
64
+ @o.associations[:p].associations[:p].associations[:p].should == @c.load(:i=>6, :name=>'B', :pi=>nil)
65
+ @o.associations[:p].associations[:p].associations[:p].associations.fetch(:p, 1).should == nil
66
+ end
67
+
68
+ it "should add all children associations when lazily loading descendants" do
69
+ @c.plugin :rcte_tree
70
+ @ds.row_sets = [[{:id=>3, :name=>'??', :parent_id=>1}, {:id=>1, :name=>'A', :parent_id=>2}, {:id=>4, :name=>'B', :parent_id=>2}, {:id=>5, :name=>'?', :parent_id=>3}]]
71
+ @o.descendants.should == [@c.load(:id=>3, :name=>'??', :parent_id=>1), @c.load(:id=>1, :name=>'A', :parent_id=>2), @c.load(:id=>4, :name=>'B', :parent_id=>2), @c.load(:id=>5, :name=>'?', :parent_id=>3)]
72
+ @o.associations[:children].should == [@c.load(:id=>1, :name=>'A', :parent_id=>2), @c.load(:id=>4, :name=>'B', :parent_id=>2)]
73
+ @o.associations[:children].map{|c1| c1.associations[:children]}.should == [[@c.load(:id=>3, :name=>'??', :parent_id=>1)], []]
74
+ @o.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[@c.load(:id=>5, :name=>'?', :parent_id=>3)]], []]
75
+ @o.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children]}}}.should == [[[[]]], []]
76
+ end
77
+
78
+ it "should add all children associations when lazily loading descendants and giving options" do
79
+ @c.plugin :rcte_tree, :primary_key=>:i, :key=>:pi, :children=>{:name=>:cs}, :descendants=>{:name=>:ds}
80
+ @ds.row_sets = [[{:i=>7, :name=>'??', :pi=>5}, {:i=>5, :name=>'A', :pi=>3}, {:i=>6, :name=>'B', :pi=>3}, {:i=>8, :name=>'?', :pi=>7}]]
81
+ @o.ds.should == [@c.load(:i=>7, :name=>'??', :pi=>5), @c.load(:i=>5, :name=>'A', :pi=>3), @c.load(:i=>6, :name=>'B', :pi=>3), @c.load(:i=>8, :name=>'?', :pi=>7)]
82
+ @o.associations[:cs].should == [@c.load(:i=>5, :name=>'A', :pi=>3), @c.load(:i=>6, :name=>'B', :pi=>3)]
83
+ @o.associations[:cs].map{|c1| c1.associations[:cs]}.should == [[@c.load(:i=>7, :name=>'??', :pi=>5)], []]
84
+ @o.associations[:cs].map{|c1| c1.associations[:cs].map{|c2| c2.associations[:cs]}}.should == [[[@c.load(:i=>8, :name=>'?', :pi=>7)]], []]
85
+ @o.associations[:cs].map{|c1| c1.associations[:cs].map{|c2| c2.associations[:cs].map{|c3| c3.associations[:cs]}}}.should == [[[[]]], []]
86
+ end
87
+
88
+ it "should eagerly load ancestors" do
89
+ @c.plugin :rcte_tree
90
+ @ds.row_sets = [[{:id=>2, :parent_id=>1, :name=>'AA'}, {:id=>6, :parent_id=>2, :name=>'C'}, {:id=>7, :parent_id=>1, :name=>'D'}, {:id=>9, :parent_id=>nil, :name=>'E'}],
91
+ [{:id=>2, :name=>'AA', :parent_id=>1, :x_root_x=>2},
92
+ {:id=>1, :name=>'00', :parent_id=>8, :x_root_x=>1}, {:id=>1, :name=>'00', :parent_id=>8, :x_root_x=>2},
93
+ {:id=>8, :name=>'?', :parent_id=>nil, :x_root_x=>2}, {:id=>8, :name=>'?', :parent_id=>nil, :x_root_x=>1}]]
94
+ os = @ds.eager(:ancestors).all
95
+ MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
96
+ MODEL_DB.new_sqls.last.should =~ /WITH t AS \(SELECT id AS x_root_x, nodes\.\* FROM nodes WHERE \(id IN \([12], [12]\)\) UNION ALL SELECT t\.x_root_x, nodes\.\* FROM nodes INNER JOIN t ON \(t\.parent_id = nodes\.id\)\) SELECT \* FROM t/
97
+ os.should == [@c.load(:id=>2, :parent_id=>1, :name=>'AA'), @c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>7, :parent_id=>1, :name=>'D'), @c.load(:id=>9, :parent_id=>nil, :name=>'E')]
98
+ os.map{|o| o.ancestors}.should == [[@c.load(:id=>1, :name=>'00', :parent_id=>8), @c.load(:id=>8, :name=>'?', :parent_id=>nil)],
99
+ [@c.load(:id=>2, :name=>'AA', :parent_id=>1), @c.load(:id=>1, :name=>'00', :parent_id=>8), @c.load(:id=>8, :name=>'?', :parent_id=>nil)],
100
+ [@c.load(:id=>1, :name=>'00', :parent_id=>8), @c.load(:id=>8, :name=>'?', :parent_id=>nil)],
101
+ []]
102
+ os.map{|o| o.parent}.should == [@c.load(:id=>1, :name=>'00', :parent_id=>8), @c.load(:id=>2, :name=>'AA', :parent_id=>1), @c.load(:id=>1, :name=>'00', :parent_id=>8), nil]
103
+ os.map{|o| o.parent.parent if o.parent}.should == [@c.load(:id=>8, :name=>'?', :parent_id=>nil), @c.load(:id=>1, :name=>'00', :parent_id=>8), @c.load(:id=>8, :name=>'?', :parent_id=>nil), nil]
104
+ os.map{|o| o.parent.parent.parent if o.parent and o.parent.parent}.should == [nil, @c.load(:id=>8, :name=>'?', :parent_id=>nil), nil, nil]
105
+ os.map{|o| o.parent.parent.parent.parent if o.parent and o.parent.parent and o.parent.parent.parent}.should == [nil, nil, nil, nil]
106
+ MODEL_DB.new_sqls.should == []
107
+ end
108
+
109
+ it "should eagerly load ancestors when giving options" do
110
+ @c.plugin :rcte_tree, :primary_key=>:i, :key=>:pi, :key_alias=>:kal, :cte_name=>:cte, :ancestors=>{:name=>:as}, :parent=>{:name=>:p}
111
+ @ds.row_sets = [[{:i=>2, :pi=>1, :name=>'AA'}, {:i=>6, :pi=>2, :name=>'C'}, {:i=>7, :pi=>1, :name=>'D'}, {:i=>9, :pi=>nil, :name=>'E'}],
112
+ [{:i=>2, :name=>'AA', :pi=>1, :kal=>2},
113
+ {:i=>1, :name=>'00', :pi=>8, :kal=>1}, {:i=>1, :name=>'00', :pi=>8, :kal=>2},
114
+ {:i=>8, :name=>'?', :pi=>nil, :kal=>2}, {:i=>8, :name=>'?', :pi=>nil, :kal=>1}]]
115
+ os = @ds.eager(:as).all
116
+ MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
117
+ MODEL_DB.new_sqls.last.should =~ /WITH cte AS \(SELECT i AS kal, nodes\.\* FROM nodes WHERE \(i IN \([12], [12]\)\) UNION ALL SELECT cte\.kal, nodes\.\* FROM nodes INNER JOIN cte ON \(cte\.pi = nodes\.i\)\) SELECT \* FROM cte/
118
+ os.should == [@c.load(:i=>2, :pi=>1, :name=>'AA'), @c.load(:i=>6, :pi=>2, :name=>'C'), @c.load(:i=>7, :pi=>1, :name=>'D'), @c.load(:i=>9, :pi=>nil, :name=>'E')]
119
+ os.map{|o| o.as}.should == [[@c.load(:i=>1, :name=>'00', :pi=>8), @c.load(:i=>8, :name=>'?', :pi=>nil)],
120
+ [@c.load(:i=>2, :name=>'AA', :pi=>1), @c.load(:i=>1, :name=>'00', :pi=>8), @c.load(:i=>8, :name=>'?', :pi=>nil)],
121
+ [@c.load(:i=>1, :name=>'00', :pi=>8), @c.load(:i=>8, :name=>'?', :pi=>nil)],
122
+ []]
123
+ os.map{|o| o.p}.should == [@c.load(:i=>1, :name=>'00', :pi=>8), @c.load(:i=>2, :name=>'AA', :pi=>1), @c.load(:i=>1, :name=>'00', :pi=>8), nil]
124
+ os.map{|o| o.p.p if o.p}.should == [@c.load(:i=>8, :name=>'?', :pi=>nil), @c.load(:i=>1, :name=>'00', :pi=>8), @c.load(:i=>8, :name=>'?', :pi=>nil), nil]
125
+ os.map{|o| o.p.p.p if o.p and o.p.p}.should == [nil, @c.load(:i=>8, :name=>'?', :pi=>nil), nil, nil]
126
+ os.map{|o| o.p.p.p.p if o.p and o.p.p and o.p.p.p}.should == [nil, nil, nil, nil]
127
+ MODEL_DB.new_sqls.should == []
128
+ end
129
+
130
+ it "should eagerly load descendants" do
131
+ @c.plugin :rcte_tree
132
+ @ds.row_sets = [[{:id=>2, :parent_id=>1, :name=>'AA'}, {:id=>6, :parent_id=>2, :name=>'C'}, {:id=>7, :parent_id=>1, :name=>'D'}],
133
+ [{:id=>6, :parent_id=>2, :name=>'C', :x_root_x=>2}, {:id=>9, :parent_id=>2, :name=>'E', :x_root_x=>2},
134
+ {:id=>3, :name=>'00', :parent_id=>6, :x_root_x=>6}, {:id=>3, :name=>'00', :parent_id=>6, :x_root_x=>2},
135
+ {:id=>4, :name=>'?', :parent_id=>7, :x_root_x=>7}, {:id=>5, :name=>'?', :parent_id=>4, :x_root_x=>7}]]
136
+ os = @ds.eager(:descendants).all
137
+ MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
138
+ MODEL_DB.new_sqls.last.should =~ /WITH t AS \(SELECT parent_id AS x_root_x, nodes\.\* FROM nodes WHERE \(parent_id IN \([267], [267], [267]\)\) UNION ALL SELECT t\.x_root_x, nodes\.\* FROM nodes INNER JOIN t ON \(t\.id = nodes\.parent_id\)\) SELECT \* FROM t/
139
+ os.should == [@c.load(:id=>2, :parent_id=>1, :name=>'AA'), @c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>7, :parent_id=>1, :name=>'D')]
140
+ os.map{|o| o.descendants}.should == [[@c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>9, :parent_id=>2, :name=>'E'), @c.load(:id=>3, :name=>'00', :parent_id=>6)],
141
+ [@c.load(:id=>3, :name=>'00', :parent_id=>6)],
142
+ [@c.load(:id=>4, :name=>'?', :parent_id=>7), @c.load(:id=>5, :name=>'?', :parent_id=>4)]]
143
+ os.map{|o| o.children}.should == [[@c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>9, :parent_id=>2, :name=>'E')], [@c.load(:id=>3, :name=>'00', :parent_id=>6)], [@c.load(:id=>4, :name=>'?', :parent_id=>7)]]
144
+ os.map{|o1| o1.children.map{|o2| o2.children}}.should == [[[@c.load(:id=>3, :name=>'00', :parent_id=>6)], []], [[]], [[@c.load(:id=>5, :name=>'?', :parent_id=>4)]]]
145
+ os.map{|o1| o1.children.map{|o2| o2.children.map{|o3| o3.children}}}.should == [[[[]], []], [[]], [[[]]]]
146
+ MODEL_DB.new_sqls.should == []
147
+ end
148
+
149
+ it "should eagerly load descendants when giving options" do
150
+ @c.plugin :rcte_tree, :primary_key=>:i, :key=>:pi, :key_alias=>:kal, :cte_name=>:cte, :children=>{:name=>:cs}, :descendants=>{:name=>:ds}
151
+ @ds.row_sets = [[{:i=>2, :pi=>1, :name=>'AA'}, {:i=>6, :pi=>2, :name=>'C'}, {:i=>7, :pi=>1, :name=>'D'}],
152
+ [{:i=>6, :pi=>2, :name=>'C', :kal=>2}, {:i=>9, :pi=>2, :name=>'E', :kal=>2},
153
+ {:i=>3, :name=>'00', :pi=>6, :kal=>6}, {:i=>3, :name=>'00', :pi=>6, :kal=>2},
154
+ {:i=>4, :name=>'?', :pi=>7, :kal=>7}, {:i=>5, :name=>'?', :pi=>4, :kal=>7}]]
155
+ os = @ds.eager(:ds).all
156
+ MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
157
+ MODEL_DB.new_sqls.last.should =~ /WITH cte AS \(SELECT pi AS kal, nodes\.\* FROM nodes WHERE \(pi IN \([267], [267], [267]\)\) UNION ALL SELECT cte\.kal, nodes\.\* FROM nodes INNER JOIN cte ON \(cte\.i = nodes\.pi\)\) SELECT \* FROM cte/
158
+ os.should == [@c.load(:i=>2, :pi=>1, :name=>'AA'), @c.load(:i=>6, :pi=>2, :name=>'C'), @c.load(:i=>7, :pi=>1, :name=>'D')]
159
+ os.map{|o| o.ds}.should == [[@c.load(:i=>6, :pi=>2, :name=>'C'), @c.load(:i=>9, :pi=>2, :name=>'E'), @c.load(:i=>3, :name=>'00', :pi=>6)],
160
+ [@c.load(:i=>3, :name=>'00', :pi=>6)],
161
+ [@c.load(:i=>4, :name=>'?', :pi=>7), @c.load(:i=>5, :name=>'?', :pi=>4)]]
162
+ os.map{|o| o.cs}.should == [[@c.load(:i=>6, :pi=>2, :name=>'C'), @c.load(:i=>9, :pi=>2, :name=>'E')], [@c.load(:i=>3, :name=>'00', :pi=>6)], [@c.load(:i=>4, :name=>'?', :pi=>7)]]
163
+ os.map{|o1| o1.cs.map{|o2| o2.cs}}.should == [[[@c.load(:i=>3, :name=>'00', :pi=>6)], []], [[]], [[@c.load(:i=>5, :name=>'?', :pi=>4)]]]
164
+ os.map{|o1| o1.cs.map{|o2| o2.cs.map{|o3| o3.cs}}}.should == [[[[]], []], [[]], [[[]]]]
165
+ MODEL_DB.new_sqls.should == []
166
+ end
167
+
168
+ it "should eagerly load descendants to a given level" do
169
+ @c.plugin :rcte_tree
170
+ @ds.row_sets = [[{:id=>2, :parent_id=>1, :name=>'AA'}, {:id=>6, :parent_id=>2, :name=>'C'}, {:id=>7, :parent_id=>1, :name=>'D'}],
171
+ [{:id=>6, :parent_id=>2, :name=>'C', :x_root_x=>2, :x_level_x=>0}, {:id=>9, :parent_id=>2, :name=>'E', :x_root_x=>2, :x_level_x=>0},
172
+ {:id=>3, :name=>'00', :parent_id=>6, :x_root_x=>6, :x_level_x=>0}, {:id=>3, :name=>'00', :parent_id=>6, :x_root_x=>2, :x_level_x=>1},
173
+ {:id=>4, :name=>'?', :parent_id=>7, :x_root_x=>7, :x_level_x=>0}, {:id=>5, :name=>'?', :parent_id=>4, :x_root_x=>7, :x_level_x=>1}]]
174
+ os = @ds.eager(:descendants=>2).all
175
+ MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
176
+ MODEL_DB.new_sqls.last.should =~ /WITH t AS \(SELECT parent_id AS x_root_x, nodes\.\*, 0 AS x_level_x FROM nodes WHERE \(parent_id IN \([267], [267], [267]\)\) UNION ALL SELECT t\.x_root_x, nodes\.\*, \(t\.x_level_x \+ 1\) AS x_level_x FROM nodes INNER JOIN t ON \(t\.id = nodes\.parent_id\) WHERE \(t\.x_level_x < 1\)\) SELECT \* FROM t/
177
+ os.should == [@c.load(:id=>2, :parent_id=>1, :name=>'AA'), @c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>7, :parent_id=>1, :name=>'D')]
178
+ os.map{|o| o.descendants}.should == [[@c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>9, :parent_id=>2, :name=>'E'), @c.load(:id=>3, :name=>'00', :parent_id=>6)],
179
+ [@c.load(:id=>3, :name=>'00', :parent_id=>6)],
180
+ [@c.load(:id=>4, :name=>'?', :parent_id=>7), @c.load(:id=>5, :name=>'?', :parent_id=>4)]]
181
+ os.map{|o| o.associations[:children]}.should == [[@c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>9, :parent_id=>2, :name=>'E')], [@c.load(:id=>3, :name=>'00', :parent_id=>6)], [@c.load(:id=>4, :name=>'?', :parent_id=>7)]]
182
+ os.map{|o1| o1.associations[:children].map{|o2| o2.associations[:children]}}.should == [[[@c.load(:id=>3, :name=>'00', :parent_id=>6)], []], [[]], [[@c.load(:id=>5, :name=>'?', :parent_id=>4)]]]
183
+ os.map{|o1| o1.associations[:children].map{|o2| o2.associations[:children].map{|o3| o3.associations[:children]}}}.should == [[[[]], []], [[]], [[nil]]]
184
+ MODEL_DB.new_sqls.should == []
185
+ end
186
+
187
+ it "should eagerly load descendants to a given level when giving options" do
188
+ @c.plugin :rcte_tree, :primary_key=>:i, :key=>:pi, :key_alias=>:kal, :level_alias=>:lal, :cte_name=>:cte, :children=>{:name=>:cs}, :descendants=>{:name=>:ds}
189
+ @ds.row_sets = [[{:i=>2, :pi=>1, :name=>'AA'}, {:i=>6, :pi=>2, :name=>'C'}, {:i=>7, :pi=>1, :name=>'D'}],
190
+ [{:i=>6, :pi=>2, :name=>'C', :kal=>2, :lal=>0}, {:i=>9, :pi=>2, :name=>'E', :kal=>2, :lal=>0},
191
+ {:i=>3, :name=>'00', :pi=>6, :kal=>6, :lal=>0}, {:i=>3, :name=>'00', :pi=>6, :kal=>2, :lal=>1},
192
+ {:i=>4, :name=>'?', :pi=>7, :kal=>7, :lal=>0}, {:i=>5, :name=>'?', :pi=>4, :kal=>7, :lal=>1}]]
193
+ os = @ds.eager(:ds=>2).all
194
+ MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
195
+ MODEL_DB.new_sqls.last.should =~ /WITH cte AS \(SELECT pi AS kal, nodes\.\*, 0 AS lal FROM nodes WHERE \(pi IN \([267], [267], [267]\)\) UNION ALL SELECT cte\.kal, nodes\.\*, \(cte\.lal \+ 1\) AS lal FROM nodes INNER JOIN cte ON \(cte\.i = nodes\.pi\) WHERE \(cte\.lal < 1\)\) SELECT \* FROM cte/
196
+ os.should == [@c.load(:i=>2, :pi=>1, :name=>'AA'), @c.load(:i=>6, :pi=>2, :name=>'C'), @c.load(:i=>7, :pi=>1, :name=>'D')]
197
+ os.map{|o| o.ds}.should == [[@c.load(:i=>6, :pi=>2, :name=>'C'), @c.load(:i=>9, :pi=>2, :name=>'E'), @c.load(:i=>3, :name=>'00', :pi=>6)],
198
+ [@c.load(:i=>3, :name=>'00', :pi=>6)],
199
+ [@c.load(:i=>4, :name=>'?', :pi=>7), @c.load(:i=>5, :name=>'?', :pi=>4)]]
200
+ os.map{|o| o.associations[:cs]}.should == [[@c.load(:i=>6, :pi=>2, :name=>'C'), @c.load(:i=>9, :pi=>2, :name=>'E')], [@c.load(:i=>3, :name=>'00', :pi=>6)], [@c.load(:i=>4, :name=>'?', :pi=>7)]]
201
+ os.map{|o1| o1.associations[:cs].map{|o2| o2.associations[:cs]}}.should == [[[@c.load(:i=>3, :name=>'00', :pi=>6)], []], [[]], [[@c.load(:i=>5, :name=>'?', :pi=>4)]]]
202
+ os.map{|o1| o1.associations[:cs].map{|o2| o2.associations[:cs].map{|o3| o3.associations[:cs]}}}.should == [[[[]], []], [[]], [[nil]]]
203
+ MODEL_DB.new_sqls.should == []
204
+ end
205
+ end