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.
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