Revision 13292
Added by Toshi MARUYAMA about 11 years ago
sandbox/rails-4.1/Gemfile | ||
---|---|---|
7 | 7 |
gem "builder", ">= 3.0.4" |
8 | 8 |
gem "request_store", "1.0.5" |
9 | 9 |
gem "mime-types" |
10 |
gem "awesome_nested_set", ">= 3.0.0.rc.6" |
|
10 | 11 |
gem "protected_attributes" |
11 | 12 |
gem "actionpack-action_caching" |
12 | 13 |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/CONTRIBUTING.md | ||
---|---|---|
1 |
# Contributing to AwesomeNestedSet |
|
2 | ||
3 |
If you find what you might think is a bug: |
|
4 | ||
5 |
1. Check the [GitHub issue tracker](https://github.com/collectiveidea/awesome_nested_set/issues/) to see if anyone else has had the same issue. |
|
6 |
2. If you don't see anything, create an issue with information on how to reproduce it. |
|
7 | ||
8 |
If you want to contribute an enhancement or a fix: |
|
9 | ||
10 |
1. Fork [the project on GitHub](https://github.com/collectiveidea/awesome_nested_set) |
|
11 |
2. Make your changes with tests. |
|
12 |
3. Commit the changes without making changes to the [Rakefile](Rakefile) or any other files that aren't related to your enhancement or fix. |
|
13 |
4. Write an entry in the [CHANGELOG](CHANGELOG) |
|
14 |
5. Send a pull request. |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/tree.rb | ||
---|---|---|
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 |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/helper.rb | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
module CollectiveIdea #:nodoc: |
|
3 |
module Acts #:nodoc: |
|
4 |
module NestedSet #:nodoc: |
|
5 |
# This module provides some helpers for the model classes using acts_as_nested_set. |
|
6 |
# It is included by default in all views. |
|
7 |
# |
|
8 |
module Helper |
|
9 |
# Returns options for select. |
|
10 |
# You can exclude some items from the tree. |
|
11 |
# You can pass a block receiving an item and returning the string displayed in the select. |
|
12 |
# |
|
13 |
# == Params |
|
14 |
# * +class_or_item+ - Class name or top level times |
|
15 |
# * +mover+ - The item that is being move, used to exlude impossible moves |
|
16 |
# * +&block+ - a block that will be used to display: { |item| ... item.name } |
|
17 |
# |
|
18 |
# == Usage |
|
19 |
# |
|
20 |
# <%= f.select :parent_id, nested_set_options(Category, @category) {|i| |
|
21 |
# "#{'–' * i.level} #{i.name}" |
|
22 |
# }) %> |
|
23 |
# |
|
24 |
def nested_set_options(class_or_item, mover = nil) |
|
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.respond_to?(:scope) |
|
29 |
items = Array(class_or_item) |
|
30 |
end |
|
31 |
result = [] |
|
32 |
items.each do |root| |
|
33 |
result += root.class.associate_parents(root.self_and_descendants).map do |i| |
|
34 |
if mover.nil? || mover.new_record? || mover.move_possible?(i) |
|
35 |
[yield(i), i.primary_id] |
|
36 |
end |
|
37 |
end.compact |
|
38 |
end |
|
39 |
result |
|
40 |
end |
|
41 |
end |
|
42 |
end |
|
43 |
end |
|
44 |
end |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/set_validator.rb | ||
---|---|---|
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 |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/columns.rb | ||
---|---|---|
1 |
# Mixed into both classes and instances to provide easy access to the column names |
|
2 |
module CollectiveIdea #:nodoc: |
|
3 |
module Acts #:nodoc: |
|
4 |
module NestedSet #:nodoc: |
|
5 |
module Columns |
|
6 |
def left_column_name |
|
7 |
acts_as_nested_set_options[:left_column] |
|
8 |
end |
|
9 | ||
10 |
def right_column_name |
|
11 |
acts_as_nested_set_options[:right_column] |
|
12 |
end |
|
13 | ||
14 |
def depth_column_name |
|
15 |
acts_as_nested_set_options[:depth_column] |
|
16 |
end |
|
17 | ||
18 |
def parent_column_name |
|
19 |
acts_as_nested_set_options[:parent_column] |
|
20 |
end |
|
21 | ||
22 |
def primary_column_name |
|
23 |
acts_as_nested_set_options[:primary_column] |
|
24 |
end |
|
25 | ||
26 |
def order_column |
|
27 |
acts_as_nested_set_options[:order_column] || left_column_name |
|
28 |
end |
|
29 | ||
30 |
def scope_column_names |
|
31 |
Array(acts_as_nested_set_options[:scope]) |
|
32 |
end |
|
33 | ||
34 |
def quoted_left_column_name |
|
35 |
ActiveRecord::Base.connection.quote_column_name(left_column_name) |
|
36 |
end |
|
37 | ||
38 |
def quoted_right_column_name |
|
39 |
ActiveRecord::Base.connection.quote_column_name(right_column_name) |
|
40 |
end |
|
41 | ||
42 |
def quoted_depth_column_name |
|
43 |
ActiveRecord::Base.connection.quote_column_name(depth_column_name) |
|
44 |
end |
|
45 | ||
46 |
def quoted_primary_column_name |
|
47 |
ActiveRecord::Base.connection.quote_column_name(primary_column_name) |
|
48 |
end |
|
49 | ||
50 |
def quoted_parent_column_name |
|
51 |
ActiveRecord::Base.connection.quote_column_name(parent_column_name) |
|
52 |
end |
|
53 | ||
54 |
def quoted_scope_column_names |
|
55 |
scope_column_names.collect {|column_name| connection.quote_column_name(column_name) } |
|
56 |
end |
|
57 | ||
58 |
def quoted_order_column_name |
|
59 |
ActiveRecord::Base.connection.quote_column_name(order_column) |
|
60 |
end |
|
61 | ||
62 |
def quoted_primary_key_column_full_name |
|
63 |
"#{quoted_table_name}.#{quoted_primary_column_name}" |
|
64 |
end |
|
65 | ||
66 |
def quoted_order_column_full_name |
|
67 |
"#{quoted_table_name}.#{quoted_order_column_name}" |
|
68 |
end |
|
69 | ||
70 |
def quoted_left_column_full_name |
|
71 |
"#{quoted_table_name}.#{quoted_left_column_name}" |
|
72 |
end |
|
73 | ||
74 |
def quoted_right_column_full_name |
|
75 |
"#{quoted_table_name}.#{quoted_right_column_name}" |
|
76 |
end |
|
77 | ||
78 |
def quoted_parent_column_full_name |
|
79 |
"#{quoted_table_name}.#{quoted_parent_column_name}" |
|
80 |
end |
|
81 |
end |
|
82 |
end |
|
83 |
end |
|
84 |
end |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/model.rb | ||
---|---|---|
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 |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/version.rb | ||
---|---|---|
1 |
module AwesomeNestedSet |
|
2 |
VERSION = '3.0.0.rc.6' unless defined?(::AwesomeNestedSet::VERSION) |
|
3 |
end |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/move.rb | ||
---|---|---|
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 |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/iterator.rb | ||
---|---|---|
1 |
module CollectiveIdea #:nodoc: |
|
2 |
module Acts #:nodoc: |
|
3 |
module NestedSet #:nodoc: |
|
4 |
class Iterator |
|
5 |
attr_reader :objects |
|
6 | ||
7 |
def initialize(objects) |
|
8 |
@objects = objects |
|
9 |
end |
|
10 | ||
11 |
def each_with_level |
|
12 |
path = [nil] |
|
13 |
objects.each do |o| |
|
14 |
if o.parent_id != path.last |
|
15 |
# we are on a new level, did we descend or ascend? |
|
16 |
if path.include?(o.parent_id) |
|
17 |
# remove wrong tailing paths elements |
|
18 |
path.pop while path.last != o.parent_id |
|
19 |
else |
|
20 |
path << o.parent_id |
|
21 |
end |
|
22 |
end |
|
23 |
yield(o, path.length - 1) |
|
24 |
end |
|
25 |
end |
|
26 |
end |
|
27 |
end |
|
28 |
end |
|
29 |
end |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/awesome_nested_set.rb | ||
---|---|---|
1 |
require 'awesome_nested_set/columns' |
|
2 |
require 'awesome_nested_set/model' |
|
3 | ||
4 |
module CollectiveIdea #:nodoc: |
|
5 |
module Acts #:nodoc: |
|
6 |
module NestedSet #:nodoc: |
|
7 | ||
8 |
# This acts provides Nested Set functionality. Nested Set is a smart way to implement |
|
9 |
# an _ordered_ tree, with the added feature that you can select the children and all of their |
|
10 |
# descendants with a single query. The drawback is that insertion or move need some complex |
|
11 |
# sql queries. But everything is done here by this module! |
|
12 |
# |
|
13 |
# Nested sets are appropriate each time you want either an orderd tree (menus, |
|
14 |
# commercial categories) or an efficient way of querying big trees (threaded posts). |
|
15 |
# |
|
16 |
# == API |
|
17 |
# |
|
18 |
# Methods names are aligned with acts_as_tree as much as possible to make replacment from one |
|
19 |
# by another easier. |
|
20 |
# |
|
21 |
# item.children.create(:name => "child1") |
|
22 |
# |
|
23 | ||
24 |
# Configuration options are: |
|
25 |
# |
|
26 |
# * +:parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id) |
|
27 |
# * +:primary_column+ - specifies the column name to use as the inverse of the parent column (default: id) |
|
28 |
# * +:left_column+ - column name for left boundry data, default "lft" |
|
29 |
# * +:right_column+ - column name for right boundry data, default "rgt" |
|
30 |
# * +:depth_column+ - column name for the depth data, default "depth" |
|
31 |
# * +:scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id" |
|
32 |
# (if it hasn't been already) and use that as the foreign key restriction. You |
|
33 |
# can also pass an array to scope by multiple attributes. |
|
34 |
# Example: <tt>acts_as_nested_set :scope => [:notable_id, :notable_type]</tt> |
|
35 |
# * +:dependent+ - behavior for cascading destroy. If set to :destroy, all the |
|
36 |
# child objects are destroyed alongside this object by calling their destroy |
|
37 |
# method. If set to :delete_all (default), all the child objects are deleted |
|
38 |
# without calling their destroy method. |
|
39 |
# * +:counter_cache+ adds a counter cache for the number of children. |
|
40 |
# defaults to false. |
|
41 |
# Example: <tt>acts_as_nested_set :counter_cache => :children_count</tt> |
|
42 |
# * +:order_column+ on which column to do sorting, by default it is the left_column_name |
|
43 |
# Example: <tt>acts_as_nested_set :order_column => :position</tt> |
|
44 |
# |
|
45 |
# See CollectiveIdea::Acts::NestedSet::Model::ClassMethods for a list of class methods and |
|
46 |
# CollectiveIdea::Acts::NestedSet::Model for a list of instance methods added |
|
47 |
# to acts_as_nested_set models |
|
48 |
def acts_as_nested_set(options = {}) |
|
49 |
acts_as_nested_set_parse_options! options |
|
50 | ||
51 |
include Model |
|
52 |
include Columns |
|
53 |
extend Columns |
|
54 | ||
55 |
acts_as_nested_set_relate_parent! |
|
56 |
acts_as_nested_set_relate_children! |
|
57 | ||
58 |
attr_accessor :skip_before_destroy |
|
59 | ||
60 |
acts_as_nested_set_prevent_assignment_to_reserved_columns! |
|
61 |
acts_as_nested_set_define_callbacks! |
|
62 |
end |
|
63 | ||
64 |
private |
|
65 |
def acts_as_nested_set_define_callbacks! |
|
66 |
# on creation, set automatically lft and rgt to the end of the tree |
|
67 |
before_create :set_default_left_and_right |
|
68 |
before_save :store_new_parent |
|
69 |
after_save :move_to_new_parent, :set_depth! |
|
70 |
before_destroy :destroy_descendants |
|
71 | ||
72 |
define_model_callbacks :move |
|
73 |
end |
|
74 | ||
75 |
def acts_as_nested_set_relate_children! |
|
76 |
has_many_children_options = { |
|
77 |
:class_name => self.base_class.to_s, |
|
78 |
:foreign_key => parent_column_name, |
|
79 |
:primary_key => primary_column_name, |
|
80 |
:inverse_of => (:parent unless acts_as_nested_set_options[:polymorphic]), |
|
81 |
} |
|
82 | ||
83 |
# Add callbacks, if they were supplied.. otherwise, we don't want them. |
|
84 |
[:before_add, :after_add, :before_remove, :after_remove].each do |ar_callback| |
|
85 |
has_many_children_options.update( |
|
86 |
ar_callback => acts_as_nested_set_options[ar_callback] |
|
87 |
) if acts_as_nested_set_options[ar_callback] |
|
88 |
end |
|
89 | ||
90 |
has_many :children, -> { order(quoted_order_column_name) }, |
|
91 |
has_many_children_options |
|
92 |
end |
|
93 | ||
94 |
def acts_as_nested_set_relate_parent! |
|
95 |
belongs_to :parent, :class_name => self.base_class.to_s, |
|
96 |
:foreign_key => parent_column_name, |
|
97 |
:primary_key => primary_column_name, |
|
98 |
:counter_cache => acts_as_nested_set_options[:counter_cache], |
|
99 |
:inverse_of => (:children unless acts_as_nested_set_options[:polymorphic]), |
|
100 |
:polymorphic => acts_as_nested_set_options[:polymorphic], |
|
101 |
:touch => acts_as_nested_set_options[:touch] |
|
102 |
end |
|
103 | ||
104 |
def acts_as_nested_set_default_options |
|
105 |
{ |
|
106 |
:parent_column => 'parent_id', |
|
107 |
:primary_column => 'id', |
|
108 |
:left_column => 'lft', |
|
109 |
:right_column => 'rgt', |
|
110 |
:depth_column => 'depth', |
|
111 |
:dependent => :delete_all, # or :destroy |
|
112 |
:polymorphic => false, |
|
113 |
:counter_cache => false, |
|
114 |
:touch => false |
|
115 |
}.freeze |
|
116 |
end |
|
117 | ||
118 |
def acts_as_nested_set_parse_options!(options) |
|
119 |
options = acts_as_nested_set_default_options.merge(options) |
|
120 | ||
121 |
if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/ |
|
122 |
options[:scope] = "#{options[:scope]}_id".intern |
|
123 |
end |
|
124 | ||
125 |
class_attribute :acts_as_nested_set_options |
|
126 |
self.acts_as_nested_set_options = options |
|
127 |
end |
|
128 | ||
129 |
def acts_as_nested_set_prevent_assignment_to_reserved_columns! |
|
130 |
# no assignment to structure fields |
|
131 |
[left_column_name, right_column_name, depth_column_name].each do |column| |
|
132 |
module_eval <<-"end_eval", __FILE__, __LINE__ |
|
133 |
def #{column}=(x) |
|
134 |
raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead." |
|
135 |
end |
|
136 |
end_eval |
|
137 |
end |
|
138 |
end |
|
139 |
end |
|
140 |
end |
|
141 |
end |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/model/validatable.rb | ||
---|---|---|
1 |
require 'awesome_nested_set/set_validator' |
|
2 | ||
3 |
module CollectiveIdea |
|
4 |
module Acts |
|
5 |
module NestedSet |
|
6 |
module Model |
|
7 |
module Validatable |
|
8 | ||
9 |
def valid? |
|
10 |
left_and_rights_valid? && no_duplicates_for_columns? && all_roots_valid? |
|
11 |
end |
|
12 | ||
13 |
def left_and_rights_valid? |
|
14 |
SetValidator.new(self).valid? |
|
15 |
end |
|
16 | ||
17 |
def no_duplicates_for_columns? |
|
18 |
[quoted_left_column_full_name, quoted_right_column_full_name].all? do |column| |
|
19 |
# No duplicates |
|
20 |
select("#{scope_string}#{column}, COUNT(#{column}) as _count"). |
|
21 |
group("#{scope_string}#{column}", quoted_primary_key_column_full_name). |
|
22 |
having("COUNT(#{column}) > 1"). |
|
23 |
order(quoted_primary_key_column_full_name). |
|
24 |
first.nil? |
|
25 |
end |
|
26 |
end |
|
27 | ||
28 |
# Wrapper for each_root_valid? that can deal with scope. |
|
29 |
def all_roots_valid? |
|
30 |
if acts_as_nested_set_options[:scope] |
|
31 |
all_roots_valid_by_scope?(roots) |
|
32 |
else |
|
33 |
each_root_valid?(roots) |
|
34 |
end |
|
35 |
end |
|
36 | ||
37 |
def all_roots_valid_by_scope?(roots_to_validate) |
|
38 |
roots_grouped_by_scope(roots_to_validate).all? do |scope, grouped_roots| |
|
39 |
each_root_valid?(grouped_roots) |
|
40 |
end |
|
41 |
end |
|
42 | ||
43 |
def each_root_valid?(roots_to_validate) |
|
44 |
left = right = 0 |
|
45 |
roots_to_validate.all? do |root| |
|
46 |
(root.left > left && root.right > right).tap do |
|
47 |
left = root.left |
|
48 |
right = root.right |
|
49 |
end |
|
50 |
end |
|
51 |
end |
|
52 | ||
53 |
private |
|
54 |
def roots_grouped_by_scope(roots_to_group) |
|
55 |
roots_to_group.group_by {|record| |
|
56 |
scope_column_names.collect {|col| record.send(col) } |
|
57 |
} |
|
58 |
end |
|
59 | ||
60 |
def scope_string |
|
61 |
Array(acts_as_nested_set_options[:scope]).map do |c| |
|
62 |
connection.quote_column_name(c) |
|
63 |
end.push(nil).join(", ") |
|
64 |
end |
|
65 | ||
66 |
end |
|
67 |
end |
|
68 |
end |
|
69 |
end |
|
70 |
end |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/model/prunable.rb | ||
---|---|---|
1 |
module CollectiveIdea #:nodoc: |
|
2 |
module Acts #:nodoc: |
|
3 |
module NestedSet #:nodoc: |
|
4 |
module Model |
|
5 |
module Prunable |
|
6 | ||
7 |
# Prunes a branch off of the tree, shifting all of the elements on the right |
|
8 |
# back to the left so the counts still work. |
|
9 |
def destroy_descendants |
|
10 |
return if right.nil? || left.nil? || skip_before_destroy |
|
11 | ||
12 |
in_tenacious_transaction do |
|
13 |
reload_nested_set |
|
14 |
# select the rows in the model that extend past the deletion point and apply a lock |
|
15 |
nested_set_scope.right_of(left).select(primary_id).lock(true) |
|
16 | ||
17 |
return false unless destroy_or_delete_descendants |
|
18 | ||
19 |
# update lefts and rights for remaining nodes |
|
20 |
update_siblings_for_remaining_nodes |
|
21 | ||
22 |
# Reload is needed because children may have updated their parent (self) during deletion. |
|
23 |
reload |
|
24 | ||
25 |
# Don't allow multiple calls to destroy to corrupt the set |
|
26 |
self.skip_before_destroy = true |
|
27 |
end |
|
28 |
end |
|
29 | ||
30 |
def destroy_or_delete_descendants |
|
31 |
if acts_as_nested_set_options[:dependent] == :destroy |
|
32 |
descendants.each do |model| |
|
33 |
model.skip_before_destroy = true |
|
34 |
model.destroy |
|
35 |
end |
|
36 |
elsif acts_as_nested_set_options[:dependent] == :restrict_with_exception |
|
37 |
raise ActiveRecord::DeleteRestrictionError.new(:children) unless leaf? |
|
38 |
elsif acts_as_nested_set_options[:dependent] == :restrict_with_error |
|
39 |
unless leaf? |
|
40 |
record = self.class.human_attribute_name(:children).downcase |
|
41 |
errors.add(:base, :"restrict_dependent_destroy.many", record: record) |
|
42 |
return false |
|
43 |
end |
|
44 |
return true |
|
45 |
else |
|
46 |
descendants.delete_all |
|
47 |
end |
|
48 |
end |
|
49 | ||
50 |
def update_siblings_for_remaining_nodes |
|
51 |
update_siblings(:left) |
|
52 |
update_siblings(:right) |
|
53 |
end |
|
54 | ||
55 |
def update_siblings(direction) |
|
56 |
full_column_name = send("quoted_#{direction}_column_full_name") |
|
57 |
column_name = send("quoted_#{direction}_column_name") |
|
58 | ||
59 |
nested_set_scope.where(["#{full_column_name} > ?", right]). |
|
60 |
update_all(["#{column_name} = (#{column_name} - ?)", diff]) |
|
61 |
end |
|
62 | ||
63 |
def diff |
|
64 |
right - left + 1 |
|
65 |
end |
|
66 |
end |
|
67 |
end |
|
68 |
end |
|
69 |
end |
|
70 |
end |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/model/rebuildable.rb | ||
---|---|---|
1 |
require 'awesome_nested_set/tree' |
|
2 | ||
3 |
module CollectiveIdea |
|
4 |
module Acts |
|
5 |
module NestedSet |
|
6 |
module Model |
|
7 |
module Rebuildable |
|
8 | ||
9 | ||
10 |
# Rebuilds the left & rights if unset or invalid. |
|
11 |
# Also very useful for converting from acts_as_tree. |
|
12 |
def rebuild!(validate_nodes = true) |
|
13 |
# default_scope with order may break database queries so we do all operation without scope |
|
14 |
unscoped do |
|
15 |
Tree.new(self, validate_nodes).rebuild! |
|
16 |
end |
|
17 |
end |
|
18 | ||
19 |
def scope_for_rebuild |
|
20 |
scope = proc {} |
|
21 | ||
22 |
if acts_as_nested_set_options[:scope] |
|
23 |
scope = proc {|node| |
|
24 |
scope_column_names.inject("") {|str, column_name| |
|
25 |
column_value = node.send(column_name) |
|
26 |
cond = column_value.nil? ? "IS NULL" : "= #{connection.quote(column_value)}" |
|
27 |
str << "AND #{connection.quote_column_name(column_name)} #{cond} " |
|
28 |
} |
|
29 |
} |
|
30 |
end |
|
31 |
scope |
|
32 |
end |
|
33 | ||
34 |
def order_for_rebuild |
|
35 |
"#{quoted_left_column_full_name}, #{quoted_right_column_full_name}, #{primary_key}" |
|
36 |
end |
|
37 |
end |
|
38 | ||
39 |
end |
|
40 |
end |
|
41 |
end |
|
42 |
end |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/model/relatable.rb | ||
---|---|---|
1 |
module CollectiveIdea |
|
2 |
module Acts |
|
3 |
module NestedSet |
|
4 |
module Model |
|
5 |
module Relatable |
|
6 | ||
7 |
# Returns an collection of all parents |
|
8 |
def ancestors |
|
9 |
without_self self_and_ancestors |
|
10 |
end |
|
11 | ||
12 |
# Returns the collection of all parents and self |
|
13 |
def self_and_ancestors |
|
14 |
nested_set_scope. |
|
15 |
where(arel_table[left_column_name].lteq(left)). |
|
16 |
where(arel_table[right_column_name].gteq(right)) |
|
17 |
end |
|
18 | ||
19 |
# Returns the collection of all children of the parent, except self |
|
20 |
def siblings |
|
21 |
without_self self_and_siblings |
|
22 |
end |
|
23 | ||
24 |
# Returns the collection of all children of the parent, including self |
|
25 |
def self_and_siblings |
|
26 |
nested_set_scope.children_of parent_id |
|
27 |
end |
|
28 | ||
29 |
# Returns a set of all of its nested children which do not have children |
|
30 |
def leaves |
|
31 |
descendants.where( |
|
32 |
"#{quoted_right_column_full_name} - #{quoted_left_column_full_name} = 1" |
|
33 |
) |
|
34 |
end |
|
35 | ||
36 |
# Returns the level of this object in the tree |
|
37 |
# root level is 0 |
|
38 |
def level |
|
39 |
parent_id.nil? ? 0 : compute_level |
|
40 |
end |
|
41 | ||
42 |
# Returns a collection including all of its children and nested children |
|
43 |
def descendants |
|
44 |
without_self self_and_descendants |
|
45 |
end |
|
46 | ||
47 |
# Returns a collection including itself and all of its nested children |
|
48 |
def self_and_descendants |
|
49 |
# using _left_ for both sides here lets us benefit from an index on that column if one exists |
|
50 |
nested_set_scope.right_of(left).left_of(right) |
|
51 |
end |
|
52 | ||
53 |
def is_descendant_of?(other) |
|
54 |
within_node?(other, self) && same_scope?(other) |
|
55 |
end |
|
56 | ||
57 |
def is_or_is_descendant_of?(other) |
|
58 |
(other == self || within_node?(other, self)) && same_scope?(other) |
|
59 |
end |
|
60 | ||
61 |
def is_ancestor_of?(other) |
|
62 |
within_node?(self, other) && same_scope?(other) |
|
63 |
end |
|
64 | ||
65 |
def is_or_is_ancestor_of?(other) |
|
66 |
(self == other || within_node?(self, other)) && same_scope?(other) |
|
67 |
end |
|
68 | ||
69 |
# Check if other model is in the same scope |
|
70 |
def same_scope?(other) |
|
71 |
Array(acts_as_nested_set_options[:scope]).all? do |attr| |
|
72 |
self.send(attr) == other.send(attr) |
|
73 |
end |
|
74 |
end |
|
75 | ||
76 |
# Find the first sibling to the left |
|
77 |
def left_sibling |
|
78 |
siblings.left_of(left).last |
|
79 |
end |
|
80 | ||
81 |
# Find the first sibling to the right |
|
82 |
def right_sibling |
|
83 |
siblings.right_of(left).first |
|
84 |
end |
|
85 | ||
86 |
def root |
|
87 |
return self_and_ancestors.children_of(nil).first if persisted? |
|
88 | ||
89 |
if parent_id && current_parent = nested_set_scope.where(primary_column_name => parent_id).first! |
|
90 |
current_parent.root |
|
91 |
else |
|
92 |
self |
|
93 |
end |
|
94 |
end |
|
95 | ||
96 |
protected |
|
97 | ||
98 |
def compute_level |
|
99 |
node, nesting = determine_depth |
|
100 | ||
101 |
node == self ? ancestors.count : node.level + nesting |
|
102 |
end |
|
103 | ||
104 |
def determine_depth(node = self, nesting = 0) |
|
105 |
while (association = node.association(:parent)).loaded? && association.target |
|
106 |
nesting += 1 |
|
107 |
node = node.parent |
|
108 |
end if node.respond_to?(:association) |
|
109 | ||
110 |
[node, nesting] |
|
111 |
end |
|
112 | ||
113 |
def within_node?(node, within) |
|
114 |
node.left < within.left && within.left < node.right |
|
115 |
end |
|
116 | ||
117 |
end |
|
118 |
end |
|
119 |
end |
|
120 |
end |
|
121 |
end |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/model/transactable.rb | ||
---|---|---|
1 |
module CollectiveIdea #:nodoc: |
|
2 |
module Acts #:nodoc: |
|
3 |
module NestedSet #:nodoc: |
|
4 |
module Model |
|
5 |
module Transactable |
|
6 | ||
7 |
protected |
|
8 |
def in_tenacious_transaction(&block) |
|
9 |
retry_count = 0 |
|
10 |
begin |
|
11 |
transaction(&block) |
|
12 |
rescue ActiveRecord::StatementInvalid => error |
|
13 |
raise unless self.class.connection.open_transactions.zero? |
|
14 |
raise unless error.message =~ /[Dd]eadlock|Lock wait timeout exceeded/ |
|
15 |
raise unless retry_count < 10 |
|
16 |
retry_count += 1 |
|
17 |
logger.info "Deadlock detected on retry #{retry_count}, restarting transaction" |
|
18 |
sleep(rand(retry_count)*0.1) # Aloha protocol |
|
19 |
retry |
|
20 |
end |
|
21 |
end |
|
22 | ||
23 |
end |
|
24 |
end |
|
25 |
end |
|
26 |
end |
|
27 |
end |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set/model/movable.rb | ||
---|---|---|
1 |
require 'awesome_nested_set/move' |
|
2 | ||
3 |
module CollectiveIdea #:nodoc: |
|
4 |
module Acts #:nodoc: |
|
5 |
module NestedSet #:nodoc: |
|
6 |
module Model |
|
7 |
module Movable |
|
8 | ||
9 |
def move_possible?(target) |
|
10 |
self != target && # Can't target self |
|
11 |
same_scope?(target) && # can't be in different scopes |
|
12 |
# detect impossible move |
|
13 |
within_bounds?(target.left, target.left) && |
|
14 |
within_bounds?(target.right, target.right) |
|
15 |
end |
|
16 | ||
17 |
# Shorthand method for finding the left sibling and moving to the left of it. |
|
18 |
def move_left |
|
19 |
move_to_left_of left_sibling |
|
20 |
end |
|
21 | ||
22 |
# Shorthand method for finding the right sibling and moving to the right of it. |
|
23 |
def move_right |
|
24 |
move_to_right_of right_sibling |
|
25 |
end |
|
26 | ||
27 |
# Move the node to the left of another node |
|
28 |
def move_to_left_of(node) |
|
29 |
move_to node, :left |
|
30 |
end |
|
31 | ||
32 |
# Move the node to the right of another node |
|
33 |
def move_to_right_of(node) |
|
34 |
move_to node, :right |
|
35 |
end |
|
36 | ||
37 |
# Move the node to the child of another node |
|
38 |
def move_to_child_of(node) |
|
39 |
move_to node, :child |
|
40 |
end |
|
41 | ||
42 |
# Move the node to the child of another node with specify index |
|
43 |
def move_to_child_with_index(node, index) |
|
44 |
if node.children.empty? |
|
45 |
move_to_child_of(node) |
|
46 |
elsif node.children.count == index |
|
47 |
move_to_right_of(node.children.last) |
|
48 |
else |
|
49 |
my_position = node.children.index(self) |
|
50 |
if my_position && my_position < index |
|
51 |
# e.g. if self is at position 0 and we want to move self to position 1 then self |
|
52 |
# needs to move to the *right* of the node at position 1. That's because the node |
|
53 |
# that is currently at position 1 will be at position 0 after the move completes. |
|
54 |
move_to_right_of(node.children[index]) |
|
55 |
elsif my_position && my_position == index |
|
56 |
# do nothing. already there. |
|
57 |
else |
|
58 |
move_to_left_of(node.children[index]) |
|
59 |
end |
|
60 |
end |
|
61 |
end |
|
62 | ||
63 |
# Move the node to root nodes |
|
64 |
def move_to_root |
|
65 |
move_to self, :root |
|
66 |
end |
|
67 | ||
68 |
# Order children in a nested set by an attribute |
|
69 |
# Can order by any attribute class that uses the Comparable mixin, for example a string or integer |
|
70 |
# Usage example when sorting categories alphabetically: @new_category.move_to_ordered_child_of(@root, "name") |
|
71 |
def move_to_ordered_child_of(parent, order_attribute, ascending = true) |
|
72 |
self.move_to_root and return unless parent |
|
73 | ||
74 |
left_neighbor = find_left_neighbor(parent, order_attribute, ascending) |
|
75 |
self.move_to_child_of(parent) |
|
76 | ||
77 |
return unless parent.children.many? |
|
78 | ||
79 |
if left_neighbor |
|
80 |
self.move_to_right_of(left_neighbor) |
|
81 |
else # Self is the left most node. |
|
82 |
self.move_to_left_of(parent.children[0]) |
|
83 |
end |
|
84 |
end |
|
85 | ||
86 |
# Find the node immediately to the left of this node. |
|
87 |
def find_left_neighbor(parent, order_attribute, ascending) |
|
88 |
left = nil |
|
89 |
parent.children.each do |n| |
|
90 |
if ascending |
|
91 |
left = n if n.send(order_attribute) < self.send(order_attribute) |
|
92 |
else |
|
93 |
left = n if n.send(order_attribute) > self.send(order_attribute) |
|
94 |
end |
|
95 |
end |
|
96 |
left |
|
97 |
end |
|
98 | ||
99 |
def move_to(target, position) |
|
100 |
prevent_unpersisted_move |
|
101 | ||
102 |
run_callbacks :move do |
|
103 |
in_tenacious_transaction do |
|
104 |
target = reload_target(target, position) |
|
105 |
self.reload_nested_set |
|
106 | ||
107 |
Move.new(target, position, self).move |
|
108 |
end |
|
109 |
after_move_to(target, position) |
|
110 |
end |
|
111 |
end |
|
112 | ||
113 |
protected |
|
114 | ||
115 |
def after_move_to(target, position) |
|
116 |
target.reload_nested_set if target |
|
117 |
self.set_depth_for_self_and_descendants! |
|
118 |
self.reload_nested_set |
|
119 |
end |
|
120 | ||
121 |
def move_to_new_parent |
|
122 |
if @move_to_new_parent_id.nil? |
|
123 |
move_to_root |
|
124 |
elsif @move_to_new_parent_id |
|
125 |
move_to_child_of(@move_to_new_parent_id) |
|
126 |
end |
|
127 |
end |
|
128 | ||
129 |
def out_of_bounds?(left_bound, right_bound) |
|
130 |
left <= left_bound && right >= right_bound |
|
131 |
end |
|
132 | ||
133 |
def prevent_unpersisted_move |
|
134 |
if self.new_record? |
|
135 |
raise ActiveRecord::ActiveRecordError, "You cannot move a new node" |
|
136 |
end |
|
137 |
end |
|
138 | ||
139 |
def within_bounds?(left_bound, right_bound) |
|
140 |
!out_of_bounds?(left_bound, right_bound) |
|
141 |
end |
|
142 |
end |
|
143 |
end |
|
144 |
end |
|
145 |
end |
|
146 |
end |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/lib/awesome_nested_set.rb | ||
---|---|---|
1 |
require 'awesome_nested_set/awesome_nested_set' |
|
2 |
require 'active_record' |
|
3 |
ActiveRecord::Base.send :extend, CollectiveIdea::Acts::NestedSet |
|
4 | ||
5 |
if defined?(ActionView) |
|
6 |
require 'awesome_nested_set/helper' |
|
7 |
ActionView::Base.send :include, CollectiveIdea::Acts::NestedSet::Helper |
|
8 |
end |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/.travis.yml | ||
---|---|---|
1 |
language: ruby |
|
2 |
script: bundle exec rspec spec |
|
3 |
env: |
|
4 |
- DB=sqlite3 |
|
5 |
- DB=sqlite3mem |
|
6 |
- DB=postgresql |
|
7 |
- DB=mysql |
|
8 |
rvm: |
|
9 |
- 2.0.0 |
|
10 |
- 1.9.3 |
|
11 |
- rbx-2 |
|
12 |
- jruby-19mode |
|
13 |
gemfile: |
|
14 |
- gemfiles/rails_4.0.gemfile |
|
15 |
- gemfiles/rails_4.1.gemfile |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/.autotest | ||
---|---|---|
1 |
Autotest.add_hook :initialize do |at| |
|
2 |
at.clear_mappings |
|
3 | ||
4 |
at.add_mapping %r%^lib/(.*)\.rb$% do |_, m| |
|
5 |
at.files_matching %r%^test/#{m[1]}_test.rb$% |
|
6 |
end |
|
7 | ||
8 |
at.add_mapping(%r%^test/.*\.rb$%) {|filename, _| filename } |
|
9 | ||
10 |
at.add_mapping %r%^test/fixtures/(.*)s.yml% do |_, _| |
|
11 |
at.files_matching %r%^test/.*\.rb$% |
|
12 |
end |
|
13 |
end |
sandbox/rails-4.1/lib/plugins/awesome_nested_set/CHANGELOG | ||
---|---|---|
1 |
* Support Rails 4.1 [Micah Geisel] |
|
2 |
* Support dependent: restrict_with_error [Tiago Moraes] |
|
3 |
* Added information to the README regarding indexes to be added for performance [bdarfler] |
|
4 |
* Modified associate_parents to add child objects to the parent#children collection [Tiago Moraes] |
|
5 | ||
6 |
2.1.6 |
|
7 |
* Fixed rebuild! when there is a default_scope with order [Adrian Serafin] |
|
8 |
* Testing with stable bundler, ruby 2.0, MySQL and PostgreSQL [Philip Arndt] |
|
9 |
* Optimized move_to for large trees [ericsmith66] |
|
10 | ||
11 |
2.1.5 |
|
12 |
* Worked around issues where AR#association wasn't present on Rails 3.0.x. [Philip Arndt] |
|
13 |
* Adds option 'order_column' which defaults to 'left_column_name'. [gudata] |
|
14 |
* Added moving with order functionality. [Sytse Sijbrandij] |
|
15 |
* Use tablename in all select queries. [Mikhail Dieterle] |
|
16 |
* Made sure all descendants' depths are updated when moving parent, not just immediate child. [Phil Thompson] |
|
17 |
* Add documentation of the callbacks. [Tobias Maier] |
|
18 | ||
19 |
2.1.4 |
|
20 |
* nested_set_options accept both Class & AR Relation. [Semyon Perepelitsa] |
|
21 |
* Reduce the number of queries triggered by the canonical usage of `i.level` in the `nested_set` helpers. [thedarkone] |
|
22 |
* Specifically require active_record [Bogdan Gusiev] |
|
23 |
* compute_level now checks for a non nil association target. [Joel Nimety] |
|
24 | ||
25 |
2.1.3 |
|
26 |
* Update child depth when parent node is moved. [Amanda Wagener] |
|
27 |
* Added move_to_child_with_index. [Ben Zhang] |
|
28 |
* Optimised self_and_descendants for when there's an index on lft. [Mark Torrance] |
|
29 |
* Added support for an unsaved record to return the right 'root'. [Philip Arndt] |
|
30 | ||
31 |
2.1.2 |
|
32 |
* Fixed regressions introduced. [Philip Arndt] |
|
33 | ||
34 |
2.1.1 |
|
35 |
* Added 'depth' which indicates how many levels deep the node is. |
|
36 |
This only works when you have a column called 'depth' in your table, |
|
37 |
otherwise it doesn't set itself. [Philip Arndt] |
|
38 |
* Rails 3.2 support added. [Gabriel Sobrinho] |
|
39 |
* Oracle compatibility added. [Pikender Sharma] |
|
40 |
* Adding row locking to deletion, locking source of pivot values, and adding retry on collisions. [Markus J. Q. Roberts] |
|
41 |
* Added method and helper for sorting children by column. [bluegod] |
|
42 |
* Fixed .all_roots_valid? to work with Postgres. [Joshua Clayton] |
|
43 |
* Made compatible with polymorphic belongs_to. [Graham Randall] |
|
44 |
* Added in the association callbacks to the children :has_many association. [Michael Deering] |
|
45 |
* Modified helper to allow using array of objects as argument. [Rahmat Budiharso] |
|
46 |
* Fixed cases where we were calling attr_protected. [Jacob Swanner] |
|
47 |
* Fixed nil cases involving lft and rgt. [Stuart Coyle] and [Patrick Morgan] |
|
48 | ||
49 |
2.0.2 |
|
50 |
* Fixed deprecation warning under Rails 3.1 [Philip Arndt] |
|
51 |
* Converted Test::Unit matchers to RSpec. [Uģis Ozols] |
|
52 |
* Added inverse_of to associations to improve performance rendering trees. [Sergio Cambra] |
|
53 |
* Added row locking and fixed some race conditions. [Markus J. Q. Roberts] |
|
54 | ||
55 |
2.0.1 |
|
56 |
* Fixed a bug with move_to not using nested_set_scope [Andreas Sekine] |
Also available in: Unified diff
Gemfile: use awesome_nested_set gem