sequel 4.2.0 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +28 -0
  3. data/doc/extensions.rdoc +84 -0
  4. data/doc/model_plugins.rdoc +270 -0
  5. data/doc/release_notes/4.3.0.txt +40 -0
  6. data/doc/testing.rdoc +3 -0
  7. data/lib/sequel/adapters/jdbc/as400.rb +4 -0
  8. data/lib/sequel/adapters/shared/mysql.rb +6 -1
  9. data/lib/sequel/adapters/shared/postgres.rb +2 -0
  10. data/lib/sequel/ast_transformer.rb +2 -0
  11. data/lib/sequel/extensions/error_sql.rb +71 -0
  12. data/lib/sequel/extensions/migration.rb +0 -1
  13. data/lib/sequel/extensions/pagination.rb +6 -2
  14. data/lib/sequel/extensions/pg_array.rb +12 -5
  15. data/lib/sequel/extensions/pg_hstore.rb +5 -3
  16. data/lib/sequel/extensions/pg_inet.rb +3 -3
  17. data/lib/sequel/extensions/pg_interval.rb +3 -3
  18. data/lib/sequel/extensions/pg_json.rb +3 -3
  19. data/lib/sequel/extensions/pg_range.rb +3 -3
  20. data/lib/sequel/extensions/pg_row.rb +3 -3
  21. data/lib/sequel/extensions/server_block.rb +11 -3
  22. data/lib/sequel/plugins/rcte_tree.rb +59 -39
  23. data/lib/sequel/plugins/tree.rb +13 -6
  24. data/lib/sequel/sql.rb +1 -1
  25. data/lib/sequel/version.rb +1 -1
  26. data/spec/adapters/postgres_spec.rb +17 -0
  27. data/spec/core/dataset_spec.rb +14 -0
  28. data/spec/core/schema_spec.rb +1 -0
  29. data/spec/extensions/error_sql_spec.rb +20 -0
  30. data/spec/extensions/migration_spec.rb +15 -0
  31. data/spec/extensions/pagination_spec.rb +19 -0
  32. data/spec/extensions/pg_array_spec.rb +3 -2
  33. data/spec/extensions/rcte_tree_spec.rb +135 -0
  34. data/spec/extensions/tree_spec.rb +130 -0
  35. data/spec/integration/database_test.rb +5 -0
  36. data/spec/integration/dataset_test.rb +4 -0
  37. data/spec/integration/plugin_test.rb +163 -177
  38. data/spec/integration/spec_helper.rb +4 -0
  39. metadata +10 -2
@@ -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 = filter(parent_column => nil)
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? && self[model.parent_column].nil?
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 self[model.parent_column].nil? && (root = model.root) && pk != root.pk
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>String#lit</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
@@ -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 = 2
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
@@ -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
@@ -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 == "ARRAY[]::#{db_type}[]"
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 == 'ARRAY[]::blah[]'
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