awesome_nested_set 2.0.1 → 2.1.2
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/CHANGELOG +24 -0
- data/README.rdoc +24 -6
- data/lib/awesome_nested_set/awesome_nested_set.rb +369 -269
- data/lib/awesome_nested_set/helper.rb +53 -4
- data/lib/awesome_nested_set/version.rb +2 -2
- metadata +20 -5
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
2.1.1
|
|
2
|
+
* Added 'depth' which indicates how many levels deep the node is.
|
|
3
|
+
This only works when you have a column called 'depth' in your table,
|
|
4
|
+
otherwise it doesn't set itself. [Philip Arndt]
|
|
5
|
+
* Rails 3.2 support added. [Gabriel Sobrinho]
|
|
6
|
+
* Oracle compatibility added. [Pikender Sharma]
|
|
7
|
+
* Adding row locking to deletion, locking source of pivot values, and adding retry on collisions. [Markus J. Q. Roberts]
|
|
8
|
+
* Added method and helper for sorting children by column. [bluegod]
|
|
9
|
+
* Fixed .all_roots_valid? to work with Postgres. [Joshua Clayton]
|
|
10
|
+
* Made compatible with polymorphic belongs_to. [Graham Randall]
|
|
11
|
+
* Added in the association callbacks to the children :has_many association. [Michael Deering]
|
|
12
|
+
* Modified helper to allow using array of objects as argument. [Rahmat Budiharso]
|
|
13
|
+
* Fixed cases where we were calling attr_protected. [Jacob Swanner]
|
|
14
|
+
* Fixed nil cases involving lft and rgt. [Stuart Coyle] and [Patrick Morgan]
|
|
15
|
+
|
|
16
|
+
2.0.2
|
|
17
|
+
* Fixed deprecation warning under Rails 3.1 [Philip Arndt]
|
|
18
|
+
* Converted Test::Unit matchers to RSpec. [Uģis Ozols]
|
|
19
|
+
* Added inverse_of to associations to improve performance rendering trees. [Sergio Cambra]
|
|
20
|
+
* Added row locking and fixed some race conditions. [Markus J. Q. Roberts]
|
|
21
|
+
|
|
22
|
+
2.0.1
|
|
23
|
+
* Fixed a bug with move_to not using nested_set_scope [Andreas Sekine]
|
|
24
|
+
|
|
1
25
|
2.0.0.pre
|
|
2
26
|
* Expect Rails 3
|
|
3
27
|
* Changed how callbacks work. Returning false in a before_move action does not block save operations. Use a validation or exception in the callback if you need that.
|
data/README.rdoc
CHANGED
|
@@ -16,7 +16,8 @@ This is a new implementation of nested set based off of BetterNestedSet that fix
|
|
|
16
16
|
|
|
17
17
|
== Usage
|
|
18
18
|
|
|
19
|
-
To make use of awesome_nested_set, your model needs to have 3 fields: lft, rgt, and parent_id
|
|
19
|
+
To make use of awesome_nested_set, your model needs to have 3 fields: lft, rgt, and parent_id.
|
|
20
|
+
You can also have an optional field: depth:
|
|
20
21
|
|
|
21
22
|
class CreateCategories < ActiveRecord::Migration
|
|
22
23
|
def self.up
|
|
@@ -25,6 +26,7 @@ To make use of awesome_nested_set, your model needs to have 3 fields: lft, rgt,
|
|
|
25
26
|
t.integer :parent_id
|
|
26
27
|
t.integer :lft
|
|
27
28
|
t.integer :rgt
|
|
29
|
+
t.integer :depth # this is optional.
|
|
28
30
|
end
|
|
29
31
|
end
|
|
30
32
|
|
|
@@ -39,7 +41,23 @@ Enable the nested set functionality by declaring acts_as_nested_set on your mode
|
|
|
39
41
|
acts_as_nested_set
|
|
40
42
|
end
|
|
41
43
|
|
|
42
|
-
Run `rake rdoc` to generate the API docs and see CollectiveIdea::Acts::NestedSet
|
|
44
|
+
Run `rake rdoc` to generate the API docs and see CollectiveIdea::Acts::NestedSet for more info.
|
|
45
|
+
|
|
46
|
+
== Protecting attributes from mass assignment
|
|
47
|
+
|
|
48
|
+
It's generally best to "white list" the attributes that can be used in mass assignment:
|
|
49
|
+
|
|
50
|
+
class Category < ActiveRecord::Base
|
|
51
|
+
acts_as_nested_set
|
|
52
|
+
attr_accessible :name, :parent_id
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
If for some reason that is not possible, you will probably want to protect the lft and rgt attributes:
|
|
56
|
+
|
|
57
|
+
class Category < ActiveRecord::Base
|
|
58
|
+
acts_as_nested_set
|
|
59
|
+
attr_protected :lft, :rgt
|
|
60
|
+
end
|
|
43
61
|
|
|
44
62
|
== Conversion from other trees
|
|
45
63
|
|
|
@@ -70,15 +88,15 @@ You can learn more about nested sets at: http://threebit.net/tutorials/nestedset
|
|
|
70
88
|
If you find what you might think is a bug:
|
|
71
89
|
|
|
72
90
|
1. Check the GitHub issue tracker to see if anyone else has had the same issue.
|
|
73
|
-
|
|
91
|
+
https://github.com/collectiveidea/awesome_nested_set/issues/
|
|
74
92
|
2. If you don't see anything, create an issue with information on how to reproduce it.
|
|
75
93
|
|
|
76
94
|
If you want to contribute an enhancement or a fix:
|
|
77
95
|
|
|
78
|
-
1. Fork the project on
|
|
79
|
-
|
|
96
|
+
1. Fork the project on GitHub.
|
|
97
|
+
https://github.com/collectiveidea/awesome_nested_set/
|
|
80
98
|
2. Make your changes with tests.
|
|
81
99
|
3. Commit the changes without making changes to the Rakefile, VERSION, or any other files that aren't related to your enhancement or fix
|
|
82
100
|
4. Send a pull request.
|
|
83
101
|
|
|
84
|
-
Copyright ©2008 Collective Idea, released under the MIT license
|
|
102
|
+
Copyright ©2008 Collective Idea, released under the MIT license
|
|
@@ -23,6 +23,7 @@ module CollectiveIdea #:nodoc:
|
|
|
23
23
|
# * +:parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
|
|
24
24
|
# * +:left_column+ - column name for left boundry data, default "lft"
|
|
25
25
|
# * +:right_column+ - column name for right boundry data, default "rgt"
|
|
26
|
+
# * +:depth_column+ - column name for the depth data, default "depth"
|
|
26
27
|
# * +:scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
|
|
27
28
|
# (if it hasn't been already) and use that as the foreign key restriction. You
|
|
28
29
|
# can also pass an array to scope by multiple attributes.
|
|
@@ -36,14 +37,16 @@ module CollectiveIdea #:nodoc:
|
|
|
36
37
|
# Example: <tt>acts_as_nested_set :counter_cache => :children_count</tt>
|
|
37
38
|
#
|
|
38
39
|
# See CollectiveIdea::Acts::NestedSet::Model::ClassMethods for a list of class methods and
|
|
39
|
-
# CollectiveIdea::Acts::NestedSet::Model
|
|
40
|
+
# CollectiveIdea::Acts::NestedSet::Model for a list of instance methods added
|
|
40
41
|
# to acts_as_nested_set models
|
|
41
42
|
def acts_as_nested_set(options = {})
|
|
42
43
|
options = {
|
|
43
44
|
:parent_column => 'parent_id',
|
|
44
45
|
:left_column => 'lft',
|
|
45
46
|
:right_column => 'rgt',
|
|
47
|
+
:depth_column => 'depth',
|
|
46
48
|
:dependent => :delete_all, # or :destroy
|
|
49
|
+
:polymorphic => false,
|
|
47
50
|
:counter_cache => false
|
|
48
51
|
}.merge(options)
|
|
49
52
|
|
|
@@ -51,33 +54,42 @@ module CollectiveIdea #:nodoc:
|
|
|
51
54
|
options[:scope] = "#{options[:scope]}_id".intern
|
|
52
55
|
end
|
|
53
56
|
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
class_attribute :acts_as_nested_set_options
|
|
58
|
+
self.acts_as_nested_set_options = options
|
|
56
59
|
|
|
57
60
|
include CollectiveIdea::Acts::NestedSet::Model
|
|
58
61
|
include Columns
|
|
59
62
|
extend Columns
|
|
60
63
|
|
|
61
64
|
belongs_to :parent, :class_name => self.base_class.to_s,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
:foreign_key => parent_column_name,
|
|
66
|
+
:counter_cache => options[:counter_cache],
|
|
67
|
+
:inverse_of => (:children unless options[:polymorphic]),
|
|
68
|
+
:polymorphic => options[:polymorphic]
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
has_many_children_options = {
|
|
71
|
+
:class_name => self.base_class.to_s,
|
|
72
|
+
:foreign_key => parent_column_name,
|
|
73
|
+
:order => left_column_name,
|
|
74
|
+
:inverse_of => (:parent unless options[:polymorphic]),
|
|
75
|
+
}
|
|
68
76
|
|
|
69
|
-
#
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
# Add callbacks, if they were supplied.. otherwise, we don't want them.
|
|
78
|
+
[:before_add, :after_add, :before_remove, :after_remove].each do |ar_callback|
|
|
79
|
+
has_many_children_options.update(ar_callback => options[ar_callback]) if options[ar_callback]
|
|
72
80
|
end
|
|
73
81
|
|
|
82
|
+
has_many :children, has_many_children_options
|
|
83
|
+
|
|
84
|
+
attr_accessor :skip_before_destroy
|
|
85
|
+
|
|
74
86
|
before_create :set_default_left_and_right
|
|
75
87
|
before_save :store_new_parent
|
|
76
|
-
after_save :move_to_new_parent
|
|
88
|
+
after_save :move_to_new_parent, :set_depth!
|
|
77
89
|
before_destroy :destroy_descendants
|
|
78
90
|
|
|
79
91
|
# no assignment to structure fields
|
|
80
|
-
[left_column_name, right_column_name].each do |column|
|
|
92
|
+
[left_column_name, right_column_name, depth_column_name].each do |column|
|
|
81
93
|
module_eval <<-"end_eval", __FILE__, __LINE__
|
|
82
94
|
def #{column}=(x)
|
|
83
95
|
raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
|
|
@@ -85,10 +97,7 @@ module CollectiveIdea #:nodoc:
|
|
|
85
97
|
end_eval
|
|
86
98
|
end
|
|
87
99
|
|
|
88
|
-
|
|
89
|
-
scope :leaves, where("#{quoted_right_column_name} - #{quoted_left_column_name} = 1").order(quoted_left_column_name)
|
|
90
|
-
|
|
91
|
-
define_callbacks :move, :terminator => "result == false"
|
|
100
|
+
define_model_callbacks :move
|
|
92
101
|
end
|
|
93
102
|
|
|
94
103
|
module Model
|
|
@@ -100,12 +109,23 @@ module CollectiveIdea #:nodoc:
|
|
|
100
109
|
roots.first
|
|
101
110
|
end
|
|
102
111
|
|
|
112
|
+
def roots
|
|
113
|
+
where(parent_column_name => nil).order(quoted_left_column_name)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def leaves
|
|
117
|
+
where("#{quoted_right_column_name} - #{quoted_left_column_name} = 1").order(quoted_left_column_name)
|
|
118
|
+
end
|
|
119
|
+
|
|
103
120
|
def valid?
|
|
104
121
|
left_and_rights_valid? && no_duplicates_for_columns? && all_roots_valid?
|
|
105
122
|
end
|
|
106
123
|
|
|
107
124
|
def left_and_rights_valid?
|
|
108
|
-
|
|
125
|
+
## AS clause not supported in Oracle in FROM clause for aliasing table name
|
|
126
|
+
joins("LEFT OUTER JOIN #{quoted_table_name}" +
|
|
127
|
+
(connection.adapter_name.match(/Oracle/).nil? ? " AS " : " ") +
|
|
128
|
+
"parent ON " +
|
|
109
129
|
"#{quoted_table_name}.#{quoted_parent_column_name} = parent.#{primary_key}").
|
|
110
130
|
where(
|
|
111
131
|
"#{quoted_table_name}.#{quoted_left_column_name} IS NULL OR " +
|
|
@@ -134,7 +154,7 @@ module CollectiveIdea #:nodoc:
|
|
|
134
154
|
# Wrapper for each_root_valid? that can deal with scope.
|
|
135
155
|
def all_roots_valid?
|
|
136
156
|
if acts_as_nested_set_options[:scope]
|
|
137
|
-
roots.
|
|
157
|
+
roots.group_by {|record| scope_column_names.collect {|col| record.send(col.to_sym) } }.all? do |scope, grouped_roots|
|
|
138
158
|
each_root_valid?(grouped_roots)
|
|
139
159
|
end
|
|
140
160
|
else
|
|
@@ -198,7 +218,7 @@ module CollectiveIdea #:nodoc:
|
|
|
198
218
|
path = [nil]
|
|
199
219
|
objects.each do |o|
|
|
200
220
|
if o.parent_id != path.last
|
|
201
|
-
# we are on a new level, did we
|
|
221
|
+
# we are on a new level, did we descend or ascend?
|
|
202
222
|
if path.include?(o.parent_id)
|
|
203
223
|
# remove wrong wrong tailing paths elements
|
|
204
224
|
path.pop while path.last != o.parent_id
|
|
@@ -209,317 +229,388 @@ module CollectiveIdea #:nodoc:
|
|
|
209
229
|
yield(o, path.length - 1)
|
|
210
230
|
end
|
|
211
231
|
end
|
|
232
|
+
|
|
233
|
+
# Same as each_with_level - Accepts a string as a second argument to sort the list
|
|
234
|
+
# Example:
|
|
235
|
+
# Category.each_with_level(Category.root.self_and_descendants, :sort_by_this_column) do |o, level|
|
|
236
|
+
def sorted_each_with_level(objects, order)
|
|
237
|
+
path = [nil]
|
|
238
|
+
children = []
|
|
239
|
+
objects.each do |o|
|
|
240
|
+
children << o if o.leaf?
|
|
241
|
+
if o.parent_id != path.last
|
|
242
|
+
if !children.empty? && !o.leaf?
|
|
243
|
+
children.sort_by! &order
|
|
244
|
+
children.each { |c| yield(c, path.length-1) }
|
|
245
|
+
children = []
|
|
246
|
+
end
|
|
247
|
+
# we are on a new level, did we decent or ascent?
|
|
248
|
+
if path.include?(o.parent_id)
|
|
249
|
+
# remove wrong wrong tailing paths elements
|
|
250
|
+
path.pop while path.last != o.parent_id
|
|
251
|
+
else
|
|
252
|
+
path << o.parent_id
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
yield(o,path.length-1) if !o.leaf?
|
|
256
|
+
end
|
|
257
|
+
if !children.empty?
|
|
258
|
+
children.sort_by! &order
|
|
259
|
+
children.each { |c| yield(c, path.length-1) }
|
|
260
|
+
end
|
|
261
|
+
end
|
|
212
262
|
end
|
|
213
263
|
|
|
214
264
|
# Any instance method that returns a collection makes use of Rails 2.1's named_scope (which is bundled for Rails 2.0), so it can be treated as a finder.
|
|
215
265
|
#
|
|
216
266
|
# category.self_and_descendants.count
|
|
217
267
|
# category.ancestors.find(:all, :conditions => "name like '%foo%'")
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
end
|
|
268
|
+
# Value of the parent column
|
|
269
|
+
def parent_id
|
|
270
|
+
self[parent_column_name]
|
|
271
|
+
end
|
|
223
272
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
273
|
+
# Value of the left column
|
|
274
|
+
def left
|
|
275
|
+
self[left_column_name]
|
|
276
|
+
end
|
|
228
277
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
278
|
+
# Value of the right column
|
|
279
|
+
def right
|
|
280
|
+
self[right_column_name]
|
|
281
|
+
end
|
|
233
282
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
283
|
+
# Returns true if this is a root node.
|
|
284
|
+
def root?
|
|
285
|
+
parent_id.nil?
|
|
286
|
+
end
|
|
238
287
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
288
|
+
# Returns true if this is the end of a branch.
|
|
289
|
+
def leaf?
|
|
290
|
+
persisted? && right.to_i - left.to_i == 1
|
|
291
|
+
end
|
|
242
292
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
293
|
+
# Returns true is this is a child node
|
|
294
|
+
def child?
|
|
295
|
+
!parent_id.nil?
|
|
296
|
+
end
|
|
247
297
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
298
|
+
# Returns root
|
|
299
|
+
def root
|
|
300
|
+
self_and_ancestors.where(parent_column_name => nil).first
|
|
301
|
+
end
|
|
252
302
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
303
|
+
# Returns the array of all parents and self
|
|
304
|
+
def self_and_ancestors
|
|
305
|
+
nested_set_scope.where([
|
|
306
|
+
"#{self.class.quoted_table_name}.#{quoted_left_column_name} <= ? AND #{self.class.quoted_table_name}.#{quoted_right_column_name} >= ?", left, right
|
|
307
|
+
])
|
|
308
|
+
end
|
|
259
309
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
310
|
+
# Returns an array of all parents
|
|
311
|
+
def ancestors
|
|
312
|
+
without_self self_and_ancestors
|
|
313
|
+
end
|
|
264
314
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
315
|
+
# Returns the array of all children of the parent, including self
|
|
316
|
+
def self_and_siblings
|
|
317
|
+
nested_set_scope.where(parent_column_name => parent_id)
|
|
318
|
+
end
|
|
269
319
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
320
|
+
# Returns the array of all children of the parent, except self
|
|
321
|
+
def siblings
|
|
322
|
+
without_self self_and_siblings
|
|
323
|
+
end
|
|
274
324
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
325
|
+
# Returns a set of all of its nested children which do not have children
|
|
326
|
+
def leaves
|
|
327
|
+
descendants.where("#{self.class.quoted_table_name}.#{quoted_right_column_name} - #{self.class.quoted_table_name}.#{quoted_left_column_name} = 1")
|
|
328
|
+
end
|
|
279
329
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
330
|
+
# Returns the level of this object in the tree
|
|
331
|
+
# root level is 0
|
|
332
|
+
def level
|
|
333
|
+
parent_id.nil? ? 0 : ancestors.count
|
|
334
|
+
end
|
|
285
335
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
336
|
+
# Returns a set of itself and all of its nested children
|
|
337
|
+
def self_and_descendants
|
|
338
|
+
nested_set_scope.where([
|
|
339
|
+
"#{self.class.quoted_table_name}.#{quoted_left_column_name} >= ? AND #{self.class.quoted_table_name}.#{quoted_right_column_name} <= ?", left, right
|
|
340
|
+
])
|
|
341
|
+
end
|
|
292
342
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
343
|
+
# Returns a set of all of its children and nested children
|
|
344
|
+
def descendants
|
|
345
|
+
without_self self_and_descendants
|
|
346
|
+
end
|
|
297
347
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
348
|
+
def is_descendant_of?(other)
|
|
349
|
+
other.left < self.left && self.left < other.right && same_scope?(other)
|
|
350
|
+
end
|
|
301
351
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
352
|
+
def is_or_is_descendant_of?(other)
|
|
353
|
+
other.left <= self.left && self.left < other.right && same_scope?(other)
|
|
354
|
+
end
|
|
305
355
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
356
|
+
def is_ancestor_of?(other)
|
|
357
|
+
self.left < other.left && other.left < self.right && same_scope?(other)
|
|
358
|
+
end
|
|
309
359
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
360
|
+
def is_or_is_ancestor_of?(other)
|
|
361
|
+
self.left <= other.left && other.left < self.right && same_scope?(other)
|
|
362
|
+
end
|
|
313
363
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
end
|
|
364
|
+
# Check if other model is in the same scope
|
|
365
|
+
def same_scope?(other)
|
|
366
|
+
Array(acts_as_nested_set_options[:scope]).all? do |attr|
|
|
367
|
+
self.send(attr) == other.send(attr)
|
|
319
368
|
end
|
|
369
|
+
end
|
|
320
370
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
371
|
+
# Find the first sibling to the left
|
|
372
|
+
def left_sibling
|
|
373
|
+
siblings.where(["#{self.class.quoted_table_name}.#{quoted_left_column_name} < ?", left]).
|
|
374
|
+
order("#{self.class.quoted_table_name}.#{quoted_left_column_name} DESC").last
|
|
375
|
+
end
|
|
326
376
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
377
|
+
# Find the first sibling to the right
|
|
378
|
+
def right_sibling
|
|
379
|
+
siblings.where(["#{self.class.quoted_table_name}.#{quoted_left_column_name} > ?", left]).first
|
|
380
|
+
end
|
|
331
381
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
382
|
+
# Shorthand method for finding the left sibling and moving to the left of it.
|
|
383
|
+
def move_left
|
|
384
|
+
move_to_left_of left_sibling
|
|
385
|
+
end
|
|
336
386
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
387
|
+
# Shorthand method for finding the right sibling and moving to the right of it.
|
|
388
|
+
def move_right
|
|
389
|
+
move_to_right_of right_sibling
|
|
390
|
+
end
|
|
341
391
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
392
|
+
# Move the node to the left of another node (you can pass id only)
|
|
393
|
+
def move_to_left_of(node)
|
|
394
|
+
move_to node, :left
|
|
395
|
+
end
|
|
346
396
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
397
|
+
# Move the node to the left of another node (you can pass id only)
|
|
398
|
+
def move_to_right_of(node)
|
|
399
|
+
move_to node, :right
|
|
400
|
+
end
|
|
351
401
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
402
|
+
# Move the node to the child of another node (you can pass id only)
|
|
403
|
+
def move_to_child_of(node)
|
|
404
|
+
move_to node, :child
|
|
405
|
+
end
|
|
356
406
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
407
|
+
# Move the node to root nodes
|
|
408
|
+
def move_to_root
|
|
409
|
+
move_to nil, :root
|
|
410
|
+
end
|
|
361
411
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
412
|
+
def move_possible?(target)
|
|
413
|
+
self != target && # Can't target self
|
|
414
|
+
same_scope?(target) && # can't be in different scopes
|
|
415
|
+
# !(left..right).include?(target.left..target.right) # this needs tested more
|
|
416
|
+
# detect impossible move
|
|
417
|
+
!((left <= target.left && right >= target.left) or (left <= target.right && right >= target.right))
|
|
418
|
+
end
|
|
369
419
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
420
|
+
def to_text
|
|
421
|
+
self_and_descendants.map do |node|
|
|
422
|
+
"#{'*'*(node.level+1)} #{node.id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})"
|
|
423
|
+
end.join("\n")
|
|
424
|
+
end
|
|
375
425
|
|
|
376
|
-
|
|
426
|
+
protected
|
|
377
427
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
428
|
+
def without_self(scope)
|
|
429
|
+
scope.where(["#{self.class.quoted_table_name}.#{self.class.primary_key} != ?", self])
|
|
430
|
+
end
|
|
381
431
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
432
|
+
# All nested set queries should use this nested_set_scope, which performs finds on
|
|
433
|
+
# the base ActiveRecord class, using the :scope declared in the acts_as_nested_set
|
|
434
|
+
# declaration.
|
|
435
|
+
def nested_set_scope(options = {})
|
|
436
|
+
options = {:order => quoted_left_column_name}.merge(options)
|
|
437
|
+
scopes = Array(acts_as_nested_set_options[:scope])
|
|
438
|
+
options[:conditions] = scopes.inject({}) do |conditions,attr|
|
|
439
|
+
conditions.merge attr => self[attr]
|
|
440
|
+
end unless scopes.empty?
|
|
441
|
+
self.class.base_class.unscoped.scoped options
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def store_new_parent
|
|
445
|
+
@move_to_new_parent_id = send("#{parent_column_name}_changed?") ? parent_id : false
|
|
446
|
+
true # force callback to return true
|
|
447
|
+
end
|
|
393
448
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
449
|
+
def move_to_new_parent
|
|
450
|
+
if @move_to_new_parent_id.nil?
|
|
451
|
+
move_to_root
|
|
452
|
+
elsif @move_to_new_parent_id
|
|
453
|
+
move_to_child_of(@move_to_new_parent_id)
|
|
397
454
|
end
|
|
455
|
+
end
|
|
398
456
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
457
|
+
def set_depth!
|
|
458
|
+
if nested_set_scope.column_names.map(&:to_s).include?(depth_column_name.to_s)
|
|
459
|
+
in_tenacious_transaction do
|
|
460
|
+
reload
|
|
461
|
+
|
|
462
|
+
nested_set_scope.where(:id => id).update_all(["#{quoted_depth_column_name} = ?", level])
|
|
404
463
|
end
|
|
464
|
+
self[depth_column_name.to_sym] = self.level
|
|
405
465
|
end
|
|
466
|
+
end
|
|
406
467
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
468
|
+
# on creation, set automatically lft and rgt to the end of the tree
|
|
469
|
+
def set_default_left_and_right
|
|
470
|
+
highest_right_row = nested_set_scope(:order => "#{quoted_right_column_name} desc").find(:first, :limit => 1,:lock => true )
|
|
471
|
+
maxright = highest_right_row ? (highest_right_row[right_column_name] || 0) : 0
|
|
472
|
+
# adds the new node to the right of all existing nodes
|
|
473
|
+
self[left_column_name] = maxright + 1
|
|
474
|
+
self[right_column_name] = maxright + 2
|
|
475
|
+
end
|
|
414
476
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
477
|
+
def in_tenacious_transaction(&block)
|
|
478
|
+
retry_count = 0
|
|
479
|
+
begin
|
|
480
|
+
transaction(&block)
|
|
481
|
+
rescue ActiveRecord::StatementInvalid => error
|
|
482
|
+
raise unless connection.open_transactions.zero?
|
|
483
|
+
raise unless error.message =~ /Deadlock found when trying to get lock|Lock wait timeout exceeded/
|
|
484
|
+
raise unless retry_count < 10
|
|
485
|
+
retry_count += 1
|
|
486
|
+
logger.info "Deadlock detected on retry #{retry_count}, restarting transaction"
|
|
487
|
+
sleep(rand(retry_count)*0.1) # Aloha protocol
|
|
488
|
+
retry
|
|
489
|
+
end
|
|
490
|
+
end
|
|
419
491
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
492
|
+
# Prunes a branch off of the tree, shifting all of the elements on the right
|
|
493
|
+
# back to the left so the counts still work.
|
|
494
|
+
def destroy_descendants
|
|
495
|
+
return if right.nil? || left.nil? || skip_before_destroy
|
|
496
|
+
|
|
497
|
+
in_tenacious_transaction do
|
|
498
|
+
reload_nested_set
|
|
499
|
+
# select the rows in the model that extend past the deletion point and apply a lock
|
|
500
|
+
self.class.base_class.find(:all,
|
|
501
|
+
:select => "id",
|
|
502
|
+
:conditions => ["#{quoted_left_column_name} >= ?", left],
|
|
503
|
+
:lock => true
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
if acts_as_nested_set_options[:dependent] == :destroy
|
|
507
|
+
descendants.each do |model|
|
|
508
|
+
model.skip_before_destroy = true
|
|
509
|
+
model.destroy
|
|
431
510
|
end
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff],
|
|
437
|
-
["#{quoted_left_column_name} > ?", right]
|
|
438
|
-
)
|
|
439
|
-
nested_set_scope.update_all(
|
|
440
|
-
["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff],
|
|
441
|
-
["#{quoted_right_column_name} > ?", right]
|
|
511
|
+
else
|
|
512
|
+
nested_set_scope.delete_all(
|
|
513
|
+
["#{quoted_left_column_name} > ? AND #{quoted_right_column_name} < ?",
|
|
514
|
+
left, right]
|
|
442
515
|
)
|
|
443
|
-
|
|
444
|
-
# Don't allow multiple calls to destroy to corrupt the set
|
|
445
|
-
self.skip_before_destroy = true
|
|
446
516
|
end
|
|
447
|
-
end
|
|
448
517
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
"#{
|
|
518
|
+
# update lefts and rights for remaining nodes
|
|
519
|
+
diff = right - left + 1
|
|
520
|
+
nested_set_scope.update_all(
|
|
521
|
+
["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff],
|
|
522
|
+
["#{quoted_left_column_name} > ?", right]
|
|
523
|
+
)
|
|
524
|
+
nested_set_scope.update_all(
|
|
525
|
+
["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff],
|
|
526
|
+
["#{quoted_right_column_name} > ?", right]
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
# Don't allow multiple calls to destroy to corrupt the set
|
|
530
|
+
self.skip_before_destroy = true
|
|
453
531
|
end
|
|
532
|
+
end
|
|
454
533
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
# load object if node is not an object
|
|
463
|
-
target = nested_set_scope.find(target)
|
|
464
|
-
end
|
|
465
|
-
self.reload_nested_set
|
|
534
|
+
# reload left, right, and parent
|
|
535
|
+
def reload_nested_set
|
|
536
|
+
reload(
|
|
537
|
+
:select => "#{quoted_left_column_name}, #{quoted_right_column_name}, #{quoted_parent_column_name}",
|
|
538
|
+
:lock => true
|
|
539
|
+
)
|
|
540
|
+
end
|
|
466
541
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
542
|
+
def move_to(target, position)
|
|
543
|
+
raise ActiveRecord::ActiveRecordError, "You cannot move a new node" if self.new_record?
|
|
544
|
+
run_callbacks :move do
|
|
545
|
+
in_tenacious_transaction do
|
|
546
|
+
if target.is_a? self.class.base_class
|
|
547
|
+
target.reload_nested_set
|
|
548
|
+
elsif position != :root
|
|
549
|
+
# load object if node is not an object
|
|
550
|
+
target = nested_set_scope.find(target)
|
|
551
|
+
end
|
|
552
|
+
self.reload_nested_set
|
|
470
553
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
when :right; target[right_column_name] + 1
|
|
475
|
-
when :root; 1
|
|
476
|
-
else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)."
|
|
477
|
-
end
|
|
554
|
+
unless position == :root || move_possible?(target)
|
|
555
|
+
raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree."
|
|
556
|
+
end
|
|
478
557
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
558
|
+
bound = case position
|
|
559
|
+
when :child; target[right_column_name]
|
|
560
|
+
when :left; target[left_column_name]
|
|
561
|
+
when :right; target[right_column_name] + 1
|
|
562
|
+
when :root; 1
|
|
563
|
+
else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)."
|
|
564
|
+
end
|
|
485
565
|
|
|
486
|
-
|
|
487
|
-
|
|
566
|
+
if bound > self[right_column_name]
|
|
567
|
+
bound = bound - 1
|
|
568
|
+
other_bound = self[right_column_name] + 1
|
|
569
|
+
else
|
|
570
|
+
other_bound = self[left_column_name] - 1
|
|
571
|
+
end
|
|
488
572
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
a, b, c, d = [self[left_column_name], self[right_column_name], bound, other_bound].sort
|
|
573
|
+
# there would be no change
|
|
574
|
+
return if bound == self[right_column_name] || bound == self[left_column_name]
|
|
492
575
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
576
|
+
# we have defined the boundaries of two non-overlapping intervals,
|
|
577
|
+
# so sorting puts both the intervals and their boundaries in order
|
|
578
|
+
a, b, c, d = [self[left_column_name], self[right_column_name], bound, other_bound].sort
|
|
579
|
+
|
|
580
|
+
# select the rows in the model between a and d, and apply a lock
|
|
581
|
+
self.class.base_class.select('id').lock(true).where(
|
|
582
|
+
["#{quoted_left_column_name} >= :a and #{quoted_right_column_name} <= :d", {:a => a, :d => d}]
|
|
583
|
+
)
|
|
498
584
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
"WHEN #{quoted_left_column_name} BETWEEN :c AND :d " +
|
|
504
|
-
"THEN #{quoted_left_column_name} + :a - :c " +
|
|
505
|
-
"ELSE #{quoted_left_column_name} END, " +
|
|
506
|
-
"#{quoted_right_column_name} = CASE " +
|
|
507
|
-
"WHEN #{quoted_right_column_name} BETWEEN :a AND :b " +
|
|
508
|
-
"THEN #{quoted_right_column_name} + :d - :b " +
|
|
509
|
-
"WHEN #{quoted_right_column_name} BETWEEN :c AND :d " +
|
|
510
|
-
"THEN #{quoted_right_column_name} + :a - :c " +
|
|
511
|
-
"ELSE #{quoted_right_column_name} END, " +
|
|
512
|
-
"#{quoted_parent_column_name} = CASE " +
|
|
513
|
-
"WHEN #{self.class.base_class.primary_key} = :id THEN :new_parent " +
|
|
514
|
-
"ELSE #{quoted_parent_column_name} END",
|
|
515
|
-
{:a => a, :b => b, :c => c, :d => d, :id => self.id, :new_parent => new_parent}
|
|
516
|
-
])
|
|
585
|
+
new_parent = case position
|
|
586
|
+
when :child; target.id
|
|
587
|
+
when :root; nil
|
|
588
|
+
else target[parent_column_name]
|
|
517
589
|
end
|
|
518
|
-
|
|
519
|
-
self.
|
|
590
|
+
|
|
591
|
+
self.nested_set_scope.update_all([
|
|
592
|
+
"#{quoted_left_column_name} = CASE " +
|
|
593
|
+
"WHEN #{quoted_left_column_name} BETWEEN :a AND :b " +
|
|
594
|
+
"THEN #{quoted_left_column_name} + :d - :b " +
|
|
595
|
+
"WHEN #{quoted_left_column_name} BETWEEN :c AND :d " +
|
|
596
|
+
"THEN #{quoted_left_column_name} + :a - :c " +
|
|
597
|
+
"ELSE #{quoted_left_column_name} END, " +
|
|
598
|
+
"#{quoted_right_column_name} = CASE " +
|
|
599
|
+
"WHEN #{quoted_right_column_name} BETWEEN :a AND :b " +
|
|
600
|
+
"THEN #{quoted_right_column_name} + :d - :b " +
|
|
601
|
+
"WHEN #{quoted_right_column_name} BETWEEN :c AND :d " +
|
|
602
|
+
"THEN #{quoted_right_column_name} + :a - :c " +
|
|
603
|
+
"ELSE #{quoted_right_column_name} END, " +
|
|
604
|
+
"#{quoted_parent_column_name} = CASE " +
|
|
605
|
+
"WHEN #{self.class.base_class.primary_key} = :id THEN :new_parent " +
|
|
606
|
+
"ELSE #{quoted_parent_column_name} END",
|
|
607
|
+
{:a => a, :b => b, :c => c, :d => d, :id => self.id, :new_parent => new_parent}
|
|
608
|
+
])
|
|
520
609
|
end
|
|
610
|
+
target.reload_nested_set if target
|
|
611
|
+
self.set_depth!
|
|
612
|
+
self.reload_nested_set
|
|
521
613
|
end
|
|
522
|
-
|
|
523
614
|
end
|
|
524
615
|
|
|
525
616
|
end
|
|
@@ -534,6 +625,10 @@ module CollectiveIdea #:nodoc:
|
|
|
534
625
|
acts_as_nested_set_options[:right_column]
|
|
535
626
|
end
|
|
536
627
|
|
|
628
|
+
def depth_column_name
|
|
629
|
+
acts_as_nested_set_options[:depth_column]
|
|
630
|
+
end
|
|
631
|
+
|
|
537
632
|
def parent_column_name
|
|
538
633
|
acts_as_nested_set_options[:parent_column]
|
|
539
634
|
end
|
|
@@ -550,6 +645,10 @@ module CollectiveIdea #:nodoc:
|
|
|
550
645
|
connection.quote_column_name(right_column_name)
|
|
551
646
|
end
|
|
552
647
|
|
|
648
|
+
def quoted_depth_column_name
|
|
649
|
+
connection.quote_column_name(depth_column_name)
|
|
650
|
+
end
|
|
651
|
+
|
|
553
652
|
def quoted_parent_column_name
|
|
554
653
|
connection.quote_column_name(parent_column_name)
|
|
555
654
|
end
|
|
@@ -561,4 +660,5 @@ module CollectiveIdea #:nodoc:
|
|
|
561
660
|
|
|
562
661
|
end
|
|
563
662
|
end
|
|
564
|
-
end
|
|
663
|
+
end
|
|
664
|
+
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
1
2
|
module CollectiveIdea #:nodoc:
|
|
2
3
|
module Acts #:nodoc:
|
|
3
4
|
module NestedSet #:nodoc:
|
|
@@ -21,8 +22,12 @@ module CollectiveIdea #:nodoc:
|
|
|
21
22
|
# }) %>
|
|
22
23
|
#
|
|
23
24
|
def nested_set_options(class_or_item, mover = nil)
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
if class_or_item.is_a? Array
|
|
26
|
+
items = class_or_item.reject { |e| !e.root? }
|
|
27
|
+
else
|
|
28
|
+
class_or_item = class_or_item.roots if class_or_item.is_a?(Class)
|
|
29
|
+
items = Array(class_or_item)
|
|
30
|
+
end
|
|
26
31
|
result = []
|
|
27
32
|
items.each do |root|
|
|
28
33
|
result += root.self_and_descendants.map do |i|
|
|
@@ -33,8 +38,52 @@ module CollectiveIdea #:nodoc:
|
|
|
33
38
|
end
|
|
34
39
|
result
|
|
35
40
|
end
|
|
36
|
-
|
|
41
|
+
|
|
42
|
+
# Returns options for select as nested_set_options, sorted by an specific column
|
|
43
|
+
# It requires passing a string with the name of the column to sort the set with
|
|
44
|
+
# You can exclude some items from the tree.
|
|
45
|
+
# You can pass a block receiving an item and returning the string displayed in the select.
|
|
46
|
+
#
|
|
47
|
+
# == Params
|
|
48
|
+
# * +class_or_item+ - Class name or top level times
|
|
49
|
+
# * +:column+ - Column to sort the set (this will sort each children for all root elements)
|
|
50
|
+
# * +mover+ - The item that is being move, used to exlude impossible moves
|
|
51
|
+
# * +&block+ - a block that will be used to display: { |item| ... item.name }
|
|
52
|
+
#
|
|
53
|
+
# == Usage
|
|
54
|
+
#
|
|
55
|
+
# <%= f.select :parent_id, nested_set_options(Category, :sort_by_this_column, @category) {|i|
|
|
56
|
+
# "#{'–' * i.level} #{i.name}"
|
|
57
|
+
# }) %>
|
|
58
|
+
#
|
|
59
|
+
def sorted_nested_set_options(class_or_item, order, mover = nil)
|
|
60
|
+
if class_or_item.is_a? Array
|
|
61
|
+
items = class_or_item.reject { |e| !e.root? }
|
|
62
|
+
else
|
|
63
|
+
class_or_item = class_or_item.roots if class_or_item.is_a?(Class)
|
|
64
|
+
items = Array(class_or_item)
|
|
65
|
+
end
|
|
66
|
+
result = []
|
|
67
|
+
children = []
|
|
68
|
+
items.each do |root|
|
|
69
|
+
root.self_and_descendants.map do |i|
|
|
70
|
+
if mover.nil? || mover.new_record? || mover.move_possible?(i)
|
|
71
|
+
if !i.leaf?
|
|
72
|
+
children.sort_by! &order
|
|
73
|
+
children.each { |c| result << [yield(c), c.id] }
|
|
74
|
+
children = []
|
|
75
|
+
result << [yield(i), i.id]
|
|
76
|
+
else
|
|
77
|
+
children << i
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end.compact
|
|
81
|
+
end
|
|
82
|
+
children.sort_by! &order
|
|
83
|
+
children.each { |c| result << [yield(c), c.id] }
|
|
84
|
+
result
|
|
85
|
+
end
|
|
37
86
|
end
|
|
38
87
|
end
|
|
39
88
|
end
|
|
40
|
-
end
|
|
89
|
+
end
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
module AwesomeNestedSet
|
|
2
|
-
VERSION = '2.
|
|
3
|
-
end
|
|
2
|
+
VERSION = '2.1.2' unless defined?(::AwesomeNestedSet::VERSION)
|
|
3
|
+
end
|
metadata
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: awesome_nested_set
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 15
|
|
4
5
|
prerelease:
|
|
5
|
-
|
|
6
|
+
segments:
|
|
7
|
+
- 2
|
|
8
|
+
- 1
|
|
9
|
+
- 2
|
|
10
|
+
version: 2.1.2
|
|
6
11
|
platform: ruby
|
|
7
12
|
authors:
|
|
8
13
|
- Brandon Keepers
|
|
9
14
|
- Daniel Morrison
|
|
15
|
+
- Philip Arndt
|
|
10
16
|
autorequire:
|
|
11
17
|
bindir: bin
|
|
12
18
|
cert_chain: []
|
|
13
19
|
|
|
14
|
-
date:
|
|
15
|
-
default_executable:
|
|
20
|
+
date: 2012-01-26 00:00:00 Z
|
|
16
21
|
dependencies:
|
|
17
22
|
- !ruby/object:Gem::Dependency
|
|
18
23
|
name: activerecord
|
|
@@ -22,6 +27,11 @@ dependencies:
|
|
|
22
27
|
requirements:
|
|
23
28
|
- - ">="
|
|
24
29
|
- !ruby/object:Gem::Version
|
|
30
|
+
hash: 7
|
|
31
|
+
segments:
|
|
32
|
+
- 3
|
|
33
|
+
- 0
|
|
34
|
+
- 0
|
|
25
35
|
version: 3.0.0
|
|
26
36
|
type: :runtime
|
|
27
37
|
version_requirements: *id001
|
|
@@ -41,7 +51,6 @@ files:
|
|
|
41
51
|
- MIT-LICENSE
|
|
42
52
|
- README.rdoc
|
|
43
53
|
- CHANGELOG
|
|
44
|
-
has_rdoc: true
|
|
45
54
|
homepage: http://github.com/collectiveidea/awesome_nested_set
|
|
46
55
|
licenses: []
|
|
47
56
|
|
|
@@ -58,17 +67,23 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
58
67
|
requirements:
|
|
59
68
|
- - ">="
|
|
60
69
|
- !ruby/object:Gem::Version
|
|
70
|
+
hash: 3
|
|
71
|
+
segments:
|
|
72
|
+
- 0
|
|
61
73
|
version: "0"
|
|
62
74
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
75
|
none: false
|
|
64
76
|
requirements:
|
|
65
77
|
- - ">="
|
|
66
78
|
- !ruby/object:Gem::Version
|
|
79
|
+
hash: 3
|
|
80
|
+
segments:
|
|
81
|
+
- 0
|
|
67
82
|
version: "0"
|
|
68
83
|
requirements: []
|
|
69
84
|
|
|
70
85
|
rubyforge_project:
|
|
71
|
-
rubygems_version: 1.
|
|
86
|
+
rubygems_version: 1.8.10
|
|
72
87
|
signing_key:
|
|
73
88
|
specification_version: 3
|
|
74
89
|
summary: An awesome nested set implementation for Active Record
|