sequel 4.2.0 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +28 -0
- data/doc/extensions.rdoc +84 -0
- data/doc/model_plugins.rdoc +270 -0
- data/doc/release_notes/4.3.0.txt +40 -0
- data/doc/testing.rdoc +3 -0
- data/lib/sequel/adapters/jdbc/as400.rb +4 -0
- data/lib/sequel/adapters/shared/mysql.rb +6 -1
- data/lib/sequel/adapters/shared/postgres.rb +2 -0
- data/lib/sequel/ast_transformer.rb +2 -0
- data/lib/sequel/extensions/error_sql.rb +71 -0
- data/lib/sequel/extensions/migration.rb +0 -1
- data/lib/sequel/extensions/pagination.rb +6 -2
- data/lib/sequel/extensions/pg_array.rb +12 -5
- data/lib/sequel/extensions/pg_hstore.rb +5 -3
- data/lib/sequel/extensions/pg_inet.rb +3 -3
- data/lib/sequel/extensions/pg_interval.rb +3 -3
- data/lib/sequel/extensions/pg_json.rb +3 -3
- data/lib/sequel/extensions/pg_range.rb +3 -3
- data/lib/sequel/extensions/pg_row.rb +3 -3
- data/lib/sequel/extensions/server_block.rb +11 -3
- data/lib/sequel/plugins/rcte_tree.rb +59 -39
- data/lib/sequel/plugins/tree.rb +13 -6
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +17 -0
- data/spec/core/dataset_spec.rb +14 -0
- data/spec/core/schema_spec.rb +1 -0
- data/spec/extensions/error_sql_spec.rb +20 -0
- data/spec/extensions/migration_spec.rb +15 -0
- data/spec/extensions/pagination_spec.rb +19 -0
- data/spec/extensions/pg_array_spec.rb +3 -2
- data/spec/extensions/rcte_tree_spec.rb +135 -0
- data/spec/extensions/tree_spec.rb +130 -0
- data/spec/integration/database_test.rb +5 -0
- data/spec/integration/dataset_test.rb +4 -0
- data/spec/integration/plugin_test.rb +163 -177
- data/spec/integration/spec_helper.rb +4 -0
- metadata +10 -2
data/lib/sequel/plugins/tree.rb
CHANGED
@@ -23,9 +23,9 @@ module Sequel
|
|
23
23
|
# end
|
24
24
|
module Tree
|
25
25
|
# Create parent and children associations. Any options
|
26
|
-
# specified are passed to both associations. You can
|
27
|
-
# specify options to use for the parent association
|
28
|
-
# using a :parent option, and options to use for the
|
26
|
+
# specified are passed to both associations. You can also
|
27
|
+
# specify options to use for just the parent association
|
28
|
+
# using a :parent option, and options to use for just the
|
29
29
|
# children association using a :children option.
|
30
30
|
def self.apply(model, opts=OPTS)
|
31
31
|
opts = opts.dup
|
@@ -72,7 +72,7 @@ module Sequel
|
|
72
72
|
#
|
73
73
|
# TreeClass.roots_dataset => Sequel#Dataset
|
74
74
|
def roots_dataset
|
75
|
-
ds =
|
75
|
+
ds = where(Sequel.or(Array(parent_column).zip([])))
|
76
76
|
ds = ds.order(*tree_order) if tree_order
|
77
77
|
ds
|
78
78
|
end
|
@@ -105,7 +105,7 @@ module Sequel
|
|
105
105
|
|
106
106
|
# Returns true if this is a root node, false otherwise.
|
107
107
|
def root?
|
108
|
-
!new? &&
|
108
|
+
!new? && possible_root?
|
109
109
|
end
|
110
110
|
|
111
111
|
# Returns all siblings and a reference to the current node.
|
@@ -121,6 +121,13 @@ module Sequel
|
|
121
121
|
def siblings
|
122
122
|
self_and_siblings - [self]
|
123
123
|
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
# True if if all parent columns values are not NULL.
|
128
|
+
def possible_root?
|
129
|
+
!Array(model.parent_column).map{|c| self[c]}.all?
|
130
|
+
end
|
124
131
|
end
|
125
132
|
|
126
133
|
# Plugin included when :single_root option is passed.
|
@@ -135,7 +142,7 @@ module Sequel
|
|
135
142
|
module InstanceMethods
|
136
143
|
# Hook that prevents a second root from being created.
|
137
144
|
def before_save
|
138
|
-
if
|
145
|
+
if possible_root? && (root = model.root) && pk != root.pk
|
139
146
|
raise TreeMultipleRootError, "there is already a root #{model.name} defined"
|
140
147
|
end
|
141
148
|
super
|
data/lib/sequel/sql.rb
CHANGED
@@ -1692,7 +1692,7 @@ module Sequel
|
|
1692
1692
|
|
1693
1693
|
# +LiteralString+ is used to represent literal SQL expressions. A
|
1694
1694
|
# +LiteralString+ is copied verbatim into an SQL statement. Instances of
|
1695
|
-
# +LiteralString+ can be created by calling <tt>
|
1695
|
+
# +LiteralString+ can be created by calling <tt>Sequel.lit</tt>.
|
1696
1696
|
class LiteralString
|
1697
1697
|
include SQL::OrderMethods
|
1698
1698
|
include SQL::ComplexExpressionMethods
|
data/lib/sequel/version.rb
CHANGED
@@ -3,7 +3,7 @@ module Sequel
|
|
3
3
|
MAJOR = 4
|
4
4
|
# The minor version of Sequel. Bumped for every non-patch level
|
5
5
|
# release, generally around once a month.
|
6
|
-
MINOR =
|
6
|
+
MINOR = 3
|
7
7
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
8
8
|
# releases that fix regressions from previous versions.
|
9
9
|
TINY = 0
|
@@ -1927,6 +1927,23 @@ describe 'PostgreSQL array handling' do
|
|
1927
1927
|
end
|
1928
1928
|
end
|
1929
1929
|
|
1930
|
+
specify 'insert and retrieve empty arrays' do
|
1931
|
+
@db.create_table!(:items) do
|
1932
|
+
column :n, 'integer[]'
|
1933
|
+
end
|
1934
|
+
@ds.insert(:n=>Sequel.pg_array([], :integer))
|
1935
|
+
@ds.count.should == 1
|
1936
|
+
if @native
|
1937
|
+
rs = @ds.all
|
1938
|
+
rs.should == [{:n=>[]}]
|
1939
|
+
rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
|
1940
|
+
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
|
1941
|
+
@ds.delete
|
1942
|
+
@ds.insert(rs.first)
|
1943
|
+
@ds.all.should == rs
|
1944
|
+
end
|
1945
|
+
end
|
1946
|
+
|
1930
1947
|
specify 'insert and retrieve custom array types' do
|
1931
1948
|
int2vector = Class.new do
|
1932
1949
|
attr_reader :array
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -3542,6 +3542,14 @@ describe "Sequel::Dataset#qualify" do
|
|
3542
3542
|
@ds.select{sum(:over, :args=>:a, :partition=>:b, :order=>:c){}}.qualify.sql.should == 'SELECT sum(t.a) OVER (PARTITION BY t.b ORDER BY t.c) FROM t'
|
3543
3543
|
end
|
3544
3544
|
|
3545
|
+
specify "should handle SQL::DelayedEvaluation" do
|
3546
|
+
t = :a
|
3547
|
+
ds = @ds.filter(Sequel.delay{t}).qualify
|
3548
|
+
ds.sql.should == 'SELECT t.* FROM t WHERE t.a'
|
3549
|
+
t = :b
|
3550
|
+
ds.sql.should == 'SELECT t.* FROM t WHERE t.b'
|
3551
|
+
end
|
3552
|
+
|
3545
3553
|
specify "should handle all other objects by returning them unchanged" do
|
3546
3554
|
@ds.select("a").filter{a(3)}.filter('blah').order(Sequel.lit('true')).group(Sequel.lit('a > ?', 1)).having(false).qualify.sql.should == "SELECT 'a' FROM t WHERE (a(3) AND (blah)) GROUP BY a > 1 HAVING 'f' ORDER BY true"
|
3547
3555
|
end
|
@@ -4529,6 +4537,12 @@ describe "Dataset#supports_replace?" do
|
|
4529
4537
|
end
|
4530
4538
|
end
|
4531
4539
|
|
4540
|
+
describe "Dataset#supports_lateral_subqueries?" do
|
4541
|
+
it "should be false by default" do
|
4542
|
+
Sequel::Dataset.new(nil).supports_lateral_subqueries?.should be_false
|
4543
|
+
end
|
4544
|
+
end
|
4545
|
+
|
4532
4546
|
describe "Frozen Datasets" do
|
4533
4547
|
before do
|
4534
4548
|
@ds = Sequel.mock[:test].freeze
|
data/spec/core/schema_spec.rb
CHANGED
@@ -1532,6 +1532,7 @@ describe "Schema Parser" do
|
|
1532
1532
|
@db = Sequel.mock(:host=>'postgres')
|
1533
1533
|
@db.extend(sm)
|
1534
1534
|
@db.schema(:interval).first.last[:type].should == :interval
|
1535
|
+
@db.schema(:citext).first.last[:type].should == :string
|
1535
1536
|
|
1536
1537
|
@db = Sequel.mock(:host=>'mysql')
|
1537
1538
|
@db.extend(sm)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
|
+
|
3
|
+
describe "error_sql extension" do
|
4
|
+
before do
|
5
|
+
@db = Sequel.mock(:fetch=>proc{|sql| @db.log_yield(sql){raise StandardError}}).extension(:error_sql)
|
6
|
+
end
|
7
|
+
|
8
|
+
specify "should have Sequel::DatabaseError#sql give the SQL causing the error" do
|
9
|
+
@db["SELECT"].all rescue (e = $!)
|
10
|
+
e.sql.should == "SELECT"
|
11
|
+
end
|
12
|
+
|
13
|
+
specify "should have Sequel::DatabaseError#sql give the SQL causing the error when using a logger" do
|
14
|
+
l = Object.new
|
15
|
+
def l.method_missing(*) end
|
16
|
+
@db.loggers = [l]
|
17
|
+
@db["SELECT"].all rescue (e = $!)
|
18
|
+
e.sql.should == "SELECT"
|
19
|
+
end
|
20
|
+
end
|
@@ -211,6 +211,21 @@ describe "Reversible Migrations with Sequel.migration{change{}}" do
|
|
211
211
|
end
|
212
212
|
end
|
213
213
|
|
214
|
+
describe "Sequel::Migrator.migrator_class" do
|
215
|
+
specify "should return IntegerMigrator if not using timestamp migrations" do
|
216
|
+
Sequel::Migrator.migrator_class("spec/files/integer_migrations").should == Sequel::IntegerMigrator
|
217
|
+
end
|
218
|
+
|
219
|
+
specify "should return TimestampMigrator if using timestamp migrations" do
|
220
|
+
Sequel::Migrator.migrator_class('spec/files/timestamped_migrations').should == Sequel::TimestampMigrator
|
221
|
+
end
|
222
|
+
|
223
|
+
specify "should return self if run on a subclass" do
|
224
|
+
Sequel::IntegerMigrator.migrator_class("spec/files/timestamped_migrations").should == Sequel::IntegerMigrator
|
225
|
+
Sequel::TimestampMigrator.migrator_class("spec/files/integer_migrations").should == Sequel::TimestampMigrator
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
214
229
|
describe "Sequel::IntegerMigrator" do
|
215
230
|
before do
|
216
231
|
dbc = Class.new(Sequel::Mock::Database) do
|
@@ -11,6 +11,8 @@ describe "A paginated dataset" do
|
|
11
11
|
specify "should raise an error if the dataset already has a limit" do
|
12
12
|
proc{@d.limit(10).paginate(1,10)}.should raise_error(Sequel::Error)
|
13
13
|
proc{@paginated.paginate(2,20)}.should raise_error(Sequel::Error)
|
14
|
+
proc{@d.limit(10).each_page(10){|ds|}}.should raise_error(Sequel::Error)
|
15
|
+
proc{@d.limit(10).each_page(10)}.should raise_error(Sequel::Error)
|
14
16
|
end
|
15
17
|
|
16
18
|
specify "should set the limit and offset options correctly" do
|
@@ -21,6 +23,9 @@ describe "A paginated dataset" do
|
|
21
23
|
specify "should set the page count correctly" do
|
22
24
|
@paginated.page_count.should == 8
|
23
25
|
@d.paginate(1, 50).page_count.should == 4
|
26
|
+
|
27
|
+
@d.meta_def(:count) {0}
|
28
|
+
@d.paginate(1, 50).page_count.should == 1
|
24
29
|
end
|
25
30
|
|
26
31
|
specify "should set the current page number correctly" do
|
@@ -61,6 +66,10 @@ describe "A paginated dataset" do
|
|
61
66
|
@d.paginate(2, 20).last_page?.should be_false
|
62
67
|
@d.paginate(5, 30).last_page?.should be_false
|
63
68
|
@d.paginate(6, 30).last_page?.should be_true
|
69
|
+
|
70
|
+
@d.meta_def(:count) {0}
|
71
|
+
@d.paginate(1, 30).last_page?.should be_true
|
72
|
+
@d.paginate(2, 30).last_page?.should be_false
|
64
73
|
end
|
65
74
|
|
66
75
|
specify "should know if current page is first page" do
|
@@ -96,4 +105,14 @@ describe "Dataset#each_page" do
|
|
96
105
|
'SELECT * FROM items LIMIT 50 OFFSET 150',
|
97
106
|
]
|
98
107
|
end
|
108
|
+
|
109
|
+
specify "should return an enumerator if no block is given" do
|
110
|
+
enum = @d.each_page(50)
|
111
|
+
enum.map {|p| p.sql}.should == [
|
112
|
+
'SELECT * FROM items LIMIT 50 OFFSET 0',
|
113
|
+
'SELECT * FROM items LIMIT 50 OFFSET 50',
|
114
|
+
'SELECT * FROM items LIMIT 50 OFFSET 100',
|
115
|
+
'SELECT * FROM items LIMIT 50 OFFSET 150',
|
116
|
+
]
|
117
|
+
end
|
99
118
|
end
|
@@ -147,6 +147,7 @@ describe "pg_array extension" do
|
|
147
147
|
end
|
148
148
|
|
149
149
|
it "should literalize with types correctly" do
|
150
|
+
@db.literal(@m::PGArray.new([], :int4)).should == "'{}'::int4[]"
|
150
151
|
@db.literal(@m::PGArray.new([1], :int4)).should == 'ARRAY[1]::int4[]'
|
151
152
|
@db.literal(@m::PGArray.new([nil], :text)).should == 'ARRAY[NULL]::text[]'
|
152
153
|
@db.literal(@m::PGArray.new([nil, 1], :int8)).should == 'ARRAY[NULL,1]::int8[]'
|
@@ -230,7 +231,7 @@ describe "pg_array extension" do
|
|
230
231
|
end
|
231
232
|
|
232
233
|
@db.literal(@db.typecast_value(meth, [array_in])).should == "ARRAY[#{output}]::#{db_type}[]"
|
233
|
-
@db.literal(@db.typecast_value(meth, [])).should == "
|
234
|
+
@db.literal(@db.typecast_value(meth, [])).should == "'{}'::#{db_type}[]"
|
234
235
|
end
|
235
236
|
proc{@db.typecast_value(:integer_array, {})}.should raise_error(Sequel::InvalidValue)
|
236
237
|
end
|
@@ -311,7 +312,7 @@ describe "pg_array extension" do
|
|
311
312
|
|
312
313
|
it "should support registering custom types with :array_type option" do
|
313
314
|
Sequel::Postgres::PGArray.register('foo', :oid=>3, :array_type=>:blah)
|
314
|
-
@db.literal(Sequel::Postgres::PG_TYPES[3].call('{}')).should == '
|
315
|
+
@db.literal(Sequel::Postgres::PG_TYPES[3].call('{}')).should == "'{}'::blah[]"
|
315
316
|
end
|
316
317
|
|
317
318
|
it "should use and not override existing database typecast method if :typecast_method option is given" do
|
@@ -39,6 +39,18 @@ describe Sequel::Model, "rcte_tree" do
|
|
39
39
|
@o.descendants_dataset.sql.should == 'WITH t(id, name, parent_id, i, pi) AS (SELECT id, name, parent_id, i, pi FROM nodes WHERE (parent_id = 2) UNION ALL SELECT nodes.id, nodes.name, nodes.parent_id, nodes.i, nodes.pi FROM nodes INNER JOIN t ON (t.id = nodes.parent_id)) SELECT * FROM t AS nodes'
|
40
40
|
end
|
41
41
|
|
42
|
+
it "should use the correct SQL for eager loading when recursive CTEs require column aliases" do
|
43
|
+
@c.dataset.meta_def(:recursive_cte_requires_column_aliases?){true}
|
44
|
+
@c.plugin :rcte_tree
|
45
|
+
@ds._fetch = [[{:id=>1, :name=>'A', :parent_id=>3}]]
|
46
|
+
@c.eager(:ancestors).all
|
47
|
+
DB.sqls.should == ["SELECT * FROM nodes", "WITH t(x_root_x, id, name, parent_id, i, pi) AS (SELECT id AS x_root_x, nodes.id, nodes.name, nodes.parent_id, nodes.i, nodes.pi FROM nodes WHERE (id IN (3)) UNION ALL SELECT t.x_root_x, nodes.id, nodes.name, nodes.parent_id, nodes.i, nodes.pi FROM nodes INNER JOIN t ON (t.parent_id = nodes.id)) SELECT * FROM t AS nodes"]
|
48
|
+
|
49
|
+
@ds._fetch = [[{:id=>1, :name=>'A', :parent_id=>3}]]
|
50
|
+
@c.eager(:descendants).all
|
51
|
+
DB.sqls.should == ["SELECT * FROM nodes", "WITH t(x_root_x, id, name, parent_id, i, pi) AS (SELECT parent_id AS x_root_x, nodes.id, nodes.name, nodes.parent_id, nodes.i, nodes.pi FROM nodes WHERE (parent_id IN (1)) UNION ALL SELECT t.x_root_x, nodes.id, nodes.name, nodes.parent_id, nodes.i, nodes.pi FROM nodes INNER JOIN t ON (t.id = nodes.parent_id)) SELECT * FROM t AS nodes"]
|
52
|
+
end
|
53
|
+
|
42
54
|
it "should use the correct SQL for lazy associations when giving options" do
|
43
55
|
@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}
|
44
56
|
@o.p_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.i = 4) ORDER BY name LIMIT 1'
|
@@ -242,3 +254,126 @@ describe Sequel::Model, "rcte_tree" do
|
|
242
254
|
sqls.last.should =~ /WITH t AS \(SELECT parent_id AS x_root_x, nodes\.\* FROM nodes WHERE \(\(parent_id IN \([267], [267], [267]\)\) AND \(i = 1\)\) UNION ALL SELECT t\.x_root_x, nodes\.\* FROM nodes INNER JOIN t ON \(t\.id = nodes\.parent_id\) WHERE \(i = 1\)\) SELECT \* FROM t AS nodes WHERE \(i = 1\)/
|
243
255
|
end
|
244
256
|
end
|
257
|
+
|
258
|
+
describe Sequel::Model, "rcte_tree with composite keys" do
|
259
|
+
before do
|
260
|
+
@c = Class.new(Sequel::Model(DB[:nodes]))
|
261
|
+
@c.class_eval do
|
262
|
+
def self.name; 'Node'; end
|
263
|
+
columns :id, :id2, :name, :parent_id, :parent_id2, :i, :pi
|
264
|
+
set_primary_key [:id, :id2]
|
265
|
+
end
|
266
|
+
@ds = @c.dataset
|
267
|
+
@o = @c.load(:id=>2, :id2=>5, :parent_id=>1, :parent_id2=>6, :name=>'AA', :i=>3, :pi=>4)
|
268
|
+
DB.reset
|
269
|
+
end
|
270
|
+
|
271
|
+
it "should use the correct SQL for lazy associations" do
|
272
|
+
@c.plugin :rcte_tree, :key=>[:parent_id, :parent_id2]
|
273
|
+
@o.parent_dataset.sql.should == 'SELECT * FROM nodes WHERE ((nodes.id = 1) AND (nodes.id2 = 6)) LIMIT 1'
|
274
|
+
@o.children_dataset.sql.should == 'SELECT * FROM nodes WHERE ((nodes.parent_id = 2) AND (nodes.parent_id2 = 5))'
|
275
|
+
@o.ancestors_dataset.sql.should == 'WITH t AS (SELECT * FROM nodes WHERE ((id = 1) AND (id2 = 6)) UNION ALL SELECT nodes.* FROM nodes INNER JOIN t ON ((t.parent_id = nodes.id) AND (t.parent_id2 = nodes.id2))) SELECT * FROM t AS nodes'
|
276
|
+
@o.descendants_dataset.sql.should == 'WITH t AS (SELECT * FROM nodes WHERE ((parent_id = 2) AND (parent_id2 = 5)) UNION ALL SELECT nodes.* FROM nodes INNER JOIN t ON ((t.id = nodes.parent_id) AND (t.id2 = nodes.parent_id2))) SELECT * FROM t AS nodes'
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should use the correct SQL for lazy associations when recursive CTEs require column aliases" do
|
280
|
+
@c.dataset.meta_def(:recursive_cte_requires_column_aliases?){true}
|
281
|
+
@c.plugin :rcte_tree, :key=>[:parent_id, :parent_id2]
|
282
|
+
@o.ancestors_dataset.sql.should == 'WITH t(id, id2, name, parent_id, parent_id2, i, pi) AS (SELECT id, id2, name, parent_id, parent_id2, i, pi FROM nodes WHERE ((id = 1) AND (id2 = 6)) UNION ALL SELECT nodes.id, nodes.id2, nodes.name, nodes.parent_id, nodes.parent_id2, nodes.i, nodes.pi FROM nodes INNER JOIN t ON ((t.parent_id = nodes.id) AND (t.parent_id2 = nodes.id2))) SELECT * FROM t AS nodes'
|
283
|
+
@o.descendants_dataset.sql.should == 'WITH t(id, id2, name, parent_id, parent_id2, i, pi) AS (SELECT id, id2, name, parent_id, parent_id2, i, pi FROM nodes WHERE ((parent_id = 2) AND (parent_id2 = 5)) UNION ALL SELECT nodes.id, nodes.id2, nodes.name, nodes.parent_id, nodes.parent_id2, nodes.i, nodes.pi FROM nodes INNER JOIN t ON ((t.id = nodes.parent_id) AND (t.id2 = nodes.parent_id2))) SELECT * FROM t AS nodes'
|
284
|
+
end
|
285
|
+
|
286
|
+
it "should use the correct SQL for eager loading when recursive CTEs require column aliases" do
|
287
|
+
@c.dataset.meta_def(:recursive_cte_requires_column_aliases?){true}
|
288
|
+
@c.plugin :rcte_tree, :key=>[:parent_id, :parent_id2]
|
289
|
+
@ds._fetch = [[{:id=>1, :id2=>2, :name=>'A', :parent_id=>3, :parent_id2=>4}]]
|
290
|
+
@c.eager(:ancestors).all
|
291
|
+
DB.sqls.should == ["SELECT * FROM nodes", "WITH t(x_root_x_0, x_root_x_1, id, id2, name, parent_id, parent_id2, i, pi) AS (SELECT id AS x_root_x_0, id2 AS x_root_x_1, nodes.id, nodes.id2, nodes.name, nodes.parent_id, nodes.parent_id2, nodes.i, nodes.pi FROM nodes WHERE ((id, id2) IN ((3, 4))) UNION ALL SELECT t.x_root_x_0, t.x_root_x_1, nodes.id, nodes.id2, nodes.name, nodes.parent_id, nodes.parent_id2, nodes.i, nodes.pi FROM nodes INNER JOIN t ON ((t.parent_id = nodes.id) AND (t.parent_id2 = nodes.id2))) SELECT * FROM t AS nodes"]
|
292
|
+
|
293
|
+
@ds._fetch = [[{:id=>1, :id2=>2, :name=>'A', :parent_id=>3, :parent_id2=>4}]]
|
294
|
+
@c.eager(:descendants).all
|
295
|
+
DB.sqls.should == ["SELECT * FROM nodes", "WITH t(x_root_x_0, x_root_x_1, id, id2, name, parent_id, parent_id2, i, pi) AS (SELECT parent_id AS x_root_x_0, parent_id2 AS x_root_x_1, nodes.id, nodes.id2, nodes.name, nodes.parent_id, nodes.parent_id2, nodes.i, nodes.pi FROM nodes WHERE ((parent_id, parent_id2) IN ((1, 2))) UNION ALL SELECT t.x_root_x_0, t.x_root_x_1, nodes.id, nodes.id2, nodes.name, nodes.parent_id, nodes.parent_id2, nodes.i, nodes.pi FROM nodes INNER JOIN t ON ((t.id = nodes.parent_id) AND (t.id2 = nodes.parent_id2))) SELECT * FROM t AS nodes"]
|
296
|
+
end
|
297
|
+
|
298
|
+
it "should add all parent associations when lazily loading ancestors" do
|
299
|
+
@c.plugin :rcte_tree, :key=>[:parent_id, :parent_id2]
|
300
|
+
@ds._fetch = [[{:id=>1, :id2=>6, :name=>'A', :parent_id=>3, :parent_id2=>5}, {:id=>4, :id2=>8, :name=>'B', :parent_id=>nil, :parent_id2=>nil}, {:id=>3, :id2=>5, :name=>'?', :parent_id=>4, :parent_id2=>8}]]
|
301
|
+
@o.ancestors.should == [@c.load(:id=>1, :id2=>6, :name=>'A', :parent_id=>3, :parent_id2=>5), @c.load(:id=>4, :id2=>8, :name=>'B', :parent_id=>nil, :parent_id2=>nil), @c.load(:id=>3, :id2=>5, :name=>'?', :parent_id=>4, :parent_id2=>8)]
|
302
|
+
@o.associations[:parent].should == @c.load(:id=>1, :id2=>6, :name=>'A', :parent_id=>3, :parent_id2=>5)
|
303
|
+
@o.associations[:parent].associations[:parent].should == @c.load(:id=>3, :id2=>5, :name=>'?', :parent_id=>4, :parent_id2=>8)
|
304
|
+
@o.associations[:parent].associations[:parent].associations[:parent].should == @c.load(:id=>4, :id2=>8, :name=>'B', :parent_id=>nil, :parent_id2=>nil)
|
305
|
+
@o.associations[:parent].associations[:parent].associations[:parent].associations.fetch(:parent, 1).should == nil
|
306
|
+
end
|
307
|
+
|
308
|
+
it "should add all children associations when lazily loading descendants" do
|
309
|
+
@c.plugin :rcte_tree, :key=>[:parent_id, :parent_id2]
|
310
|
+
@ds._fetch = [[{:id=>3, :id2=>4, :name=>'??', :parent_id=>1, :parent_id2=>2}, {:id=>1, :id2=>2, :name=>'A', :parent_id=>2, :parent_id2=>5}, {:id=>4, :id2=>5, :name=>'B', :parent_id=>2, :parent_id2=>5}, {:id=>5, :id2=>7, :name=>'?', :parent_id=>3, :parent_id2=>4}]]
|
311
|
+
@o.descendants.should == [@c.load(:id=>3, :id2=>4, :name=>'??', :parent_id=>1, :parent_id2=>2), @c.load(:id=>1, :id2=>2, :name=>'A', :parent_id=>2, :parent_id2=>5), @c.load(:id=>4, :id2=>5, :name=>'B', :parent_id=>2, :parent_id2=>5), @c.load(:id=>5, :id2=>7, :name=>'?', :parent_id=>3, :parent_id2=>4)]
|
312
|
+
@o.associations[:children].should == [@c.load(:id=>1, :id2=>2, :name=>'A', :parent_id=>2, :parent_id2=>5), @c.load(:id=>4, :id2=>5, :name=>'B', :parent_id=>2, :parent_id2=>5)]
|
313
|
+
@o.associations[:children].map{|c1| c1.associations[:children]}.should == [[@c.load(:id=>3, :id2=>4, :name=>'??', :parent_id=>1, :parent_id2=>2)], []]
|
314
|
+
@o.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[@c.load(:id=>5, :id2=>7, :name=>'?', :parent_id=>3, :parent_id2=>4)]], []]
|
315
|
+
@o.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children]}}}.should == [[[[]]], []]
|
316
|
+
end
|
317
|
+
|
318
|
+
it "should eagerly load ancestors" do
|
319
|
+
@c.plugin :rcte_tree, :key=>[:parent_id, :parent_id2]
|
320
|
+
@ds._fetch = [[{:id=>2, :id2=>3, :parent_id=>1, :parent_id2=>2, :name=>'AA'}, {:id=>6, :id2=>7, :parent_id=>2, :parent_id2=>3, :name=>'C'}, {:id=>7, :id2=>8, :parent_id=>1, :parent_id2=>2, :name=>'D'}, {:id=>9, :id2=>10, :parent_id=>nil, :parent_id2=>nil, :name=>'E'}],
|
321
|
+
[{:id=>2, :id2=>3, :name=>'AA', :parent_id=>1, :parent_id2=>2, :x_root_x_0=>2, :x_root_x_1=>3},
|
322
|
+
{:id=>1, :id2=>2, :name=>'00', :parent_id=>8, :parent_id2=>9, :x_root_x_0=>1, :x_root_x_1=>2}, {:id=>1, :id2=>2, :name=>'00', :parent_id=>8, :parent_id2=>9, :x_root_x_0=>2, :x_root_x_1=>3},
|
323
|
+
{:id=>8, :id2=>9, :name=>'?', :parent_id=>nil, :parent_id2=>nil, :x_root_x_0=>2, :x_root_x_1=>3}, {:id=>8, :id2=>9, :name=>'?', :parent_id=>nil, :parent_id2=>nil, :x_root_x_0=>1, :x_root_x_1=>2}]]
|
324
|
+
os = @ds.eager(:ancestors).all
|
325
|
+
sqls = DB.sqls
|
326
|
+
sqls.first.should == "SELECT * FROM nodes"
|
327
|
+
sqls.last.should =~ /WITH t AS \(SELECT id AS x_root_x_0, id2 AS x_root_x_1, nodes\.\* FROM nodes WHERE \(\(id, id2\) IN \(\([12], [23]\), \([12], [23]\)\)\) UNION ALL SELECT t\.x_root_x_0, t\.x_root_x_1, nodes\.\* FROM nodes INNER JOIN t ON \(\(t\.parent_id = nodes\.id\) AND \(t\.parent_id2 = nodes\.id2\)\)\) SELECT \* FROM t AS nodes/
|
328
|
+
os.should == [@c.load(:id=>2, :id2=>3, :parent_id=>1, :parent_id2=>2, :name=>'AA'), @c.load(:id=>6, :id2=>7, :parent_id=>2, :parent_id2=>3, :name=>'C'), @c.load(:id=>7, :id2=>8, :parent_id=>1, :parent_id2=>2, :name=>'D'), @c.load(:id=>9, :id2=>10, :parent_id=>nil, :parent_id2=>nil, :name=>'E')]
|
329
|
+
os.map{|o| o.ancestors}.should == [[@c.load(:id=>1, :id2=>2, :name=>'00', :parent_id=>8, :parent_id2=>9), @c.load(:id=>8, :id2=>9, :name=>'?', :parent_id=>nil, :parent_id2=>nil)],
|
330
|
+
[@c.load(:id=>2, :id2=>3, :name=>'AA', :parent_id=>1, :parent_id2=>2), @c.load(:id=>1, :id2=>2, :name=>'00', :parent_id=>8, :parent_id2=>9), @c.load(:id=>8, :id2=>9, :name=>'?', :parent_id=>nil, :parent_id2=>nil)],
|
331
|
+
[@c.load(:id=>1, :id2=>2, :name=>'00', :parent_id=>8, :parent_id2=>9), @c.load(:id=>8, :id2=>9, :name=>'?', :parent_id=>nil, :parent_id2=>nil)],
|
332
|
+
[]]
|
333
|
+
os.map{|o| o.parent}.should == [@c.load(:id=>1, :id2=>2, :name=>'00', :parent_id=>8, :parent_id2=>9), @c.load(:id=>2, :id2=>3, :name=>'AA', :parent_id=>1, :parent_id2=>2), @c.load(:id=>1, :id2=>2, :name=>'00', :parent_id=>8, :parent_id2=>9), nil]
|
334
|
+
os.map{|o| o.parent.parent if o.parent}.should == [@c.load(:id=>8, :id2=>9, :name=>'?', :parent_id=>nil, :parent_id2=>nil), @c.load(:id=>1, :id2=>2, :name=>'00', :parent_id=>8, :parent_id2=>9), @c.load(:id=>8, :id2=>9, :name=>'?', :parent_id=>nil, :parent_id2=>nil), nil]
|
335
|
+
os.map{|o| o.parent.parent.parent if o.parent and o.parent.parent}.should == [nil, @c.load(:id=>8, :id2=>9, :name=>'?', :parent_id=>nil, :parent_id2=>nil), nil, nil]
|
336
|
+
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]
|
337
|
+
DB.sqls.should == []
|
338
|
+
end
|
339
|
+
|
340
|
+
it "should eagerly load descendants" do
|
341
|
+
@c.plugin :rcte_tree, :key=>[:parent_id, :parent_id2]
|
342
|
+
@ds._fetch = [[{:id=>2, :id2=>3, :parent_id=>1, :parent_id2=>2, :name=>'AA'}, {:id=>6, :id2=>7, :parent_id=>2, :parent_id2=>3, :name=>'C'}, {:id=>7, :id2=>8, :parent_id=>1, :parent_id2=>2, :name=>'D'}],
|
343
|
+
[{:id=>6, :id2=>7, :parent_id=>2, :parent_id2=>3, :name=>'C', :x_root_x_0=>2, :x_root_x_1=>3}, {:id=>9, :id2=>10, :parent_id=>2, :parent_id2=>3, :name=>'E', :x_root_x_0=>2, :x_root_x_1=>3},
|
344
|
+
{:id=>3, :id2=>4, :name=>'00', :parent_id=>6, :parent_id2=>7, :x_root_x_0=>6, :x_root_x_1=>7}, {:id=>3, :id2=>4, :name=>'00', :parent_id=>6, :parent_id2=>7, :x_root_x_0=>2, :x_root_x_1=>3},
|
345
|
+
{:id=>4, :id2=>5, :name=>'?', :parent_id=>7, :parent_id2=>8, :x_root_x_0=>7, :x_root_x_1=>8}, {:id=>5, :id2=>6, :name=>'?', :parent_id=>4, :parent_id2=>5, :x_root_x_0=>7, :x_root_x_1=>8}]]
|
346
|
+
os = @ds.eager(:descendants).all
|
347
|
+
sqls = DB.sqls
|
348
|
+
sqls.first.should == "SELECT * FROM nodes"
|
349
|
+
sqls.last.should =~ /WITH t AS \(SELECT parent_id AS x_root_x_0, parent_id2 AS x_root_x_1, nodes\.\* FROM nodes WHERE \(\(parent_id, parent_id2\) IN \(\([267], [378]\), \([267], [378]\), \([267], [378]\)\)\) UNION ALL SELECT t\.x_root_x_0, t\.x_root_x_1, nodes\.\* FROM nodes INNER JOIN t ON \(\(t\.id = nodes\.parent_id\) AND \(t\.id2 = nodes\.parent_id2\)\)\) SELECT \* FROM t AS nodes/
|
350
|
+
os.should == [@c.load(:id=>2, :id2=>3, :parent_id=>1, :parent_id2=>2, :name=>'AA'), @c.load(:id=>6, :id2=>7, :parent_id=>2, :parent_id2=>3, :name=>'C'), @c.load(:id=>7, :id2=>8, :parent_id=>1, :parent_id2=>2, :name=>'D')]
|
351
|
+
os.map{|o| o.descendants}.should == [[@c.load(:id=>6, :id2=>7, :parent_id=>2, :parent_id2=>3, :name=>'C'), @c.load(:id=>9, :id2=>10, :parent_id=>2, :parent_id2=>3, :name=>'E'), @c.load(:id=>3, :id2=>4, :name=>'00', :parent_id=>6, :parent_id2=>7)],
|
352
|
+
[@c.load(:id=>3, :id2=>4, :name=>'00', :parent_id=>6, :parent_id2=>7)],
|
353
|
+
[@c.load(:id=>4, :id2=>5, :name=>'?', :parent_id=>7, :parent_id2=>8), @c.load(:id=>5, :id2=>6, :name=>'?', :parent_id=>4, :parent_id2=>5)]]
|
354
|
+
os.map{|o| o.children}.should == [[@c.load(:id=>6, :id2=>7, :parent_id=>2, :parent_id2=>3, :name=>'C'), @c.load(:id=>9, :id2=>10, :parent_id=>2, :parent_id2=>3, :name=>'E')], [@c.load(:id=>3, :id2=>4, :name=>'00', :parent_id=>6, :parent_id2=>7)], [@c.load(:id=>4, :id2=>5, :name=>'?', :parent_id=>7, :parent_id2=>8)]]
|
355
|
+
os.map{|o1| o1.children.map{|o2| o2.children}}.should == [[[@c.load(:id=>3, :id2=>4, :name=>'00', :parent_id=>6, :parent_id2=>7)], []], [[]], [[@c.load(:id=>5, :id2=>6, :name=>'?', :parent_id=>4, :parent_id2=>5)]]]
|
356
|
+
os.map{|o1| o1.children.map{|o2| o2.children.map{|o3| o3.children}}}.should == [[[[]], []], [[]], [[[]]]]
|
357
|
+
DB.sqls.should == []
|
358
|
+
end
|
359
|
+
|
360
|
+
it "should eagerly load descendants to a given level" do
|
361
|
+
@c.plugin :rcte_tree, :key=>[:parent_id, :parent_id2]
|
362
|
+
@ds._fetch = [[{:id=>2, :id2=>3, :parent_id=>1, :parent_id=>2, :name=>'AA'}, {:id=>6, :id2=>7, :parent_id=>2, :parent_id2=>3, :name=>'C'}, {:id=>7, :id2=>8, :parent_id=>1, :parent_id2=>2, :name=>'D'}],
|
363
|
+
[{:id=>6, :id2=>7, :parent_id=>2, :parent_id2=>3, :name=>'C', :x_root_x_0=>2, :x_root_x_1=>3, :x_level_x=>0}, {:id=>9, :id2=>10, :parent_id=>2, :parent_id2=>3, :name=>'E', :x_root_x_0=>2, :x_root_x_1=>3, :x_level_x=>0},
|
364
|
+
{:id=>3, :id2=>4, :name=>'00', :parent_id=>6, :parent_id2=>7, :x_root_x_0=>6, :x_root_x_1=>7, :x_level_x=>0}, {:id=>3, :id2=>4, :name=>'00', :parent_id=>6, :parent_id2=>7, :x_root_x_0=>2, :x_root_x_1=>3, :x_level_x=>1},
|
365
|
+
{:id=>4, :id2=>5, :name=>'?', :parent_id=>7, :parent_id2=>8, :x_root_x_0=>7, :x_root_x_1=>8, :x_level_x=>0}, {:id=>5, :id2=>6, :name=>'?', :parent_id=>4, :parent_id2=>5, :x_root_x_0=>7, :x_root_x_1=>8, :x_level_x=>1}]]
|
366
|
+
os = @ds.eager(:descendants=>2).all
|
367
|
+
sqls = DB.sqls
|
368
|
+
sqls.first.should == "SELECT * FROM nodes"
|
369
|
+
sqls.last.should =~ /WITH t AS \(SELECT parent_id AS x_root_x_0, parent_id2 AS x_root_x_1, nodes\.\*, 0 AS x_level_x FROM nodes WHERE \(\(parent_id, parent_id2\) IN \(\([267], [378]\), \([267], [378]\), \([267], [378]\)\)\) UNION ALL SELECT t\.x_root_x_0, t\.x_root_x_1, nodes\.\*, \(t\.x_level_x \+ 1\) AS x_level_x FROM nodes INNER JOIN t ON \(\(t\.id = nodes\.parent_id\) AND \(t\.id2 = nodes\.parent_id2\)\) WHERE \(t\.x_level_x < 1\)\) SELECT \* FROM t AS nodes/
|
370
|
+
os.should == [@c.load(:id=>2, :id2=>3, :parent_id=>1, :parent_id=>2, :name=>'AA'), @c.load(:id=>6, :id2=>7, :parent_id=>2, :parent_id2=>3, :name=>'C'), @c.load(:id=>7, :id2=>8, :parent_id=>1, :parent_id2=>2, :name=>'D')]
|
371
|
+
os.map{|o| o.descendants}.should == [[@c.load(:id=>6, :id2=>7, :parent_id=>2, :parent_id2=>3, :name=>'C'), @c.load(:id=>9, :id2=>10, :parent_id=>2, :parent_id2=>3, :name=>'E'), @c.load(:id=>3, :id2=>4, :name=>'00', :parent_id=>6, :parent_id2=>7)],
|
372
|
+
[@c.load(:id=>3, :id2=>4, :name=>'00', :parent_id=>6, :parent_id2=>7)],
|
373
|
+
[@c.load(:id=>4, :id2=>5, :name=>'?', :parent_id=>7, :parent_id2=>8), @c.load(:id=>5, :id2=>6, :name=>'?', :parent_id=>4, :parent_id2=>5)]]
|
374
|
+
os.map{|o| o.associations[:children]}.should == [[@c.load(:id=>6, :id2=>7, :parent_id=>2, :parent_id2=>3, :name=>'C'), @c.load(:id=>9, :id2=>10, :parent_id=>2, :parent_id2=>3, :name=>'E')], [@c.load(:id=>3, :id2=>4, :name=>'00', :parent_id=>6, :parent_id2=>7)], [@c.load(:id=>4, :id2=>5, :name=>'?', :parent_id=>7, :parent_id2=>8)]]
|
375
|
+
os.map{|o1| o1.associations[:children].map{|o2| o2.associations[:children]}}.should == [[[@c.load(:id=>3, :id2=>4, :name=>'00', :parent_id=>6, :parent_id2=>7)], []], [[]], [[@c.load(:id=>5, :id2=>6, :name=>'?', :parent_id=>4, :parent_id2=>5)]]]
|
376
|
+
os.map{|o1| o1.associations[:children].map{|o2| o2.associations[:children].map{|o3| o3.associations[:children]}}}.should == [[[[]], []], [[]], [[nil]]]
|
377
|
+
DB.sqls.should == []
|
378
|
+
end
|
379
|
+
end
|
@@ -138,3 +138,133 @@ describe Sequel::Model, "tree plugin" do
|
|
138
138
|
end
|
139
139
|
end
|
140
140
|
end
|
141
|
+
|
142
|
+
describe Sequel::Model, "tree plugin with composite keys" do
|
143
|
+
def klass(opts={})
|
144
|
+
@db = DB
|
145
|
+
c = Class.new(Sequel::Model(@db[:nodes]))
|
146
|
+
c.class_eval do
|
147
|
+
def self.name; 'Node'; end
|
148
|
+
columns :id, :id2, :name, :parent_id, :parent_id2, :i, :pi
|
149
|
+
set_primary_key [:id, :id2]
|
150
|
+
plugin :tree, opts.merge(:key=>[:parent_id, :parent_id2])
|
151
|
+
end
|
152
|
+
c
|
153
|
+
end
|
154
|
+
|
155
|
+
before do
|
156
|
+
@c = klass
|
157
|
+
@ds = @c.dataset
|
158
|
+
@o = @c.load(:id=>2, :id2=>5, :parent_id=>1, :parent_id2=>6, :name=>'AA', :i=>3, :pi=>4)
|
159
|
+
@db.reset
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
it "should use the correct SQL for lazy associations" do
|
164
|
+
@o.parent_dataset.sql.should == 'SELECT * FROM nodes WHERE ((nodes.id = 1) AND (nodes.id2 = 6)) LIMIT 1'
|
165
|
+
@o.children_dataset.sql.should == 'SELECT * FROM nodes WHERE ((nodes.parent_id = 2) AND (nodes.parent_id2 = 5))'
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should have parent_column give an array of symbols of the parent column" do
|
169
|
+
@c.parent_column.should == [:parent_id, :parent_id2]
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should have roots return an array of the tree's roots" do
|
173
|
+
@ds._fetch = [{:id=>1, :parent_id=>nil, :parent_id2=>nil, :name=>'r'}]
|
174
|
+
@c.roots.should == [@c.load(:id=>1, :parent_id=>nil, :parent_id2=>nil, :name=>'r')]
|
175
|
+
@db.sqls.should == ["SELECT * FROM nodes WHERE ((parent_id IS NULL) OR (parent_id2 IS NULL))"]
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should have roots_dataset be a dataset representing the tree's roots" do
|
179
|
+
@c.roots_dataset.sql.should == "SELECT * FROM nodes WHERE ((parent_id IS NULL) OR (parent_id2 IS NULL))"
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should have ancestors return the ancestors of the current node" do
|
183
|
+
@ds._fetch = [[{:id=>1, :id2=>6, :parent_id=>5, :parent_id2=>7, :name=>'r'}], [{:id=>5, :id2=>7, :parent_id=>nil, :parent_id2=>nil, :name=>'r2'}]]
|
184
|
+
@o.ancestors.should == [@c.load(:id=>1, :id2=>6, :parent_id=>5, :parent_id2=>7, :name=>'r'), @c.load(:id=>5, :id2=>7, :parent_id=>nil, :parent_id2=>nil, :name=>'r2')]
|
185
|
+
sqls = @db.sqls
|
186
|
+
sqls.length.should == 2
|
187
|
+
["SELECT * FROM nodes WHERE ((id = 1) AND (id2 = 6)) LIMIT 1", "SELECT * FROM nodes WHERE ((id2 = 6) AND (id = 1)) LIMIT 1"].should include(sqls[0])
|
188
|
+
["SELECT * FROM nodes WHERE ((id = 5) AND (id2 = 7)) LIMIT 1", "SELECT * FROM nodes WHERE ((id2 = 7) AND (id = 5)) LIMIT 1"].should include(sqls[1])
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should have descendants return the descendants of the current node" do
|
192
|
+
@ds._fetch = [[{:id=>3, :id2=>7, :parent_id=>2, :parent_id2=>5, :name=>'r'}, {:id=>4, :id2=>8, :parent_id=>2, :parent_id2=>5, :name=>'r2'}], [{:id=>5, :id2=>9, :parent_id=>4, :parent_id2=>8, :name=>'r3'}], []]
|
193
|
+
@o.descendants.should == [@c.load(:id=>3, :id2=>7, :parent_id=>2, :parent_id2=>5, :name=>'r'), @c.load(:id=>4, :id2=>8, :parent_id=>2, :parent_id2=>5, :name=>'r2'), @c.load(:id=>5, :id2=>9, :parent_id=>4, :parent_id2=>8, :name=>'r3')]
|
194
|
+
@db.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.parent_id = 2) AND (nodes.parent_id2 = 5))",
|
195
|
+
"SELECT * FROM nodes WHERE ((nodes.parent_id = 3) AND (nodes.parent_id2 = 7))",
|
196
|
+
"SELECT * FROM nodes WHERE ((nodes.parent_id = 5) AND (nodes.parent_id2 = 9))",
|
197
|
+
"SELECT * FROM nodes WHERE ((nodes.parent_id = 4) AND (nodes.parent_id2 = 8))"]
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should have root return the root of the current node" do
|
201
|
+
@ds._fetch = [[{:id=>1, :id2=>6, :parent_id=>5, :parent_id2=>7, :name=>'r'}], [{:id=>5, :id2=>7, :parent_id=>nil, :parent_id2=>nil, :name=>'r2'}]]
|
202
|
+
@o.root.should == @c.load(:id=>5, :id2=>7, :parent_id=>nil, :parent_id2=>nil, :name=>'r2')
|
203
|
+
sqls = @db.sqls
|
204
|
+
sqls.length.should == 2
|
205
|
+
["SELECT * FROM nodes WHERE ((id = 1) AND (id2 = 6)) LIMIT 1", "SELECT * FROM nodes WHERE ((id2 = 6) AND (id = 1)) LIMIT 1"].should include(sqls[0])
|
206
|
+
["SELECT * FROM nodes WHERE ((id = 5) AND (id2 = 7)) LIMIT 1", "SELECT * FROM nodes WHERE ((id2 = 7) AND (id = 5)) LIMIT 1"].should include(sqls[1])
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should have root? return true for a root node and false for a child node" do
|
210
|
+
@c.load(:parent_id => nil, :parent_id2=>nil).root?.should be_true
|
211
|
+
@c.load(:parent_id => 1, :parent_id2=>nil).root?.should be_true
|
212
|
+
@c.load(:parent_id => nil, :parent_id2=>2).root?.should be_true
|
213
|
+
@c.load(:parent_id => 1, :parent_id2=>2).root?.should be_false
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should have root? return false for an new node" do
|
217
|
+
@c.new.root?.should be_false
|
218
|
+
end
|
219
|
+
|
220
|
+
it "should have self_and_siblings return the children of the current node's parent" do
|
221
|
+
@ds._fetch = [[{:id=>1, :id2=>6, :parent_id=>3, :parent_id2=>7, :name=>'r'}], [{:id=>7, :id2=>9, :parent_id=>1, :parent_id2=>6, :name=>'r2'}, @o.values.dup]]
|
222
|
+
@o.self_and_siblings.should == [@c.load(:id=>7, :id2=>9, :parent_id=>1, :parent_id2=>6, :name=>'r2'), @o]
|
223
|
+
sqls = @db.sqls
|
224
|
+
sqls.length.should == 2
|
225
|
+
["SELECT * FROM nodes WHERE ((id = 1) AND (id2 = 6)) LIMIT 1", "SELECT * FROM nodes WHERE ((id2 = 6) AND (id = 1)) LIMIT 1"].should include(sqls[0])
|
226
|
+
sqls[1].should == "SELECT * FROM nodes WHERE ((nodes.parent_id = 1) AND (nodes.parent_id2 = 6))"
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should have siblings return the children of the current node's parent, except for the current node" do
|
230
|
+
@ds._fetch = [[{:id=>1, :id2=>6, :parent_id=>3, :parent_id2=>7, :name=>'r'}], [{:id=>7, :id2=>9, :parent_id=>1, :parent_id2=>6, :name=>'r2'}, @o.values.dup]]
|
231
|
+
@o.siblings.should == [@c.load(:id=>7, :id2=>9, :parent_id=>1, :parent_id2=>6, :name=>'r2')]
|
232
|
+
sqls = @db.sqls
|
233
|
+
sqls.length.should == 2
|
234
|
+
["SELECT * FROM nodes WHERE ((id = 1) AND (id2 = 6)) LIMIT 1", "SELECT * FROM nodes WHERE ((id2 = 6) AND (id = 1)) LIMIT 1"].should include(sqls[0])
|
235
|
+
sqls[1].should == "SELECT * FROM nodes WHERE ((nodes.parent_id = 1) AND (nodes.parent_id2 = 6))"
|
236
|
+
end
|
237
|
+
|
238
|
+
describe ":single_root option" do
|
239
|
+
before do
|
240
|
+
@c = klass(:single_root => true)
|
241
|
+
end
|
242
|
+
|
243
|
+
it "prevents creating a second root" do
|
244
|
+
@c.dataset._fetch = [{:id=>1, :id2=>6, :parent_id=>nil, :parent_id2=>nil, :name=>'r'}]
|
245
|
+
lambda { @c.create }.should raise_error(Sequel::Plugins::Tree::TreeMultipleRootError)
|
246
|
+
@c.dataset._fetch = [{:id=>1, :id2=>6, :parent_id=>1, :parent_id2=>nil, :name=>'r'}]
|
247
|
+
lambda { @c.create(:parent_id2=>1) }.should raise_error(Sequel::Plugins::Tree::TreeMultipleRootError)
|
248
|
+
@c.dataset._fetch = [{:id=>1, :id2=>6, :parent_id=>nil, :parent_id2=>2, :name=>'r'}]
|
249
|
+
lambda { @c.create(:parent_id=>2) }.should raise_error(Sequel::Plugins::Tree::TreeMultipleRootError)
|
250
|
+
end
|
251
|
+
|
252
|
+
it "errors when promoting an existing record to a second root" do
|
253
|
+
@c.dataset._fetch = [{:id=>1, :id2=>6, :parent_id=>nil, :parent_id2=>nil, :name=>'r'}]
|
254
|
+
lambda { @c.load(:id => 2, :id2=>7, :parent_id => 1, :parent_id2=>2).update(:parent_id => nil, :parent_id2=>nil) }.should raise_error(Sequel::Plugins::Tree::TreeMultipleRootError)
|
255
|
+
@c.dataset._fetch = [{:id=>1, :id2=>6, :parent_id=>1, :parent_id2=>nil, :name=>'r'}]
|
256
|
+
lambda { @c.load(:id => 2, :id2=>7, :parent_id => 1, :parent_id2=>2).update(:parent_id => nil) }.should raise_error(Sequel::Plugins::Tree::TreeMultipleRootError)
|
257
|
+
@c.dataset._fetch = [{:id=>1, :id2=>6, :parent_id=>nil, :parent_id2=>2, :name=>'r'}]
|
258
|
+
lambda { @c.load(:id => 2, :id2=>7, :parent_id => 1, :parent_id2=>2).update(:parent_id2 => nil) }.should raise_error(Sequel::Plugins::Tree::TreeMultipleRootError)
|
259
|
+
end
|
260
|
+
|
261
|
+
it "allows updating existing root" do
|
262
|
+
@c.dataset._fetch = {:id=>1, :id2=>6, :parent_id=>nil, :parent_id2=>nil, :name=>'r'}
|
263
|
+
lambda { @c.root.update(:name => 'fdsa') }.should_not raise_error
|
264
|
+
@c.dataset._fetch = {:id=>1, :id2=>6, :parent_id=>1, :parent_id2=>nil, :name=>'r'}
|
265
|
+
lambda { @c.root.update(:name => 'fdsa') }.should_not raise_error
|
266
|
+
@c.dataset._fetch = {:id=>1, :id2=>6, :parent_id=>nil, :parent_id2=>2, :name=>'r'}
|
267
|
+
lambda { @c.root.update(:name => 'fdsa') }.should_not raise_error
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|