sequel 4.2.0 → 4.3.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.
- 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
|