acts_as_nested_interval 0.0.1

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 (47) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +171 -0
  3. data/Rakefile +38 -0
  4. data/lib/acts_as_nested_interval/core_ext/integer.rb +21 -0
  5. data/lib/acts_as_nested_interval/version.rb +3 -0
  6. data/lib/acts_as_nested_interval.rb +293 -0
  7. data/lib/tasks/acts_as_nested_interval_tasks.rake +4 -0
  8. data/test/acts_as_nested_interval_test.rb +184 -0
  9. data/test/dummy/README.rdoc +261 -0
  10. data/test/dummy/Rakefile +7 -0
  11. data/test/dummy/app/assets/javascripts/application.js +15 -0
  12. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  13. data/test/dummy/app/controllers/application_controller.rb +3 -0
  14. data/test/dummy/app/helpers/application_helper.rb +2 -0
  15. data/test/dummy/app/models/region.rb +3 -0
  16. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  17. data/test/dummy/config/application.rb +56 -0
  18. data/test/dummy/config/boot.rb +10 -0
  19. data/test/dummy/config/database.yml +25 -0
  20. data/test/dummy/config/environment.rb +5 -0
  21. data/test/dummy/config/environments/development.rb +37 -0
  22. data/test/dummy/config/environments/production.rb +67 -0
  23. data/test/dummy/config/environments/test.rb +37 -0
  24. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  25. data/test/dummy/config/initializers/inflections.rb +15 -0
  26. data/test/dummy/config/initializers/mime_types.rb +5 -0
  27. data/test/dummy/config/initializers/secret_token.rb +7 -0
  28. data/test/dummy/config/initializers/session_store.rb +8 -0
  29. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  30. data/test/dummy/config/locales/en.yml +5 -0
  31. data/test/dummy/config/routes.rb +58 -0
  32. data/test/dummy/config.ru +4 -0
  33. data/test/dummy/db/development.sqlite3 +0 -0
  34. data/test/dummy/db/migrate/20120302143528_create_regions.rb +15 -0
  35. data/test/dummy/db/schema.rb +28 -0
  36. data/test/dummy/db/test.sqlite3 +0 -0
  37. data/test/dummy/log/development.log +129 -0
  38. data/test/dummy/log/test.log +13942 -0
  39. data/test/dummy/public/404.html +26 -0
  40. data/test/dummy/public/422.html +26 -0
  41. data/test/dummy/public/500.html +25 -0
  42. data/test/dummy/public/favicon.ico +0 -0
  43. data/test/dummy/script/rails +6 -0
  44. data/test/dummy/test/fixtures/regions.yml +7 -0
  45. data/test/dummy/test/unit/region_test.rb +7 -0
  46. data/test/test_helper.rb +10 -0
  47. metadata +154 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # ActsAsNestedInterval
