sequel 3.9.0 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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