acts_as_nested_interval 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -2,14 +2,16 @@
2
2
 
3
3
  ## About
4
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.
5
+ Pythonic's acts_as_nested_interval updated to Rails 3 and gemified.
9
6
 
10
7
  This act implements a nested-interval tree. You can find all descendants or all
11
8
  ancestors with just one select query. You can insert and delete records without
12
- a full table update.
9
+ a full table update (compared to nested set, where at insert, half the table is updated on average).
10
+
11
+ Nested sets/intervals are good if you need to sort in preorder at DB-level.
12
+ If you don't need that give a look to https://github.com/stefankroes/ancestry ,
13
+ that implements a simpler encoding model (variant of materialized path).
14
+
13
15
 
14
16
  ## Install
15
17
 
@@ -166,6 +168,13 @@ Region.rebuild_nested_interval_tree!
166
168
  ```
167
169
 
168
170
  NOTE! About `rebuild_nested_interval_tree!`:
171
+
169
172
  * zeroes all your tree intervals before recomputing them!
170
173
  * 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.
174
+ This might change once the AR identity_map is finished.
175
+
176
+ ## Authors
177
+
178
+ * This: https://github.com/clyfe/acts_as_nested_interval
179
+ * Original: https://github.com/pythonic/acts_as_nested_interval
180
+ * Acknowledgement: http://arxiv.org/html/cs.DB/0401014 by Vadim Tropashko.
@@ -0,0 +1,35 @@
1
+ module ActsAsNestedInterval
2
+ module ClassMethods
3
+
4
+ def rebuild_nested_interval_tree!
5
+ # temporary changes
6
+ skip_callback :update, :before, :update_nested_interval
7
+ skip_callback :update, :before, :sync_childre
8
+ old_default_scopes = default_scopes # save to revert later
9
+ default_scope where("#{quoted_table_name}.lftq > 0") # use lft1 > 0 as a "migrated?" flag
10
+
11
+ # zero all intervals
12
+ update_hash = {lftp: 0, lftq: 0}
13
+ update_hash[:rgtp] = 0 if columns_hash["rgtp"]
14
+ update_hash[:rgtq] = 0 if columns_hash["rgtq"]
15
+ update_hash[:lft] = 0 if columns_hash["lft"]
16
+ update_hash[:rgt] = 0 if columns_hash["rgt"]
17
+ update_all update_hash
18
+
19
+ # recompute intervals
20
+ clear_cache!
21
+ update_subtree = lambda { |node|
22
+ node.create_nested_interval
23
+ node.save
24
+ node.class.unscoped.where(nested_interval_foreign_key => node.id).find_each &update_subtree
25
+ }
26
+ unscoped.roots.find_each &update_subtree
27
+
28
+ # revert changes
29
+ set_callback :update, :before, :update_nested_interval
30
+ set_callback :update, :before, :sync_childre
31
+ self.default_scopes = old_default_scopes
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,220 @@
1
+ module ActsAsNestedInterval
2
+ module InstanceMethods
3
+ extend ActiveSupport::Concern
4
+
5
+ # selectively define #descendants according to table features
6
+ included do
7
+
8
+ if columns_hash["lft"]
9
+
10
+ def descendants
11
+ quoted_table_name = self.class.quoted_table_name
12
+ nested_interval_scope.where <<-SQL
13
+ #{lftp} < #{quoted_table_name}.lftp AND
14
+ #{quoted_table_name}.lft BETWEEN #{1.0 * lftp / lftq} AND #{1.0 * rgtp / rgtq}
15
+ SQL
16
+ end
17
+
18
+ elsif nested_interval_lft_index
19
+
20
+ def descendants
21
+ quoted_table_name = self.class.quoted_table_name
22
+ nested_interval_scope.where <<-SQL
23
+ #{lftp} < #{quoted_table_name}.lftp AND
24
+ 1.0 * #{quoted_table_name}.lftp / #{quoted_table_name}.lftq BETWEEN
25
+ #{1.0 * lftp / lftq} AND
26
+ #{1.0 * rgtp / rgtq}
27
+ SQL
28
+ end
29
+
30
+ elsif connection.adapter_name == "MySQL"
31
+
32
+ def descendants
33
+ quoted_table_name = self.class.quoted_table_name
34
+ nested_interval_scope.where <<-SQL
35
+ ( #{quoted_table_name}.lftp != #{rgtp} OR
36
+ #{quoted_table_name}.lftq != #{rgtq}
37
+ ) AND
38
+ #{quoted_table_name}.lftp BETWEEN
39
+ 1 + #{quoted_table_name}.lftq * #{lftp} DIV #{lftq} AND
40
+ #{quoted_table_name}.lftq * #{rgtp} DIV #{rgtq}
41
+ SQL
42
+ end
43
+
44
+ else
45
+
46
+ def descendants
47
+ quoted_table_name = self.class.quoted_table_name
48
+ nested_interval_scope.where <<-SQL
49
+ ( #{quoted_table_name}.lftp != #{rgtp} OR
50
+ #{quoted_table_name}.lftq != #{rgtq}
51
+ ) AND
52
+ #{quoted_table_name}.lftp BETWEEN
53
+ 1 + #{quoted_table_name}.lftq * CAST(#{lftp} AS BIGINT) / #{lftq} AND
54
+ #{quoted_table_name}.lftq * CAST(#{rgtp} AS BIGINT) / #{rgtq}
55
+ SQL
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+
62
+ def set_nested_interval(lftp, lftq)
63
+ self.lftp, self.lftq = lftp, lftq
64
+ self.rgtp = rgtp if has_attribute?(:rgtp)
65
+ self.rgtq = rgtq if has_attribute?(:rgtq)
66
+ self.lft = lft if has_attribute?(:lft)
67
+ self.rgt = rgt if has_attribute?(:rgt)
68
+ end
69
+
70
+ def set_nested_interval_for_top
71
+ if self.class.virtual_root
72
+ set_nested_interval *next_root_lft
73
+ else
74
+ set_nested_interval 0, 1
75
+ end
76
+ end
77
+
78
+ # Creates record.
79
+ def create_nested_interval
80
+ if read_attribute(nested_interval_foreign_key).nil?
81
+ set_nested_interval_for_top
82
+ else
83
+ set_nested_interval *parent.lock!.next_child_lft
84
+ end
85
+ end
86
+
87
+ # Destroys record.
88
+ def destroy_nested_interval
89
+ lock! rescue nil
90
+ end
91
+
92
+ def nested_interval_scope
93
+ conditions = {}
94
+ nested_interval_scope_columns.each do |column_name|
95
+ conditions[column_name] = send(column_name)
96
+ end
97
+ self.class.where conditions
98
+ end
99
+
100
+ # Updates record, updating descendants if parent association updated,
101
+ # in which case caller should first acquire table lock.
102
+ def update_nested_interval
103
+ if read_attribute(nested_interval_foreign_key).nil?
104
+ set_nested_interval_for_top
105
+ elsif !association(:parent).updated?
106
+ db_self = self.class.find(id, :lock => true)
107
+ write_attribute(nested_interval_foreign_key, db_self.read_attribute(nested_interval_foreign_key))
108
+ set_nested_interval db_self.lftp, db_self.lftq
109
+ else # move
110
+ # No locking in this case -- caller should have acquired table lock.
111
+ update_nested_interval_move
112
+ end
113
+ end
114
+
115
+ def update_nested_interval_move
116
+ db_self = self.class.find(id)
117
+ db_parent = self.class.find(read_attribute(nested_interval_foreign_key))
118
+ if db_self.ancestor_of?(db_parent)
119
+ errors.add nested_interval_foreign_key, "is descendant"
120
+ raise ActiveRecord::RecordInvalid, self
121
+ end
122
+
123
+ set_nested_interval *parent.next_child_lft
124
+ mysql_tmp = "@" if ["MySQL", "Mysql2"].include?(connection.adapter_name)
125
+ cpp = db_self.lftq * rgtp - db_self.rgtq * lftp
126
+ cpq = db_self.rgtp * lftp - db_self.lftp * rgtp
127
+ cqp = db_self.lftq * rgtq - db_self.rgtq * lftq
128
+ cqq = db_self.rgtp * lftq - db_self.lftp * rgtq
129
+
130
+ db_descendants = db_self.descendants
131
+
132
+ if has_attribute?(:rgtp) && has_attribute?(:rgtq)
133
+ db_descendants.update_all %(
134
+ rgtp = #{cpp} * rgtp + #{cpq} * rgtq,
135
+ rgtq = #{cqp} * #{mysql_tmp}rgtp + #{cqq} * rgtq
136
+ ), mysql_tmp && %(@rgtp := rgtp)
137
+ db_descendants.update_all "rgt = 1.0 * rgtp / rgtq" if has_attribute?(:rgt)
138
+ end
139
+
140
+ db_descendants.update_all %(
141
+ lftp = #{cpp} * lftp + #{cpq} * lftq,
142
+ lftq = #{cqp} * #{mysql_tmp}lftp + #{cqq} * lftq
143
+ ), mysql_tmp && %(@lftp := lftp)
144
+
145
+ db_descendants.update_all %(lft = 1.0 * lftp / lftq) if has_attribute?(:lft)
146
+ end
147
+
148
+ def ancestor_of?(node)
149
+ node.lftp == lftp && node.lftq == lftq ||
150
+ node.lftp > node.lftq * lftp / lftq &&
151
+ node.lftp <= node.lftq * rgtp / rgtq &&
152
+ (node.lftp != rgtp || node.lftq != rgtq)
153
+ end
154
+
155
+ def ancestors
156
+ sqls = ["NULL"]
157
+ p, q = lftp, lftq
158
+ while p != 0
159
+ x = p.inverse(q)
160
+ p, q = (x * p - 1) / q, x
161
+ sqls << "lftq = #{q} AND lftp = #{p}"
162
+ end
163
+ nested_interval_scope.where(sqls * ' OR ')
164
+ end
165
+
166
+ # Returns depth by counting ancestors up to 0 / 1.
167
+ def depth
168
+ n = 0
169
+ p, q = lftp, lftq
170
+ while p != 0
171
+ x = p.inverse(q)
172
+ p, q = (x * p - 1) / q, x
173
+ n += 1
174
+ end
175
+ n
176
+ end
177
+
178
+ def lft; 1.0 * lftp / lftq end
179
+ def rgt; 1.0 * rgtp / rgtq end
180
+
181
+ # Returns numerator of right end of interval.
182
+ def rgtp
183
+ case lftp
184
+ when 0 then 1
185
+ when 1 then 1
186
+ else lftq.inverse(lftp)
187
+ end
188
+ end
189
+
190
+ # Returns denominator of right end of interval.
191
+ def rgtq
192
+ case lftp
193
+ when 0 then 1
194
+ when 1 then lftq - 1
195
+ else (lftq.inverse(lftp) * lftq - 1) / lftp
196
+ end
197
+ end
198
+
199
+ # Returns left end of interval for next child.
200
+ def next_child_lft
201
+ if child = children.order('lftq DESC').first
202
+ return lftp + child.lftp, lftq + child.lftq
203
+ else
204
+ return lftp + rgtp, lftq + rgtq
205
+ end
206
+ end
207
+
208
+ # Returns left end of interval for next root.
209
+ def next_root_lft
210
+ vr = self.class.new # a virtual root
211
+ vr.set_nested_interval 0, 1
212
+ if child = nested_interval_scope.roots.order('lftq DESC').first
213
+ return vr.lftp + child.lftp, vr.lftq + child.lftq
214
+ else
215
+ return vr.lftp + vr.rgtp, vr.lftq + vr.rgtq
216
+ end
217
+ end
218
+
219
+ end
220
+ end
@@ -1,3 +1,3 @@
1
1
  module ActsAsNestedInterval
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -4,292 +4,53 @@
4
4
  # Copyright (c) 2012 Nicolae Claudius
5
5
  # https://github.com/clyfe
6
6
 
7
- require 'acts_as_nested_interval/version'
8
7
  require 'acts_as_nested_interval/core_ext/integer'
8
+ require 'acts_as_nested_interval/version'
9
+ require 'acts_as_nested_interval/instance_methods'
10
+ require 'acts_as_nested_interval/class_methods'
9
11
 
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
12
+ # This act implements a nested-interval tree. You can find all descendants
13
+ # or all ancestors with just one select query. You can insert and delete
14
+ # records without a full table update.
15
+ module ActsAsNestedInterval
17
16
 
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
- # temporary changes
101
- skip_callback :update, :before, :update_nested_interval
102
- skip_callback :update, :before, :sync_childre
103
- old_default_scopes = default_scopes # save to revert later
104
- default_scope where("#{quoted_table_name}.lftq > 0") # use lft1 > 0 as a "migrated?" flag
105
-
106
- # zero all intervals
107
- update_hash = {lftp: 0, lftq: 0}
108
- update_hash[:rgtp] = 0 if columns_hash["rgtp"]
109
- update_hash[:rgtq] = 0 if columns_hash["rgtq"]
110
- update_hash[:lft] = 0 if columns_hash["lft"]
111
- update_hash[:rgt] = 0 if columns_hash["rgt"]
112
- update_all update_hash
113
-
114
- # recompute intervals
115
- clear_cache!
116
- update_subtree = lambda { |node|
117
- node.create_nested_interval
118
- node.save
119
- node.class.unscoped.where(nested_interval_foreign_key => node.id).find_each &update_subtree
120
- }
121
- unscoped.roots.find_each &update_subtree
122
-
123
- # revert changes
124
- set_callback :update, :before, :update_nested_interval
125
- set_callback :update, :before, :sync_childre
126
- self.default_scopes = old_default_scopes
127
- end
128
-
129
- end
130
-
131
- end
17
+ # The +options+ hash can include:
18
+ # * <tt>:foreign_key</tt> -- the self-reference foreign key column name (default :parent_id).
19
+ # * <tt>:scope_columns</tt> -- an array of columns to scope independent trees.
20
+ # * <tt>:lft_index</tt> -- whether to use functional index for lft (default false).
21
+ # * <tt>:virtual_root</tt> -- whether to compute root's interval as in an upper root (default false)
22
+ def acts_as_nested_interval(options = {})
23
+ cattr_accessor :nested_interval_foreign_key
24
+ cattr_accessor :nested_interval_scope_columns
25
+ cattr_accessor :nested_interval_lft_index
26
+
27
+ cattr_accessor :virtual_root
28
+ self.virtual_root = !!options[:virtual_root]
29
+
30
+ self.nested_interval_foreign_key = options[:foreign_key] || :parent_id
31
+ self.nested_interval_scope_columns = Array(options[:scope_columns])
32
+ self.nested_interval_lft_index = options[:lft_index]
33
+
34
+ belongs_to :parent, class_name: name, foreign_key: nested_interval_foreign_key
35
+ has_many :children, class_name: name, foreign_key: nested_interval_foreign_key, dependent: :destroy
36
+ scope :roots, where(nested_interval_foreign_key => nil)
37
+
38
+ if columns_hash["rgt"]
39
+ scope :preorder, order('rgt DESC, lftp ASC')
40
+ elsif columns_hash["rgtp"] && columns_hash["rgtq"]
41
+ scope :preorder, order('1.0 * rgtp / rgtq DESC, lftp ASC')
42
+ else
43
+ scope :preorder, order('nested_interval_rgt(lftp, lftq) DESC, lftp ASC')
44
+ end
45
+
46
+ before_create :create_nested_interval
47
+ before_destroy :destroy_nested_interval
48
+ before_update :update_nested_interval
49
+
50
+ include ActsAsNestedInterval::InstanceMethods
51
+ extend ActsAsNestedInterval::ClassMethods
132
52
  end
133
53
 
134
- module NodeInstanceMethods
135
- def set_nested_interval(lftp, lftq)
136
- self.lftp, self.lftq = lftp, lftq
137
- self.rgtp = rgtp if has_attribute?(:rgtp)
138
- self.rgtq = rgtq if has_attribute?(:rgtq)
139
- self.lft = lft if has_attribute?(:lft)
140
- self.rgt = rgt if has_attribute?(:rgt)
141
- end
142
-
143
- def set_nested_interval_for_top
144
- if self.class.virtual_root
145
- set_nested_interval *next_root_lft
146
- else
147
- set_nested_interval 0, 1
148
- end
149
- end
150
-
151
- # Creates record.
152
- def create_nested_interval
153
- if read_attribute(nested_interval_foreign_key).nil?
154
- set_nested_interval_for_top
155
- else
156
- set_nested_interval *parent.lock!.next_child_lft
157
- end
158
- end
159
-
160
- # Destroys record.
161
- def destroy_nested_interval
162
- lock! rescue nil
163
- end
164
-
165
- def nested_interval_scope
166
- conditions = {}
167
- nested_interval_scope_columns.each do |column_name|
168
- conditions[column_name] = send(column_name)
169
- end
170
- self.class.where conditions
171
- end
172
-
173
- # Updates record, updating descendants if parent association updated,
174
- # in which case caller should first acquire table lock.
175
- def update_nested_interval
176
- if read_attribute(nested_interval_foreign_key).nil?
177
- set_nested_interval_for_top
178
- elsif !association(:parent).updated?
179
- db_self = self.class.find(id, :lock => true)
180
- write_attribute(nested_interval_foreign_key, db_self.read_attribute(nested_interval_foreign_key))
181
- set_nested_interval db_self.lftp, db_self.lftq
182
- else # move
183
- # No locking in this case -- caller should have acquired table lock.
184
- update_nested_interval_move
185
- end
186
- end
187
-
188
- def update_nested_interval_move
189
- db_self = self.class.find(id)
190
- db_parent = self.class.find(read_attribute(nested_interval_foreign_key))
191
- if db_self.ancestor_of?(db_parent)
192
- errors.add nested_interval_foreign_key, "is descendant"
193
- raise ActiveRecord::RecordInvalid, self
194
- end
195
-
196
- set_nested_interval *parent.next_child_lft
197
- mysql_tmp = "@" if ["MySQL", "Mysql2"].include?(connection.adapter_name)
198
- cpp = db_self.lftq * rgtp - db_self.rgtq * lftp
199
- cpq = db_self.rgtp * lftp - db_self.lftp * rgtp
200
- cqp = db_self.lftq * rgtq - db_self.rgtq * lftq
201
- cqq = db_self.rgtp * lftq - db_self.lftp * rgtq
202
-
203
- db_descendants = db_self.descendants
204
-
205
- if has_attribute?(:rgtp) && has_attribute?(:rgtq)
206
- db_descendants.update_all %(
207
- rgtp = #{cpp} * rgtp + #{cpq} * rgtq,
208
- rgtq = #{cqp} * #{mysql_tmp}rgtp + #{cqq} * rgtq
209
- ), mysql_tmp && %(@rgtp := rgtp)
210
- db_descendants.update_all "rgt = 1.0 * rgtp / rgtq" if has_attribute?(:rgt)
211
- end
212
-
213
- db_descendants.update_all %(
214
- lftp = #{cpp} * lftp + #{cpq} * lftq,
215
- lftq = #{cqp} * #{mysql_tmp}lftp + #{cqq} * lftq
216
- ), mysql_tmp && %(@lftp := lftp)
217
-
218
- db_descendants.update_all %(lft = 1.0 * lftp / lftq) if has_attribute?(:lft)
219
- end
220
-
221
- def ancestor_of?(node)
222
- node.lftp == lftp && node.lftq == lftq ||
223
- node.lftp > node.lftq * lftp / lftq &&
224
- node.lftp <= node.lftq * rgtp / rgtq &&
225
- (node.lftp != rgtp || node.lftq != rgtq)
226
- end
227
-
228
- def ancestors
229
- sqls = ["NULL"]
230
- p, q = lftp, lftq
231
- while p != 0
232
- x = p.inverse(q)
233
- p, q = (x * p - 1) / q, x
234
- sqls << "lftq = #{q} AND lftp = #{p}"
235
- end
236
- nested_interval_scope.where(sqls * ' OR ')
237
- end
238
-
239
- # Returns depth by counting ancestors up to 0 / 1.
240
- def depth
241
- n = 0
242
- p, q = lftp, lftq
243
- while p != 0
244
- x = p.inverse(q)
245
- p, q = (x * p - 1) / q, x
246
- n += 1
247
- end
248
- n
249
- end
250
-
251
- def lft; 1.0 * lftp / lftq end
252
- def rgt; 1.0 * rgtp / rgtq end
253
-
254
- # Returns numerator of right end of interval.
255
- def rgtp
256
- case lftp
257
- when 0 then 1
258
- when 1 then 1
259
- else lftq.inverse(lftp)
260
- end
261
- end
262
-
263
- # Returns denominator of right end of interval.
264
- def rgtq
265
- case lftp
266
- when 0 then 1
267
- when 1 then lftq - 1
268
- else (lftq.inverse(lftp) * lftq - 1) / lftp
269
- end
270
- end
271
-
272
- # Returns left end of interval for next child.
273
- def next_child_lft
274
- if child = children.order('lftq DESC').first
275
- return lftp + child.lftp, lftq + child.lftq
276
- else
277
- return lftp + rgtp, lftq + rgtq
278
- end
279
- end
280
-
281
- # Returns left end of interval for next root.
282
- def next_root_lft
283
- vr = self.class.new # a virtual root
284
- vr.set_nested_interval 0, 1
285
- if child = nested_interval_scope.roots.order('lftq DESC').first
286
- return vr.lftp + child.lftp, vr.lftq + child.lftq
287
- else
288
- return vr.lftp + vr.rgtp, vr.lftq + vr.rgtq
289
- end
290
- end
291
- end
292
54
  end
293
55
 
294
- ActiveRecord::Base.send :include, ActsAsNestedInterval
295
-
56
+ ActiveRecord::Base.send :extend, ActsAsNestedInterval