2
+
3
+ ## About
4
+
5
+ * Pythonic's acts_as_nested_interval updated to Rails 3 and gemified.
6
+ * This: https://github.com/clyfe/acts_as_nested_interval
7
+ * Original: https://github.com/pythonic/acts_as_nested_interval
8
+ * Acknowledgement: http://arxiv.org/html/cs.DB/0401014 by Vadim Tropashko.
9
+
10
+ This act implements a nested-interval tree. You can find all descendants or all
11
+ ancestors with just one select query. You can insert and delete records without
12
+ a full table update.
13
+
14
+ ## Install
15
+
16
+ ```ruby
17
+ # add to Gemfile
18
+ gem 'acts_as_nested_interval'
19
+ ```
20
+
21
+ ```sh
22
+ # install
23
+ bundle install
24
+ ```
25
+
26
+ * requires a `parent_id` foreign key column, and `lftp` and `lftq` integer columns.
27
+ * if your database does not support stored procedures then you also need `rgtp` and `rgtq` integer columns
28
+ * if your database does not support functional indexes then you also need a `rgt` float column
29
+ * the `lft` float column is optional
30
+
31
+ Example:
32
+
33
+ ```ruby
34
+ create_table :regions do |t|
35
+ t.integer :parent_id
36
+ t.integer :lftp, :null => false
37
+ t.integer :lftq, :null => false
38
+ t.integer :rgtp, :null => false
39
+ t.integer :rgtq, :null => false
40
+ t.float :lft, :null => false
41
+ t.float :rgt, :null => false
42
+ t.string :name, :null => false
43
+ end
44
+ add_index :regions, :parent_id
45
+ add_index :regions, :lftp
46
+ add_index :regions, :lftq
47
+ add_index :regions, :lft
48
+ add_index :regions, :rgt
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ The size of the tree is limited by the precision of the integer and floating
54
+ point data types in the database.
55
+
56
+ This act provides these named scopes:
57
+
58
+ ```ruby
59
+ Region.roots # returns roots of tree.
60
+ Region.preorder # returns records for preorder traversal.
61
+ ```
62
+
63
+ This act provides these instance methods:
64
+
65
+ ```ruby
66
+ Region.parent # returns parent of record.
67
+ Region.children # returns children of record.
68
+ Region.ancestors # returns scoped ancestors of record.
69
+ Region.descendants # returns scoped descendants of record.
70
+ Region.depth # returns depth of record.
71
+ ```
72
+
73
+ Example:
74
+
75
+ ```ruby
76
+ class Region < ActiveRecord::Base
77
+ acts_as_nested_interval
78
+ end
79
+
80
+ earth = Region.create :name => "Earth"
81
+ oceania = Region.create :name => "Oceania", :parent => earth
82
+ australia = Region.create :name => "Australia", :parent => oceania
83
+ new_zealand = Region.new :name => "New Zealand"
84
+ oceania.children << new_zealand
85
+ earth.descendants # => [oceania, australia, new_zealand]
86
+ earth.children # => [oceania]
87
+ oceania.children # => [australia, new_zealand]
88
+ oceania.depth # => 1
89
+ australia.parent # => oceania
90
+ new_zealand.ancestors # => [earth, oceania]
91
+ Region.roots # => [earth]
92
+ ```
93
+
94
+ ## How it works
95
+
96
+ The `mediant` of two rationals is the rational with the sum of the two
97
+ numerators for the numerator, and the sum of the two denominators for the
98
+ denominator (where the denominators are positive). The mediant is numerically
99
+ between the two rationals.
100
+ Example: `3 / 5` is the mediant of `1 / 2` and `2 / 3`, and `1 / 2 < 3 / 5 < 2 / 3`.
101
+
102
+ Each record `covers` a half-open interval `(lftp / lftq, rgtp / rgtq]`. The tree
103
+ root covers `(0 / 1, 1 / 1]`. The first child of a record covers interval
104
+ `(mediant{lftp / lftq, rgtp / rgtq}, rgtp / rgtq]`; the next child covers the interval
105
+ `(mediant{lftp / lftq, mediant{lftp / lftq, rgtp / rgtq}}, mediant{lftp / lftq, rgtp / rgtq}]`.
106
+
107
+ With this construction each lftp and lftq are relatively prime and the identity
108
+ `lftq * rgtp = 1 + lftp * rgtq holds`.
109
+
110
+ Example:
111
+
112
+ 0/1 1/2 3/5 2/3 1/1
113
+ earth (-----------------------------------------------------------]
114
+ oceania (-----------------------------]
115
+ australia (-------------------]
116
+ new zealand (---]
117
+
118
+ The descendants of a record are those records that cover subintervals of the
119
+ interval covered by the record, and the ancestors are those records that cover
120
+ superintervals.
121
+
122
+ Only the left end of an interval needs to be stored, since the right end can be
123
+ calculated (with special exceptions) using the above identity:
124
+
125
+ rgtp := x
126
+ rgtq := (x * lftq - 1) / lftp
127
+
128
+ where x is the inverse of lftq modulo lftp.
129
+
130
+ Similarly, the left end of the interval covered by the parent of a record can
131
+ be calculated using the above identity:
132
+
133
+ lftp := (x * lftp - 1) / lftq
134
+ lftq := x
135
+
136
+ where x is the inverse of lftp modulo lftq.
137
+
138
+ ## Moving nodes
139
+
140
+ To move a record from old.lftp, old.lftq to new.lftp, new.lftq, apply this
141
+ linear transform to lftp, lftq of all descendants:
142
+
143
+ lftp := (old.lftq * new.rgtp - old.rgtq * new.lftp) * lftp
144
+ + (old.rgtp * new.lftp - old.lftp * new.rgtp) * lftq
145
+ lftq := (old.lftq * new.rgtq - old.rgtq * new.lftq) * lftp
146
+ + (old.rgtp * new.lftq - old.lftp * new.rgtq) * lftq
147
+
148
+ You should acquire a table lock before moving a record.
149
+
150
+ Example:
151
+
152
+ ```ruby
153
+ pacific = Region.create :name => "Pacific", :parent => earth
154
+ oceania.parent = pacific
155
+ oceania.save!
156
+ ```
157
+
158
+ ## Migrating from acts_as_tree
159
+
160
+ If you come from acts_as_tree or another system where you only have a parent_id,
161
+ to rebuild the intervals based on `acts_as_nested_set`, after you migrated the DB
162
+ and created the columns required by `acts_as_nested_set` run:
163
+
164
+ ```ruby
165
+ Region.rebuild_nested_interval_tree!
166
+ ```
167
+
168
+ NOTE! About `rebuild_nested_interval_tree!`:
169
+ * zeroes all your tree intervals before recomputing them!
170
+ * does a lot of N+1 queries of type `record.parent` and not only.
171
+ This might change once the AR identity_map is finished.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'ActsAsNestedInterval'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ require 'rake/testtask'
29
+
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.libs << 'test'
33
+ t.pattern = 'test/**/*_test.rb'
34
+ t.verbose = false
35
+ end
36
+
37
+
38
+ task :default => :test
@@ -0,0 +1,21 @@
1
+ # Copyright (c) 2007, 2008 Pythonic Pty Ltd
2
+ # http://www.pythonic.com.au/
3
+
4
+ class Integer
5
+ # Returns modular multiplicative inverse.
6
+ # Examples:
7
+ # 2.inverse(7) # => 4
8
+ # 4.inverse(7) # => 2
9
+ def inverse(m)
10
+ u, v = m, self
11
+ x, y = 0, 1
12
+ while v != 0
13
+ q, r = u.divmod(v)
14
+ x, y = y, x - q * y
15
+ u, v = v, r
16
+ end
17
+ if u.abs == 1
18
+ x < 0 ? x + m : x
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module ActsAsNestedInterval
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,293 @@
1
+ # Copyright (c) 2007, 2008 Pythonic Pty Ltd
2
+ # http://www.pythonic.com.au/
3
+
4
+ # Copyright (c) 2012 Nicolae Claudius
5
+ # https://github.com/clyfe
6
+
7
+ require 'acts_as_nested_interval/version'
8
+ require 'acts_as_nested_interval/core_ext/integer'
9
+
10
+ module ActsAsNestedInterval
11
+ extend ActiveSupport::Concern
12
+
13
+ # This act implements a nested-interval tree. You can find all descendants
14
+ # or all ancestors with just one select query. You can insert and delete
15
+ # records without a full table update.
16
+ module ClassMethods
17
+
18
+ # The +options+ hash can include:
19
+ # * <tt>:foreign_key</tt> -- the self-reference foreign key column name (default :parent_id).
20
+ # * <tt>:scope_columns</tt> -- an array of columns to scope independent trees.
21
+ # * <tt>:lft_index</tt> -- whether to use functional index for lft (default false).
22
+ # * <tt>:virtual_root</tt> -- whether to compute root's interval as in an upper root (default false)
23
+ def acts_as_nested_interval(options = {})
24
+ cattr_accessor :nested_interval_foreign_key
25
+ cattr_accessor :nested_interval_scope_columns
26
+ cattr_accessor :nested_interval_lft_index
27
+
28
+ cattr_accessor :virtual_root
29
+ self.virtual_root = !!options[:virtual_root]
30
+
31
+ self.nested_interval_foreign_key = options[:foreign_key] || :parent_id
32
+ self.nested_interval_scope_columns = Array(options[:scope_columns])
33
+ self.nested_interval_lft_index = options[:lft_index]
34
+
35
+ belongs_to :parent, :class_name => name, :foreign_key => nested_interval_foreign_key
36
+ has_many :children, :class_name => name, :foreign_key => nested_interval_foreign_key, :dependent => :destroy
37
+ scope :roots, where(nested_interval_foreign_key => nil)
38
+
39
+ if columns_hash["rgt"]
40
+ scope :preorder, order('rgt DESC, lftp ASC')
41
+ elsif columns_hash["rgtp"] && columns_hash["rgtq"]
42
+ scope :preorder, order('1.0 * rgtp / rgtq DESC, lftp ASC')
43
+ else
44
+ scope :preorder, order('nested_interval_rgt(lftp, lftq) DESC, lftp ASC')
45
+ end
46
+
47
+ class_eval do
48
+ include ActsAsNestedInterval::NodeInstanceMethods
49
+
50
+ # TODO make into before filters
51
+ before_create :create_nested_interval
52
+ before_destroy :destroy_nested_interval
53
+ before_update :update_nested_interval
54
+
55
+ if columns_hash["lft"]
56
+ def descendants
57
+ quoted_table_name = self.class.quoted_table_name
58
+ nested_interval_scope.where <<-SQL
59
+ #{lftp} < #{quoted_table_name}.lftp AND
60
+ #{quoted_table_name}.lft BETWEEN #{1.0 * lftp / lftq} AND #{1.0 * rgtp / rgtq}
61
+ SQL
62
+ end
63
+ elsif nested_interval_lft_index
64
+ def descendants
65
+ quoted_table_name = self.class.quoted_table_name
66
+ nested_interval_scope.where <<-SQL
67
+ #{lftp} < #{quoted_table_name}.lftp AND
68
+ 1.0 * #{quoted_table_name}.lftp / #{quoted_table_name}.lftq BETWEEN
69
+ #{1.0 * lftp / lftq} AND
70
+ #{1.0 * rgtp / rgtq}
71
+ SQL
72
+ end
73
+ elsif connection.adapter_name == "MySQL"
74
+ def descendants
75
+ quoted_table_name = self.class.quoted_table_name
76
+ nested_interval_scope.where <<-SQL
77
+ ( #{quoted_table_name}.lftp != #{rgtp} OR
78
+ #{quoted_table_name}.lftq != #{rgtq}
79
+ ) AND
80
+ #{quoted_table_name}.lftp BETWEEN
81
+ 1 + #{quoted_table_name}.lftq * #{lftp} DIV #{lftq} AND
82
+ #{quoted_table_name}.lftq * #{rgtp} DIV #{rgtq}
83
+ SQL
84
+ end
85
+ else
86
+ def descendants
87
+ quoted_table_name = self.class.quoted_table_name
88
+ nested_interval_scope.where <<-SQL
89
+ ( #{quoted_table_name}.lftp != #{rgtp} OR
90
+ #{quoted_table_name}.lftq != #{rgtq}
91
+ ) AND
92
+ #{quoted_table_name}.lftp BETWEEN
93
+ 1 + #{quoted_table_name}.lftq * CAST(#{lftp} AS BIGINT) / #{lftq} AND
94
+ #{quoted_table_name}.lftq * CAST(#{rgtp} AS BIGINT) / #{rgtq}
95
+ SQL
96
+ end
97
+ end
98
+
99
+ def self.rebuild_nested_interval_tree!
100
+ skip_callback :update, :before, :update_nested_interval
101
+ skip_callback :update, :before, :sync_childre
102
+ scope :roots, where(nested_interval_foreign_key => nil).where("#{quoted_table_name}.lftq > 0")
103
+ update_all(:lftp => 0, :lftq => 0)
104
+ update_all(:rgtp => 0) if columns_hash["rgtp"]
105
+ update_all(:rgtq => 0) if columns_hash["rgtq"]
106
+ update_all(:lft => 0) if columns_hash["lft"]
107
+ update_all(:rgt => 0) if columns_hash["rgt"]
108
+ clear_cache!
109
+
110
+ scoped.find_each do |d|
111
+ begin
112
+ d.create_nested_interval
113
+ unless d.save
114
+ puts "WARNING #{d.name} did not save because #{d.errors.inspect}"
115
+ end
116
+ rescue => e
117
+ puts "WARNING #{d.name} exception because #{e.message}"
118
+ puts "WARNING lftq: #{d.lftq}, llftp: #{d.lftp}"
119
+ end
120
+ end
121
+
122
+ set_callback :update, :before, :update_nested_interval
123
+ set_callback :update, :before, :sync_childre
124
+ scope :roots, where(nested_interval_foreign_key => nil)
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+ end
131
+
132
+ module NodeInstanceMethods
133
+ def set_nested_interval(lftp, lftq)
134
+ self.lftp, self.lftq = lftp, lftq
135
+ self.rgtp = rgtp if has_attribute?(:rgtp)
136
+ self.rgtq = rgtq if has_attribute?(:rgtq)
137
+ self.lft = lft if has_attribute?(:lft)
138
+ self.rgt = rgt if has_attribute?(:rgt)
139
+ end
140
+
141
+ def set_nested_interval_for_top
142
+ if self.class.virtual_root
143
+ set_nested_interval *next_root_lft
144
+ else
145
+ set_nested_interval 0, 1
146
+ end
147
+ end
148
+
149
+ # Creates record.
150
+ def create_nested_interval
151
+ if read_attribute(nested_interval_foreign_key).nil?
152
+ set_nested_interval_for_top
153
+ else
154
+ set_nested_interval *parent.lock!.next_child_lft
155
+ end
156
+ end
157
+
158
+ # Destroys record.
159
+ def destroy_nested_interval
160
+ lock! rescue nil
161
+ end
162
+
163
+ def nested_interval_scope
164
+ conditions = {}
165
+ nested_interval_scope_columns.each do |column_name|
166
+ conditions[column_name] = send(column_name)
167
+ end
168
+ self.class.where conditions
169
+ end
170
+
171
+ # Updates record, updating descendants if parent association updated,
172
+ # in which case caller should first acquire table lock.
173
+ def update_nested_interval
174
+ if read_attribute(nested_interval_foreign_key).nil?
175
+ set_nested_interval_for_top
176
+ elsif !association(:parent).updated?
177
+ db_self = self.class.find(id, :lock => true)
178
+ write_attribute(nested_interval_foreign_key, db_self.read_attribute(nested_interval_foreign_key))
179
+ set_nested_interval db_self.lftp, db_self.lftq
180
+ else # move
181
+ # No locking in this case -- caller should have acquired table lock.
182
+ update_nested_interval_move
183
+ end
184
+ end
185
+
186
+ def update_nested_interval_move
187
+ db_self = self.class.find(id)
188
+ db_parent = self.class.find(read_attribute(nested_interval_foreign_key))
189
+ if db_self.ancestor_of?(db_parent)
190
+ errors.add nested_interval_foreign_key, "is descendant"
191
+ raise ActiveRecord::RecordInvalid, self
192
+ end
193
+
194
+ set_nested_interval *parent.next_child_lft
195
+ mysql_tmp = "@" if ["MySQL", "Mysql2"].include?(connection.adapter_name)
196
+ cpp = db_self.lftq * rgtp - db_self.rgtq * lftp
197
+ cpq = db_self.rgtp * lftp - db_self.lftp * rgtp
198
+ cqp = db_self.lftq * rgtq - db_self.rgtq * lftq
199
+ cqq = db_self.rgtp * lftq - db_self.lftp * rgtq
200
+
201
+ db_descendants = db_self.descendants
202
+
203
+ if has_attribute?(:rgtp) && has_attribute?(:rgtq)
204
+ db_descendants.update_all %(
205
+ rgtp = #{cpp} * rgtp + #{cpq} * rgtq,
206
+ rgtq = #{cqp} * #{mysql_tmp}rgtp + #{cqq} * rgtq
207
+ ), mysql_tmp && %(@rgtp := rgtp)
208
+ db_descendants.update_all "rgt = 1.0 * rgtp / rgtq" if has_attribute?(:rgt)
209
+ end
210
+
211
+ db_descendants.update_all %(
212
+ lftp = #{cpp} * lftp + #{cpq} * lftq,
213
+ lftq = #{cqp} * #{mysql_tmp}lftp + #{cqq} * lftq
214
+ ), mysql_tmp && %(@lftp := lftp)
215
+
216
+ db_descendants.update_all %(lft = 1.0 * lftp / lftq) if has_attribute?(:lft)
217
+ end
218
+
219
+ def ancestor_of?(node)
220
+ node.lftp == lftp && node.lftq == lftq ||
221
+ node.lftp > node.lftq * lftp / lftq &&
222
+ node.lftp <= node.lftq * rgtp / rgtq &&
223
+ (node.lftp != rgtp || node.lftq != rgtq)
224
+ end
225
+
226
+ def ancestors
227
+ sqls = ["NULL"]
228
+ p, q = lftp, lftq
229
+ while p != 0
230
+ x = p.inverse(q)
231
+ p, q = (x * p - 1) / q, x
232
+ sqls << "lftq = #{q} AND lftp = #{p}"
233
+ end
234
+ nested_interval_scope.where(sqls * ' OR ')
235
+ end
236
+
237
+ # Returns depth by counting ancestors up to 0 / 1.
238
+ def depth
239
+ n = 0
240
+ p, q = lftp, lftq
241
+ while p != 0
242
+ x = p.inverse(q)
243
+ p, q = (x * p - 1) / q, x
244
+ n += 1
245
+ end
246
+ n
247
+ end
248
+
249
+ def lft; 1.0 * lftp / lftq end
250
+ def rgt; 1.0 * rgtp / rgtq end
251
+
252
+ # Returns numerator of right end of interval.
253
+ def rgtp
254
+ case lftp
255
+ when 0 then 1
256
+ when 1 then 1
257
+ else lftq.inverse(lftp)
258
+ end
259
+ end
260
+
261
+ # Returns denominator of right end of interval.
262
+ def rgtq
263
+ case lftp
264
+ when 0 then 1
265
+ when 1 then lftq - 1
266
+ else (lftq.inverse(lftp) * lftq - 1) / lftp
267
+ end
268
+ end
269
+
270
+ # Returns left end of interval for next child.
271
+ def next_child_lft
272
+ if child = children.order('lftq DESC').first
273
+ return lftp + child.lftp, lftq + child.lftq
274
+ else
275
+ return lftp + rgtp, lftq + rgtq
276
+ end
277
+ end
278
+
279
+ # Returns left end of interval for next root.
280
+ def next_root_lft
281
+ vr = self.class.new # a virtual root
282
+ vr.set_nested_interval 0, 1
283
+ if child = nested_interval_scope.roots.order('lftq DESC').first
284
+ return vr.lftp + child.lftp, vr.lftq + child.lftq
285
+ else
286
+ return vr.lftp + vr.rgtp, vr.lftq + vr.rgtq
287
+ end
288
+ end
289
+ end
290
+ end
291
+
292
+ ActiveRecord::Base.send :include, ActsAsNestedInterval
293
+
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :acts_as_nested_interval do
3
+ # # Task goes here
4
+ # end