awesome_nested_set 2.1.6 → 3.0.0.rc.6
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.
- checksums.yaml +7 -0
- data/CHANGELOG +5 -0
- data/README.md +230 -0
- data/lib/awesome_nested_set/awesome_nested_set.rb +66 -692
- data/lib/awesome_nested_set/columns.rb +84 -0
- data/lib/awesome_nested_set/helper.rb +2 -47
- data/lib/awesome_nested_set/iterator.rb +29 -0
- data/lib/awesome_nested_set/model/movable.rb +146 -0
- data/lib/awesome_nested_set/model/prunable.rb +70 -0
- data/lib/awesome_nested_set/model/rebuildable.rb +42 -0
- data/lib/awesome_nested_set/model/relatable.rb +121 -0
- data/lib/awesome_nested_set/model/transactable.rb +27 -0
- data/lib/awesome_nested_set/model/validatable.rb +70 -0
- data/lib/awesome_nested_set/model.rb +248 -0
- data/lib/awesome_nested_set/move.rb +131 -0
- data/lib/awesome_nested_set/set_validator.rb +63 -0
- data/lib/awesome_nested_set/tree.rb +63 -0
- data/lib/awesome_nested_set/version.rb +1 -1
- data/lib/awesome_nested_set.rb +1 -1
- metadata +60 -37
- data/README.rdoc +0 -153
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
require 'awesome_nested_set/model/prunable'
|
|
2
|
+
require 'awesome_nested_set/model/movable'
|
|
3
|
+
require 'awesome_nested_set/model/transactable'
|
|
4
|
+
require 'awesome_nested_set/model/relatable'
|
|
5
|
+
require 'awesome_nested_set/model/rebuildable'
|
|
6
|
+
require 'awesome_nested_set/model/validatable'
|
|
7
|
+
require 'awesome_nested_set/iterator'
|
|
8
|
+
|
|
9
|
+
module CollectiveIdea #:nodoc:
|
|
10
|
+
module Acts #:nodoc:
|
|
11
|
+
module NestedSet #:nodoc:
|
|
12
|
+
|
|
13
|
+
module Model
|
|
14
|
+
extend ActiveSupport::Concern
|
|
15
|
+
|
|
16
|
+
included do
|
|
17
|
+
delegate :quoted_table_name, :arel_table, :to => self
|
|
18
|
+
extend Validatable
|
|
19
|
+
extend Rebuildable
|
|
20
|
+
include Movable
|
|
21
|
+
include Prunable
|
|
22
|
+
include Relatable
|
|
23
|
+
include Transactable
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
module ClassMethods
|
|
27
|
+
def associate_parents(objects)
|
|
28
|
+
return objects unless objects.all? {|o| o.respond_to?(:association)}
|
|
29
|
+
|
|
30
|
+
id_indexed = objects.index_by(&primary_column_name.to_sym)
|
|
31
|
+
objects.each do |object|
|
|
32
|
+
association = object.association(:parent)
|
|
33
|
+
parent = id_indexed[object.parent_id]
|
|
34
|
+
|
|
35
|
+
if !association.loaded? && parent
|
|
36
|
+
association.target = parent
|
|
37
|
+
add_to_inverse_association(association, parent)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def add_to_inverse_association(association, record)
|
|
43
|
+
inverse_reflection = association.send(:inverse_reflection_for, record)
|
|
44
|
+
inverse = record.association(inverse_reflection.name)
|
|
45
|
+
inverse.target << association.owner
|
|
46
|
+
inverse.loaded!
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def children_of(parent_id)
|
|
50
|
+
where arel_table[parent_column_name].eq(parent_id)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Iterates over tree elements and determines the current level in the tree.
|
|
54
|
+
# Only accepts default ordering, odering by an other column than lft
|
|
55
|
+
# does not work. This method is much more efficent than calling level
|
|
56
|
+
# because it doesn't require any additional database queries.
|
|
57
|
+
#
|
|
58
|
+
# Example:
|
|
59
|
+
# Category.each_with_level(Category.root.self_and_descendants) do |o, level|
|
|
60
|
+
#
|
|
61
|
+
def each_with_level(objects, &block)
|
|
62
|
+
Iterator.new(objects).each_with_level(&block)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def leaves
|
|
66
|
+
nested_set_scope.where "#{quoted_right_column_full_name} - #{quoted_left_column_full_name} = 1"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def left_of(node)
|
|
70
|
+
where arel_table[left_column_name].lt(node)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def left_of_right_side(node)
|
|
74
|
+
where arel_table[right_column_name].lteq(node)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def right_of(node)
|
|
78
|
+
where arel_table[left_column_name].gteq(node)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def nested_set_scope(options = {})
|
|
82
|
+
options = {:order => quoted_order_column_full_name}.merge(options)
|
|
83
|
+
|
|
84
|
+
where(options[:conditions]).order(options.delete(:order))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def primary_key_scope(id)
|
|
88
|
+
where arel_table[primary_column_name].eq(id)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def root
|
|
92
|
+
roots.first
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def roots
|
|
96
|
+
nested_set_scope.children_of nil
|
|
97
|
+
end
|
|
98
|
+
end # end class methods
|
|
99
|
+
|
|
100
|
+
# 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.
|
|
101
|
+
#
|
|
102
|
+
# category.self_and_descendants.count
|
|
103
|
+
# category.ancestors.find(:all, :conditions => "name like '%foo%'")
|
|
104
|
+
# Value of the parent column
|
|
105
|
+
def parent_id(target = self)
|
|
106
|
+
target[parent_column_name]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def primary_id(target = self)
|
|
110
|
+
target[primary_column_name]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Value of the left column
|
|
114
|
+
def left(target = self)
|
|
115
|
+
target[left_column_name]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Value of the right column
|
|
119
|
+
def right(target = self)
|
|
120
|
+
target[right_column_name]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Returns true if this is a root node.
|
|
124
|
+
def root?
|
|
125
|
+
parent_id.nil?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Returns true is this is a child node
|
|
129
|
+
def child?
|
|
130
|
+
!root?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Returns true if this is the end of a branch.
|
|
134
|
+
def leaf?
|
|
135
|
+
persisted? && right.to_i - left.to_i == 1
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# All nested set queries should use this nested_set_scope, which
|
|
139
|
+
# performs finds on the base ActiveRecord class, using the :scope
|
|
140
|
+
# declared in the acts_as_nested_set declaration.
|
|
141
|
+
def nested_set_scope(options = {})
|
|
142
|
+
if (scopes = Array(acts_as_nested_set_options[:scope])).any?
|
|
143
|
+
options[:conditions] = scopes.inject({}) do |conditions,attr|
|
|
144
|
+
conditions.merge attr => self[attr]
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
self.class.base_class.nested_set_scope options
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def to_text
|
|
152
|
+
self_and_descendants.map do |node|
|
|
153
|
+
"#{'*'*(node.level+1)} #{node.primary_id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})"
|
|
154
|
+
end.join("\n")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
protected
|
|
158
|
+
|
|
159
|
+
def without_self(scope)
|
|
160
|
+
return scope if new_record?
|
|
161
|
+
scope.where(["#{self.class.quoted_table_name}.#{self.class.quoted_primary_column_name} != ?", self.primary_id])
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def store_new_parent
|
|
165
|
+
@move_to_new_parent_id = send("#{parent_column_name}_changed?") ? parent_id : false
|
|
166
|
+
true # force callback to return true
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def has_depth_column?
|
|
170
|
+
nested_set_scope.column_names.map(&:to_s).include?(depth_column_name.to_s)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def right_most_node
|
|
174
|
+
@right_most_node ||= self.class.base_class.unscoped.nested_set_scope(
|
|
175
|
+
:order => "#{quoted_right_column_full_name} desc"
|
|
176
|
+
).first
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def right_most_bound
|
|
180
|
+
@right_most_bound ||= begin
|
|
181
|
+
return 0 if right_most_node.nil?
|
|
182
|
+
|
|
183
|
+
right_most_node.lock!
|
|
184
|
+
right_most_node[right_column_name] || 0
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def set_depth!
|
|
189
|
+
return unless has_depth_column?
|
|
190
|
+
|
|
191
|
+
in_tenacious_transaction do
|
|
192
|
+
reload
|
|
193
|
+
update_depth(level)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def set_depth_for_self_and_descendants!
|
|
198
|
+
return unless has_depth_column?
|
|
199
|
+
|
|
200
|
+
in_tenacious_transaction do
|
|
201
|
+
reload
|
|
202
|
+
self_and_descendants.select(primary_column_name).lock(true)
|
|
203
|
+
old_depth = self[depth_column_name] || 0
|
|
204
|
+
new_depth = level
|
|
205
|
+
update_depth(new_depth)
|
|
206
|
+
change_descendants_depth!(new_depth - old_depth)
|
|
207
|
+
new_depth
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def update_depth(depth)
|
|
212
|
+
nested_set_scope.primary_key_scope(primary_id).
|
|
213
|
+
update_all(["#{quoted_depth_column_name} = ?", depth])
|
|
214
|
+
self[depth_column_name] = depth
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def change_descendants_depth!(diff)
|
|
218
|
+
if !leaf? && diff != 0
|
|
219
|
+
sign = "++-"[diff <=> 0]
|
|
220
|
+
descendants.update_all("#{quoted_depth_column_name} = #{quoted_depth_column_name} #{sign} #{diff.abs}")
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def set_default_left_and_right
|
|
225
|
+
# adds the new node to the right of all existing nodes
|
|
226
|
+
self[left_column_name] = right_most_bound + 1
|
|
227
|
+
self[right_column_name] = right_most_bound + 2
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# reload left, right, and parent
|
|
231
|
+
def reload_nested_set
|
|
232
|
+
reload(
|
|
233
|
+
:select => "#{quoted_left_column_full_name}, #{quoted_right_column_full_name}, #{quoted_parent_column_full_name}",
|
|
234
|
+
:lock => true
|
|
235
|
+
)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def reload_target(target, position)
|
|
239
|
+
if target.is_a? self.class.base_class
|
|
240
|
+
target.reload
|
|
241
|
+
elsif position != :root
|
|
242
|
+
nested_set_scope.where(primary_column_name => target).first!
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
module CollectiveIdea #:nodoc:
|
|
2
|
+
module Acts #:nodoc:
|
|
3
|
+
module NestedSet #:nodoc:
|
|
4
|
+
class Move
|
|
5
|
+
attr_reader :target, :position, :instance
|
|
6
|
+
|
|
7
|
+
def initialize(target, position, instance)
|
|
8
|
+
@target = target
|
|
9
|
+
@position = position
|
|
10
|
+
@instance = instance
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def move
|
|
14
|
+
prevent_impossible_move
|
|
15
|
+
|
|
16
|
+
bound, other_bound = get_boundaries
|
|
17
|
+
|
|
18
|
+
# there would be no change
|
|
19
|
+
return if bound == right || bound == left
|
|
20
|
+
|
|
21
|
+
# we have defined the boundaries of two non-overlapping intervals,
|
|
22
|
+
# so sorting puts both the intervals and their boundaries in order
|
|
23
|
+
a, b, c, d = [left, right, bound, other_bound].sort
|
|
24
|
+
|
|
25
|
+
lock_nodes_between! a, d
|
|
26
|
+
|
|
27
|
+
nested_set_scope.where(where_statement(a, d)).update_all(
|
|
28
|
+
conditions(a, b, c, d)
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
delegate :left, :right, :left_column_name, :right_column_name,
|
|
35
|
+
:quoted_left_column_name, :quoted_right_column_name,
|
|
36
|
+
:quoted_parent_column_name, :parent_column_name, :nested_set_scope,
|
|
37
|
+
:primary_column_name, :quoted_primary_column_name, :primary_id,
|
|
38
|
+
:to => :instance
|
|
39
|
+
|
|
40
|
+
delegate :arel_table, :class, :to => :instance, :prefix => true
|
|
41
|
+
delegate :base_class, :to => :instance_class, :prefix => :instance
|
|
42
|
+
|
|
43
|
+
def where_statement(left_bound, right_bound)
|
|
44
|
+
instance_arel_table[left_column_name].in(left_bound..right_bound).
|
|
45
|
+
or(instance_arel_table[right_column_name].in(left_bound..right_bound))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def conditions(a, b, c, d)
|
|
49
|
+
_conditions = case_condition_for_direction(:quoted_left_column_name) +
|
|
50
|
+
case_condition_for_direction(:quoted_right_column_name) +
|
|
51
|
+
case_condition_for_parent
|
|
52
|
+
|
|
53
|
+
# We want the record to be 'touched' if it timestamps.
|
|
54
|
+
if @instance.respond_to?(:updated_at)
|
|
55
|
+
_conditions << ", updated_at = :timestamp"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
[
|
|
59
|
+
_conditions,
|
|
60
|
+
{
|
|
61
|
+
:a => a, :b => b, :c => c, :d => d,
|
|
62
|
+
:primary_id => instance.primary_id,
|
|
63
|
+
:new_parent_id => new_parent_id,
|
|
64
|
+
:timestamp => Time.now.utc
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def case_condition_for_direction(column_name)
|
|
70
|
+
column = send(column_name)
|
|
71
|
+
"#{column} = CASE " +
|
|
72
|
+
"WHEN #{column} BETWEEN :a AND :b " +
|
|
73
|
+
"THEN #{column} + :d - :b " +
|
|
74
|
+
"WHEN #{column} BETWEEN :c AND :d " +
|
|
75
|
+
"THEN #{column} + :a - :c " +
|
|
76
|
+
"ELSE #{column} END, "
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def case_condition_for_parent
|
|
80
|
+
"#{quoted_parent_column_name} = CASE " +
|
|
81
|
+
"WHEN #{quoted_primary_column_name} = :primary_id THEN :new_parent_id " +
|
|
82
|
+
"ELSE #{quoted_parent_column_name} END"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def lock_nodes_between!(left_bound, right_bound)
|
|
86
|
+
# select the rows in the model between a and d, and apply a lock
|
|
87
|
+
instance_base_class.right_of(left_bound).left_of_right_side(right_bound).
|
|
88
|
+
select(primary_column_name).lock(true)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def root
|
|
92
|
+
position == :root
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def new_parent_id
|
|
96
|
+
case position
|
|
97
|
+
when :child then target.primary_id
|
|
98
|
+
when :root then nil
|
|
99
|
+
else target[parent_column_name]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def get_boundaries
|
|
104
|
+
if (bound = target_bound) > right
|
|
105
|
+
bound -= 1
|
|
106
|
+
other_bound = right + 1
|
|
107
|
+
else
|
|
108
|
+
other_bound = left - 1
|
|
109
|
+
end
|
|
110
|
+
[bound, other_bound]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def prevent_impossible_move
|
|
114
|
+
if !root && !instance.move_possible?(target)
|
|
115
|
+
raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree."
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def target_bound
|
|
120
|
+
case position
|
|
121
|
+
when :child then right(target)
|
|
122
|
+
when :left then left(target)
|
|
123
|
+
when :right then right(target) + 1
|
|
124
|
+
when :root then nested_set_scope.pluck(right_column_name).max + 1
|
|
125
|
+
else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)."
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module CollectiveIdea #:nodoc:
|
|
2
|
+
module Acts #:nodoc:
|
|
3
|
+
module NestedSet #:nodoc:
|
|
4
|
+
class SetValidator
|
|
5
|
+
|
|
6
|
+
def initialize(model)
|
|
7
|
+
@model = model
|
|
8
|
+
@scope = model.all
|
|
9
|
+
@parent = arel_table.alias('parent')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def valid?
|
|
13
|
+
query.count == 0
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
attr_reader :model, :parent
|
|
19
|
+
attr_accessor :scope
|
|
20
|
+
|
|
21
|
+
delegate :parent_column_name, :primary_column_name, :primary_key, :left_column_name, :right_column_name, :arel_table,
|
|
22
|
+
:quoted_table_name, :quoted_parent_column_full_name, :quoted_left_column_full_name, :quoted_right_column_full_name, :quoted_left_column_name, :quoted_right_column_name, :quoted_primary_column_name,
|
|
23
|
+
:to => :model
|
|
24
|
+
|
|
25
|
+
def query
|
|
26
|
+
join_scope
|
|
27
|
+
filter_scope
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def join_scope
|
|
31
|
+
join_arel = arel_table.join(parent, Arel::Nodes::OuterJoin).on(parent[primary_column_name].eq(arel_table[parent_column_name]))
|
|
32
|
+
self.scope = scope.joins(join_arel.join_sql)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def filter_scope
|
|
36
|
+
self.scope = scope.where(
|
|
37
|
+
bound_is_null(left_column_name).
|
|
38
|
+
or(bound_is_null(right_column_name)).
|
|
39
|
+
or(left_bound_greater_than_right).
|
|
40
|
+
or(parent_not_null.and(bounds_outside_parent))
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def bound_is_null(column_name)
|
|
45
|
+
arel_table[column_name].eq(nil)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def left_bound_greater_than_right
|
|
49
|
+
arel_table[left_column_name].gteq(arel_table[right_column_name])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def parent_not_null
|
|
53
|
+
arel_table[parent_column_name].not_eq(nil)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def bounds_outside_parent
|
|
57
|
+
arel_table[left_column_name].lteq(parent[left_column_name]).or(arel_table[right_column_name].gteq(parent[right_column_name]))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module CollectiveIdea #:nodoc:
|
|
2
|
+
module Acts #:nodoc:
|
|
3
|
+
module NestedSet #:nodoc:
|
|
4
|
+
class Tree
|
|
5
|
+
attr_reader :model, :validate_nodes
|
|
6
|
+
attr_accessor :indices
|
|
7
|
+
|
|
8
|
+
delegate :left_column_name, :right_column_name, :quoted_parent_column_full_name,
|
|
9
|
+
:order_for_rebuild, :scope_for_rebuild,
|
|
10
|
+
:to => :model
|
|
11
|
+
|
|
12
|
+
def initialize(model, validate_nodes)
|
|
13
|
+
@model = model
|
|
14
|
+
@validate_nodes = validate_nodes
|
|
15
|
+
@indices = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def rebuild!
|
|
19
|
+
# Don't rebuild a valid tree.
|
|
20
|
+
return true if model.valid?
|
|
21
|
+
|
|
22
|
+
root_nodes.each do |root_node|
|
|
23
|
+
# setup index for this scope
|
|
24
|
+
indices[scope_for_rebuild.call(root_node)] ||= 0
|
|
25
|
+
set_left_and_rights(root_node)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def increment_indice!(node)
|
|
32
|
+
indices[scope_for_rebuild.call(node)] += 1
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def set_left_and_rights(node)
|
|
36
|
+
set_left!(node)
|
|
37
|
+
# find
|
|
38
|
+
node_children(node).each { |n| set_left_and_rights(n) }
|
|
39
|
+
set_right!(node)
|
|
40
|
+
|
|
41
|
+
node.save!(:validate => validate_nodes)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def node_children(node)
|
|
45
|
+
model.where(["#{quoted_parent_column_full_name} = ? #{scope_for_rebuild.call(node)}", node.primary_id]).
|
|
46
|
+
order(order_for_rebuild)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def root_nodes
|
|
50
|
+
model.where("#{quoted_parent_column_full_name} IS NULL").order(order_for_rebuild)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def set_left!(node)
|
|
54
|
+
node[left_column_name] = increment_indice!(node)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def set_right!(node)
|
|
58
|
+
node[right_column_name] = increment_indice!(node)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
data/lib/awesome_nested_set.rb
CHANGED
metadata
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: awesome_nested_set
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
5
|
-
prerelease:
|
|
4
|
+
version: 3.0.0.rc.6
|
|
6
5
|
platform: ruby
|
|
7
6
|
authors:
|
|
8
7
|
- Brandon Keepers
|
|
@@ -11,113 +10,137 @@ authors:
|
|
|
11
10
|
autorequire:
|
|
12
11
|
bindir: bin
|
|
13
12
|
cert_chain: []
|
|
14
|
-
date:
|
|
13
|
+
date: 2014-07-01 00:00:00.000000000 Z
|
|
15
14
|
dependencies:
|
|
16
15
|
- !ruby/object:Gem::Dependency
|
|
17
16
|
name: activerecord
|
|
18
17
|
requirement: !ruby/object:Gem::Requirement
|
|
19
|
-
none: false
|
|
20
18
|
requirements:
|
|
21
|
-
- -
|
|
19
|
+
- - ">="
|
|
22
20
|
- !ruby/object:Gem::Version
|
|
23
|
-
version:
|
|
21
|
+
version: 4.0.0
|
|
22
|
+
- - "<"
|
|
23
|
+
- !ruby/object:Gem::Version
|
|
24
|
+
version: '5'
|
|
24
25
|
type: :runtime
|
|
25
26
|
prerelease: false
|
|
26
27
|
version_requirements: !ruby/object:Gem::Requirement
|
|
27
|
-
none: false
|
|
28
28
|
requirements:
|
|
29
|
-
- -
|
|
29
|
+
- - ">="
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: 4.0.0
|
|
32
|
+
- - "<"
|
|
30
33
|
- !ruby/object:Gem::Version
|
|
31
|
-
version:
|
|
34
|
+
version: '5'
|
|
32
35
|
- !ruby/object:Gem::Dependency
|
|
33
36
|
name: rspec-rails
|
|
34
37
|
requirement: !ruby/object:Gem::Requirement
|
|
35
|
-
none: false
|
|
36
38
|
requirements:
|
|
37
|
-
- - ~>
|
|
39
|
+
- - "~>"
|
|
38
40
|
- !ruby/object:Gem::Version
|
|
39
41
|
version: '2.12'
|
|
40
42
|
type: :development
|
|
41
43
|
prerelease: false
|
|
42
44
|
version_requirements: !ruby/object:Gem::Requirement
|
|
43
|
-
none: false
|
|
44
45
|
requirements:
|
|
45
|
-
- - ~>
|
|
46
|
+
- - "~>"
|
|
46
47
|
- !ruby/object:Gem::Version
|
|
47
48
|
version: '2.12'
|
|
48
49
|
- !ruby/object:Gem::Dependency
|
|
49
50
|
name: rake
|
|
50
51
|
requirement: !ruby/object:Gem::Requirement
|
|
51
|
-
none: false
|
|
52
52
|
requirements:
|
|
53
|
-
- - ~>
|
|
53
|
+
- - "~>"
|
|
54
54
|
- !ruby/object:Gem::Version
|
|
55
55
|
version: '10'
|
|
56
56
|
type: :development
|
|
57
57
|
prerelease: false
|
|
58
58
|
version_requirements: !ruby/object:Gem::Requirement
|
|
59
|
-
none: false
|
|
60
59
|
requirements:
|
|
61
|
-
- - ~>
|
|
60
|
+
- - "~>"
|
|
62
61
|
- !ruby/object:Gem::Version
|
|
63
62
|
version: '10'
|
|
64
63
|
- !ruby/object:Gem::Dependency
|
|
65
64
|
name: combustion
|
|
66
65
|
requirement: !ruby/object:Gem::Requirement
|
|
67
|
-
none: false
|
|
68
66
|
requirements:
|
|
69
|
-
- -
|
|
67
|
+
- - ">="
|
|
70
68
|
- !ruby/object:Gem::Version
|
|
71
69
|
version: 0.3.3
|
|
72
70
|
type: :development
|
|
73
71
|
prerelease: false
|
|
74
72
|
version_requirements: !ruby/object:Gem::Requirement
|
|
75
|
-
none: false
|
|
76
73
|
requirements:
|
|
77
|
-
- -
|
|
74
|
+
- - ">="
|
|
78
75
|
- !ruby/object:Gem::Version
|
|
79
76
|
version: 0.3.3
|
|
77
|
+
- !ruby/object:Gem::Dependency
|
|
78
|
+
name: database_cleaner
|
|
79
|
+
requirement: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '0'
|
|
84
|
+
type: :development
|
|
85
|
+
prerelease: false
|
|
86
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '0'
|
|
80
91
|
description: An awesome nested set implementation for Active Record
|
|
81
92
|
email: info@collectiveidea.com
|
|
82
93
|
executables: []
|
|
83
94
|
extensions: []
|
|
84
95
|
extra_rdoc_files:
|
|
85
|
-
- README.
|
|
96
|
+
- README.md
|
|
86
97
|
files:
|
|
98
|
+
- CHANGELOG
|
|
99
|
+
- MIT-LICENSE
|
|
100
|
+
- README.md
|
|
101
|
+
- lib/awesome_nested_set.rb
|
|
87
102
|
- lib/awesome_nested_set/awesome_nested_set.rb
|
|
103
|
+
- lib/awesome_nested_set/columns.rb
|
|
88
104
|
- lib/awesome_nested_set/helper.rb
|
|
105
|
+
- lib/awesome_nested_set/iterator.rb
|
|
106
|
+
- lib/awesome_nested_set/model.rb
|
|
107
|
+
- lib/awesome_nested_set/model/movable.rb
|
|
108
|
+
- lib/awesome_nested_set/model/prunable.rb
|
|
109
|
+
- lib/awesome_nested_set/model/rebuildable.rb
|
|
110
|
+
- lib/awesome_nested_set/model/relatable.rb
|
|
111
|
+
- lib/awesome_nested_set/model/transactable.rb
|
|
112
|
+
- lib/awesome_nested_set/model/validatable.rb
|
|
113
|
+
- lib/awesome_nested_set/move.rb
|
|
114
|
+
- lib/awesome_nested_set/set_validator.rb
|
|
115
|
+
- lib/awesome_nested_set/tree.rb
|
|
89
116
|
- lib/awesome_nested_set/version.rb
|
|
90
|
-
- lib/awesome_nested_set.rb
|
|
91
|
-
- MIT-LICENSE
|
|
92
|
-
- README.rdoc
|
|
93
|
-
- CHANGELOG
|
|
94
117
|
homepage: http://github.com/collectiveidea/awesome_nested_set
|
|
95
118
|
licenses:
|
|
96
119
|
- MIT
|
|
120
|
+
metadata: {}
|
|
97
121
|
post_install_message:
|
|
98
122
|
rdoc_options:
|
|
99
|
-
- --main
|
|
100
|
-
- README.
|
|
101
|
-
- --inline-source
|
|
102
|
-
- --line-numbers
|
|
123
|
+
- "--main"
|
|
124
|
+
- README.md
|
|
125
|
+
- "--inline-source"
|
|
126
|
+
- "--line-numbers"
|
|
103
127
|
require_paths:
|
|
104
128
|
- lib
|
|
105
129
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
106
|
-
none: false
|
|
107
130
|
requirements:
|
|
108
|
-
- -
|
|
131
|
+
- - ">="
|
|
109
132
|
- !ruby/object:Gem::Version
|
|
110
133
|
version: '0'
|
|
111
134
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
|
-
none: false
|
|
113
135
|
requirements:
|
|
114
|
-
- -
|
|
136
|
+
- - ">"
|
|
115
137
|
- !ruby/object:Gem::Version
|
|
116
|
-
version:
|
|
138
|
+
version: 1.3.1
|
|
117
139
|
requirements: []
|
|
118
140
|
rubyforge_project:
|
|
119
|
-
rubygems_version:
|
|
141
|
+
rubygems_version: 2.2.2
|
|
120
142
|
signing_key:
|
|
121
|
-
specification_version:
|
|
143
|
+
specification_version: 4
|
|
122
144
|
summary: An awesome nested set implementation for Active Record
|
|
123
145
|
test_files: []
|
|
146
|
+
has_rdoc:
|