acts_as_nested_interval 0.0.2 → 0.0.4

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