##// END OF EJS Templates
awesome_nested_set: import git 2-1-stable branch revision 8eaab19868f326 (#6579)...
Toshi MARUYAMA -
r12870:9ea1ae48dbed
parent child
Show More
@@ -1,68 +1,72
1 # Mixed into both classes and instances to provide easy access to the column names
1 # Mixed into both classes and instances to provide easy access to the column names
2 module CollectiveIdea #:nodoc:
2 module CollectiveIdea #:nodoc:
3 module Acts #:nodoc:
3 module Acts #:nodoc:
4 module NestedSet #:nodoc:
4 module NestedSet #:nodoc:
5 module Columns
5 module Columns
6 def left_column_name
6 def left_column_name
7 acts_as_nested_set_options[:left_column]
7 acts_as_nested_set_options[:left_column]
8 end
8 end
9
9
10 def right_column_name
10 def right_column_name
11 acts_as_nested_set_options[:right_column]
11 acts_as_nested_set_options[:right_column]
12 end
12 end
13
13
14 def depth_column_name
14 def depth_column_name
15 acts_as_nested_set_options[:depth_column]
15 acts_as_nested_set_options[:depth_column]
16 end
16 end
17
17
18 def parent_column_name
18 def parent_column_name
19 acts_as_nested_set_options[:parent_column]
19 acts_as_nested_set_options[:parent_column]
20 end
20 end
21
21
22 def order_column
22 def order_column
23 acts_as_nested_set_options[:order_column] || left_column_name
23 acts_as_nested_set_options[:order_column] || left_column_name
24 end
24 end
25
25
26 def scope_column_names
26 def scope_column_names
27 Array(acts_as_nested_set_options[:scope])
27 Array(acts_as_nested_set_options[:scope])
28 end
28 end
29
29
30 def quoted_left_column_name
30 def quoted_left_column_name
31 connection.quote_column_name(left_column_name)
31 connection.quote_column_name(left_column_name)
32 end
32 end
33
33
34 def quoted_right_column_name
34 def quoted_right_column_name
35 connection.quote_column_name(right_column_name)
35 connection.quote_column_name(right_column_name)
36 end
36 end
37
37
38 def quoted_depth_column_name
38 def quoted_depth_column_name
39 connection.quote_column_name(depth_column_name)
39 connection.quote_column_name(depth_column_name)
40 end
40 end
41
41
42 def quoted_parent_column_name
42 def quoted_parent_column_name
43 connection.quote_column_name(parent_column_name)
43 connection.quote_column_name(parent_column_name)
44 end
44 end
45
45
46 def quoted_scope_column_names
46 def quoted_scope_column_names
47 scope_column_names.collect {|column_name| connection.quote_column_name(column_name) }
47 scope_column_names.collect {|column_name| connection.quote_column_name(column_name) }
48 end
48 end
49
49
50 def quoted_order_column_name
50 def quoted_order_column_name
51 connection.quote_column_name(order_column)
51 connection.quote_column_name(order_column)
52 end
52 end
53
53
54 def quoted_order_column_full_name
55 "#{quoted_table_name}.#{quoted_order_column_name}"
56 end
57
54 def quoted_left_column_full_name
58 def quoted_left_column_full_name
55 "#{quoted_table_name}.#{quoted_left_column_name}"
59 "#{quoted_table_name}.#{quoted_left_column_name}"
56 end
60 end
57
61
58 def quoted_right_column_full_name
62 def quoted_right_column_full_name
59 "#{quoted_table_name}.#{quoted_right_column_name}"
63 "#{quoted_table_name}.#{quoted_right_column_name}"
60 end
64 end
61
65
62 def quoted_parent_column_full_name
66 def quoted_parent_column_full_name
63 "#{quoted_table_name}.#{quoted_parent_column_name}"
67 "#{quoted_table_name}.#{quoted_parent_column_name}"
64 end
68 end
65 end
69 end
66 end
70 end
67 end
71 end
68 end
72 end
@@ -1,212 +1,212
1 require 'awesome_nested_set/model/prunable'
1 require 'awesome_nested_set/model/prunable'
2 require 'awesome_nested_set/model/movable'
2 require 'awesome_nested_set/model/movable'
3 require 'awesome_nested_set/model/transactable'
3 require 'awesome_nested_set/model/transactable'
4 require 'awesome_nested_set/model/relatable'
4 require 'awesome_nested_set/model/relatable'
5 require 'awesome_nested_set/model/rebuildable'
5 require 'awesome_nested_set/model/rebuildable'
6 require 'awesome_nested_set/model/validatable'
6 require 'awesome_nested_set/model/validatable'
7 require 'awesome_nested_set/iterator'
7 require 'awesome_nested_set/iterator'
8
8
9 module CollectiveIdea #:nodoc:
9 module CollectiveIdea #:nodoc:
10 module Acts #:nodoc:
10 module Acts #:nodoc:
11 module NestedSet #:nodoc:
11 module NestedSet #:nodoc:
12
12
13 module Model
13 module Model
14 extend ActiveSupport::Concern
14 extend ActiveSupport::Concern
15
15
16 included do
16 included do
17 delegate :quoted_table_name, :arel_table, :to => self
17 delegate :quoted_table_name, :arel_table, :to => self
18 extend Validatable
18 extend Validatable
19 extend Rebuildable
19 extend Rebuildable
20 include Movable
20 include Movable
21 include Prunable
21 include Prunable
22 include Relatable
22 include Relatable
23 include Transactable
23 include Transactable
24 end
24 end
25
25
26 module ClassMethods
26 module ClassMethods
27 def associate_parents(objects)
27 def associate_parents(objects)
28 return objects unless objects.all? {|o| o.respond_to?(:association)}
28 return objects unless objects.all? {|o| o.respond_to?(:association)}
29
29
30 id_indexed = objects.index_by(&:id)
30 id_indexed = objects.index_by(&:id)
31 objects.each do |object|
31 objects.each do |object|
32 association = object.association(:parent)
32 association = object.association(:parent)
33 parent = id_indexed[object.parent_id]
33 parent = id_indexed[object.parent_id]
34
34
35 if !association.loaded? && parent
35 if !association.loaded? && parent
36 association.target = parent
36 association.target = parent
37 association.set_inverse_instance(parent)
37 association.set_inverse_instance(parent)
38 end
38 end
39 end
39 end
40 end
40 end
41
41
42 def children_of(parent_id)
42 def children_of(parent_id)
43 where arel_table[parent_column_name].eq(parent_id)
43 where arel_table[parent_column_name].eq(parent_id)
44 end
44 end
45
45
46 # Iterates over tree elements and determines the current level in the tree.
46 # Iterates over tree elements and determines the current level in the tree.
47 # Only accepts default ordering, odering by an other column than lft
47 # Only accepts default ordering, odering by an other column than lft
48 # does not work. This method is much more efficent than calling level
48 # does not work. This method is much more efficent than calling level
49 # because it doesn't require any additional database queries.
49 # because it doesn't require any additional database queries.
50 #
50 #
51 # Example:
51 # Example:
52 # Category.each_with_level(Category.root.self_and_descendants) do |o, level|
52 # Category.each_with_level(Category.root.self_and_descendants) do |o, level|
53 #
53 #
54 def each_with_level(objects, &block)
54 def each_with_level(objects, &block)
55 Iterator.new(objects).each_with_level(&block)
55 Iterator.new(objects).each_with_level(&block)
56 end
56 end
57
57
58 def leaves
58 def leaves
59 nested_set_scope.where "#{quoted_right_column_full_name} - #{quoted_left_column_full_name} = 1"
59 nested_set_scope.where "#{quoted_right_column_full_name} - #{quoted_left_column_full_name} = 1"
60 end
60 end
61
61
62 def left_of(node)
62 def left_of(node)
63 where arel_table[left_column_name].lt(node)
63 where arel_table[left_column_name].lt(node)
64 end
64 end
65
65
66 def left_of_right_side(node)
66 def left_of_right_side(node)
67 where arel_table[right_column_name].lteq(node)
67 where arel_table[right_column_name].lteq(node)
68 end
68 end
69
69
70 def right_of(node)
70 def right_of(node)
71 where arel_table[left_column_name].gteq(node)
71 where arel_table[left_column_name].gteq(node)
72 end
72 end
73
73
74 def nested_set_scope(options = {})
74 def nested_set_scope(options = {})
75 options = {:order => quoted_order_column_name}.merge(options)
75 options = {:order => quoted_order_column_full_name}.merge(options)
76
76
77 order(options.delete(:order)).scoped options
77 order(options.delete(:order)).scoped options
78 end
78 end
79
79
80 def primary_key_scope(id)
80 def primary_key_scope(id)
81 where arel_table[primary_key].eq(id)
81 where arel_table[primary_key].eq(id)
82 end
82 end
83
83
84 def root
84 def root
85 roots.first
85 roots.first
86 end
86 end
87
87
88 def roots
88 def roots
89 nested_set_scope.children_of nil
89 nested_set_scope.children_of nil
90 end
90 end
91 end # end class methods
91 end # end class methods
92
92
93 # 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.
93 # 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.
94 #
94 #
95 # category.self_and_descendants.count
95 # category.self_and_descendants.count
96 # category.ancestors.find(:all, :conditions => "name like '%foo%'")
96 # category.ancestors.find(:all, :conditions => "name like '%foo%'")
97 # Value of the parent column
97 # Value of the parent column
98 def parent_id(target = self)
98 def parent_id(target = self)
99 target[parent_column_name]
99 target[parent_column_name]
100 end
100 end
101
101
102 # Value of the left column
102 # Value of the left column
103 def left(target = self)
103 def left(target = self)
104 target[left_column_name]
104 target[left_column_name]
105 end
105 end
106
106
107 # Value of the right column
107 # Value of the right column
108 def right(target = self)
108 def right(target = self)
109 target[right_column_name]
109 target[right_column_name]
110 end
110 end
111
111
112 # Returns true if this is a root node.
112 # Returns true if this is a root node.
113 def root?
113 def root?
114 parent_id.nil?
114 parent_id.nil?
115 end
115 end
116
116
117 # Returns true is this is a child node
117 # Returns true is this is a child node
118 def child?
118 def child?
119 !root?
119 !root?
120 end
120 end
121
121
122 # Returns true if this is the end of a branch.
122 # Returns true if this is the end of a branch.
123 def leaf?
123 def leaf?
124 persisted? && right.to_i - left.to_i == 1
124 persisted? && right.to_i - left.to_i == 1
125 end
125 end
126
126
127 # All nested set queries should use this nested_set_scope, which
127 # All nested set queries should use this nested_set_scope, which
128 # performs finds on the base ActiveRecord class, using the :scope
128 # performs finds on the base ActiveRecord class, using the :scope
129 # declared in the acts_as_nested_set declaration.
129 # declared in the acts_as_nested_set declaration.
130 def nested_set_scope(options = {})
130 def nested_set_scope(options = {})
131 if (scopes = Array(acts_as_nested_set_options[:scope])).any?
131 if (scopes = Array(acts_as_nested_set_options[:scope])).any?
132 options[:conditions] = scopes.inject({}) do |conditions,attr|
132 options[:conditions] = scopes.inject({}) do |conditions,attr|
133 conditions.merge attr => self[attr]
133 conditions.merge attr => self[attr]
134 end
134 end
135 end
135 end
136
136
137 self.class.nested_set_scope options
137 self.class.nested_set_scope options
138 end
138 end
139
139
140 def to_text
140 def to_text
141 self_and_descendants.map do |node|
141 self_and_descendants.map do |node|
142 "#{'*'*(node.level+1)} #{node.id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})"
142 "#{'*'*(node.level+1)} #{node.id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})"
143 end.join("\n")
143 end.join("\n")
144 end
144 end
145
145
146 protected
146 protected
147
147
148 def without_self(scope)
148 def without_self(scope)
149 return scope if new_record?
149 return scope if new_record?
150 scope.where(["#{self.class.quoted_table_name}.#{self.class.primary_key} != ?", self])
150 scope.where(["#{self.class.quoted_table_name}.#{self.class.primary_key} != ?", self])
151 end
151 end
152
152
153 def store_new_parent
153 def store_new_parent
154 @move_to_new_parent_id = send("#{parent_column_name}_changed?") ? parent_id : false
154 @move_to_new_parent_id = send("#{parent_column_name}_changed?") ? parent_id : false
155 true # force callback to return true
155 true # force callback to return true
156 end
156 end
157
157
158 def has_depth_column?
158 def has_depth_column?
159 nested_set_scope.column_names.map(&:to_s).include?(depth_column_name.to_s)
159 nested_set_scope.column_names.map(&:to_s).include?(depth_column_name.to_s)
160 end
160 end
161
161
162 def right_most_node
162 def right_most_node
163 @right_most_node ||= self.class.base_class.unscoped.nested_set_scope(
163 @right_most_node ||= self.class.base_class.unscoped.nested_set_scope(
164 :order => "#{quoted_right_column_full_name} desc"
164 :order => "#{quoted_right_column_full_name} desc"
165 ).first
165 ).first
166 end
166 end
167
167
168 def right_most_bound
168 def right_most_bound
169 @right_most_bound ||= begin
169 @right_most_bound ||= begin
170 return 0 if right_most_node.nil?
170 return 0 if right_most_node.nil?
171
171
172 right_most_node.lock!
172 right_most_node.lock!
173 right_most_node[right_column_name] || 0
173 right_most_node[right_column_name] || 0
174 end
174 end
175 end
175 end
176
176
177 def set_depth!
177 def set_depth!
178 return unless has_depth_column?
178 return unless has_depth_column?
179
179
180 in_tenacious_transaction do
180 in_tenacious_transaction do
181 reload
181 reload
182 nested_set_scope.primary_key_scope(id).
182 nested_set_scope.primary_key_scope(id).
183 update_all(["#{quoted_depth_column_name} = ?", level])
183 update_all(["#{quoted_depth_column_name} = ?", level])
184 end
184 end
185 self[depth_column_name] = self.level
185 self[depth_column_name] = self.level
186 end
186 end
187
187
188 def set_default_left_and_right
188 def set_default_left_and_right
189 # adds the new node to the right of all existing nodes
189 # adds the new node to the right of all existing nodes
190 self[left_column_name] = right_most_bound + 1
190 self[left_column_name] = right_most_bound + 1
191 self[right_column_name] = right_most_bound + 2
191 self[right_column_name] = right_most_bound + 2
192 end
192 end
193
193
194 # reload left, right, and parent
194 # reload left, right, and parent
195 def reload_nested_set
195 def reload_nested_set
196 reload(
196 reload(
197 :select => "#{quoted_left_column_full_name}, #{quoted_right_column_full_name}, #{quoted_parent_column_full_name}",
197 :select => "#{quoted_left_column_full_name}, #{quoted_right_column_full_name}, #{quoted_parent_column_full_name}",
198 :lock => true
198 :lock => true
199 )
199 )
200 end
200 end
201
201
202 def reload_target(target)
202 def reload_target(target)
203 if target.is_a? self.class.base_class
203 if target.is_a? self.class.base_class
204 target.reload
204 target.reload
205 else
205 else
206 nested_set_scope.find(target)
206 nested_set_scope.find(target)
207 end
207 end
208 end
208 end
209 end
209 end
210 end
210 end
211 end
211 end
212 end
212 end
@@ -1,58 +1,61
1 module CollectiveIdea #:nodoc:
1 module CollectiveIdea #:nodoc:
2 module Acts #:nodoc:
2 module Acts #:nodoc:
3 module NestedSet #:nodoc:
3 module NestedSet #:nodoc:
4 module Model
4 module Model
5 module Prunable
5 module Prunable
6
6
7 # Prunes a branch off of the tree, shifting all of the elements on the right
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.
8 # back to the left so the counts still work.
9 def destroy_descendants
9 def destroy_descendants
10 return if right.nil? || left.nil? || skip_before_destroy
10 return if right.nil? || left.nil? || skip_before_destroy
11
11
12 in_tenacious_transaction do
12 in_tenacious_transaction do
13 reload_nested_set
13 reload_nested_set
14 # select the rows in the model that extend past the deletion point and apply a lock
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(id).lock(true)
15 nested_set_scope.right_of(left).select(id).lock(true)
16
16
17 destroy_or_delete_descendants
17 destroy_or_delete_descendants
18
18
19 # update lefts and rights for remaining nodes
19 # update lefts and rights for remaining nodes
20 update_siblings_for_remaining_nodes
20 update_siblings_for_remaining_nodes
21
21
22 # Reload is needed because children may have updated their parent (self) during deletion.
23 reload
24
22 # Don't allow multiple calls to destroy to corrupt the set
25 # Don't allow multiple calls to destroy to corrupt the set
23 self.skip_before_destroy = true
26 self.skip_before_destroy = true
24 end
27 end
25 end
28 end
26
29
27 def destroy_or_delete_descendants
30 def destroy_or_delete_descendants
28 if acts_as_nested_set_options[:dependent] == :destroy
31 if acts_as_nested_set_options[:dependent] == :destroy
29 descendants.each do |model|
32 descendants.each do |model|
30 model.skip_before_destroy = true
33 model.skip_before_destroy = true
31 model.destroy
34 model.destroy
32 end
35 end
33 else
36 else
34 descendants.delete_all
37 descendants.delete_all
35 end
38 end
36 end
39 end
37
40
38 def update_siblings_for_remaining_nodes
41 def update_siblings_for_remaining_nodes
39 update_siblings(:left)
42 update_siblings(:left)
40 update_siblings(:right)
43 update_siblings(:right)
41 end
44 end
42
45
43 def update_siblings(direction)
46 def update_siblings(direction)
44 full_column_name = send("quoted_#{direction}_column_full_name")
47 full_column_name = send("quoted_#{direction}_column_full_name")
45 column_name = send("quoted_#{direction}_column_name")
48 column_name = send("quoted_#{direction}_column_name")
46
49
47 nested_set_scope.where(["#{full_column_name} > ?", right]).
50 nested_set_scope.where(["#{full_column_name} > ?", right]).
48 update_all(["#{column_name} = (#{column_name} - ?)", diff])
51 update_all(["#{column_name} = (#{column_name} - ?)", diff])
49 end
52 end
50
53
51 def diff
54 def diff
52 right - left + 1
55 right - left + 1
53 end
56 end
54 end
57 end
55 end
58 end
56 end
59 end
57 end
60 end
58 end
61 end
@@ -1,69 +1,69
1 require 'awesome_nested_set/set_validator'
1 require 'awesome_nested_set/set_validator'
2
2
3 module CollectiveIdea
3 module CollectiveIdea
4 module Acts
4 module Acts
5 module NestedSet
5 module NestedSet
6 module Model
6 module Model
7 module Validatable
7 module Validatable
8
8
9 def valid?
9 def valid?
10 left_and_rights_valid? && no_duplicates_for_columns? && all_roots_valid?
10 left_and_rights_valid? && no_duplicates_for_columns? && all_roots_valid?
11 end
11 end
12
12
13 def left_and_rights_valid?
13 def left_and_rights_valid?
14 SetValidator.new(self).valid?
14 SetValidator.new(self).valid?
15 end
15 end
16
16
17 def no_duplicates_for_columns?
17 def no_duplicates_for_columns?
18 [quoted_left_column_full_name, quoted_right_column_full_name].all? do |column|
18 [quoted_left_column_full_name, quoted_right_column_full_name].all? do |column|
19 # No duplicates
19 # No duplicates
20 select("#{scope_string}#{column}, COUNT(#{column})").
20 select("#{scope_string}#{column}, COUNT(#{column}) as _count").
21 group("#{scope_string}#{column}").
21 group("#{scope_string}#{column}").
22 having("COUNT(#{column}) > 1").
22 having("COUNT(#{column}) > 1").
23 first.nil?
23 first.nil?
24 end
24 end
25 end
25 end
26
26
27 # Wrapper for each_root_valid? that can deal with scope.
27 # Wrapper for each_root_valid? that can deal with scope.
28 def all_roots_valid?
28 def all_roots_valid?
29 if acts_as_nested_set_options[:scope]
29 if acts_as_nested_set_options[:scope]
30 all_roots_valid_by_scope?(roots)
30 all_roots_valid_by_scope?(roots)
31 else
31 else
32 each_root_valid?(roots)
32 each_root_valid?(roots)
33 end
33 end
34 end
34 end
35
35
36 def all_roots_valid_by_scope?(roots_to_validate)
36 def all_roots_valid_by_scope?(roots_to_validate)
37 roots_grouped_by_scope(roots_to_validate).all? do |scope, grouped_roots|
37 roots_grouped_by_scope(roots_to_validate).all? do |scope, grouped_roots|
38 each_root_valid?(grouped_roots)
38 each_root_valid?(grouped_roots)
39 end
39 end
40 end
40 end
41
41
42 def each_root_valid?(roots_to_validate)
42 def each_root_valid?(roots_to_validate)
43 left = right = 0
43 left = right = 0
44 roots_to_validate.all? do |root|
44 roots_to_validate.all? do |root|
45 (root.left > left && root.right > right).tap do
45 (root.left > left && root.right > right).tap do
46 left = root.left
46 left = root.left
47 right = root.right
47 right = root.right
48 end
48 end
49 end
49 end
50 end
50 end
51
51
52 private
52 private
53 def roots_grouped_by_scope(roots_to_group)
53 def roots_grouped_by_scope(roots_to_group)
54 roots_to_group.group_by {|record|
54 roots_to_group.group_by {|record|
55 scope_column_names.collect {|col| record.send(col) }
55 scope_column_names.collect {|col| record.send(col) }
56 }
56 }
57 end
57 end
58
58
59 def scope_string
59 def scope_string
60 Array(acts_as_nested_set_options[:scope]).map do |c|
60 Array(acts_as_nested_set_options[:scope]).map do |c|
61 connection.quote_column_name(c)
61 connection.quote_column_name(c)
62 end.push(nil).join(", ")
62 end.push(nil).join(", ")
63 end
63 end
64
64
65 end
65 end
66 end
66 end
67 end
67 end
68 end
68 end
69 end
69 end
@@ -1,117 +1,130
1 module CollectiveIdea #:nodoc:
1 module CollectiveIdea #:nodoc:
2 module Acts #:nodoc:
2 module Acts #:nodoc:
3 module NestedSet #:nodoc:
3 module NestedSet #:nodoc:
4 class Move
4 class Move
5 attr_reader :target, :position, :instance
5 attr_reader :target, :position, :instance
6
6
7 def initialize(target, position, instance)
7 def initialize(target, position, instance)
8 @target = target
8 @target = target
9 @position = position
9 @position = position
10 @instance = instance
10 @instance = instance
11 end
11 end
12
12
13 def move
13 def move
14 prevent_impossible_move
14 prevent_impossible_move
15
15
16 bound, other_bound = get_boundaries
16 bound, other_bound = get_boundaries
17
17
18 # there would be no change
18 # there would be no change
19 return if bound == right || bound == left
19 return if bound == right || bound == left
20
20
21 # we have defined the boundaries of two non-overlapping intervals,
21 # we have defined the boundaries of two non-overlapping intervals,
22 # so sorting puts both the intervals and their boundaries in order
22 # so sorting puts both the intervals and their boundaries in order
23 a, b, c, d = [left, right, bound, other_bound].sort
23 a, b, c, d = [left, right, bound, other_bound].sort
24
24
25 lock_nodes_between! a, d
25 lock_nodes_between! a, d
26
26
27 nested_set_scope.where(where_statement(a, d)).
27 nested_set_scope.where(where_statement(a, d)).update_all(
28 update_all(conditions(a, b, c, d))
28 conditions(a, b, c, d)
29 )
29 end
30 end
30
31
31 private
32 private
32
33
33 delegate :left, :right, :left_column_name, :right_column_name,
34 delegate :left, :right, :left_column_name, :right_column_name,
34 :quoted_left_column_name, :quoted_right_column_name,
35 :quoted_left_column_name, :quoted_right_column_name,
35 :quoted_parent_column_name, :parent_column_name, :nested_set_scope,
36 :quoted_parent_column_name, :parent_column_name, :nested_set_scope,
36 :to => :instance
37 :to => :instance
37
38
38 delegate :arel_table, :class, :to => :instance, :prefix => true
39 delegate :arel_table, :class, :to => :instance, :prefix => true
39 delegate :base_class, :to => :instance_class, :prefix => :instance
40 delegate :base_class, :to => :instance_class, :prefix => :instance
40
41
41 def where_statement(left_bound, right_bound)
42 def where_statement(left_bound, right_bound)
42 instance_arel_table[left_column_name].in(left_bound..right_bound).
43 instance_arel_table[left_column_name].in(left_bound..right_bound).
43 or(instance_arel_table[right_column_name].in(left_bound..right_bound))
44 or(instance_arel_table[right_column_name].in(left_bound..right_bound))
44 end
45 end
45
46
46 def conditions(a, b, c, d)
47 def conditions(a, b, c, d)
48 _conditions = case_condition_for_direction(:quoted_left_column_name) +
49 case_condition_for_direction(:quoted_right_column_name) +
50 case_condition_for_parent
51
52 # We want the record to be 'touched' if it timestamps.
53 if @instance.respond_to?(:updated_at)
54 _conditions << ", updated_at = :timestamp"
55 end
56
47 [
57 [
48 case_condition_for_direction(:quoted_left_column_name) +
58 _conditions,
49 case_condition_for_direction(:quoted_right_column_name) +
59 {
50 case_condition_for_parent,
60 :a => a, :b => b, :c => c, :d => d,
51 {:a => a, :b => b, :c => c, :d => d, :id => instance.id, :new_parent => new_parent}
61 :id => instance.id,
62 :new_parent => new_parent,
63 :timestamp => Time.now.utc
64 }
52 ]
65 ]
53 end
66 end
54
67
55 def case_condition_for_direction(column_name)
68 def case_condition_for_direction(column_name)
56 column = send(column_name)
69 column = send(column_name)
57 "#{column} = CASE " +
70 "#{column} = CASE " +
58 "WHEN #{column} BETWEEN :a AND :b " +
71 "WHEN #{column} BETWEEN :a AND :b " +
59 "THEN #{column} + :d - :b " +
72 "THEN #{column} + :d - :b " +
60 "WHEN #{column} BETWEEN :c AND :d " +
73 "WHEN #{column} BETWEEN :c AND :d " +
61 "THEN #{column} + :a - :c " +
74 "THEN #{column} + :a - :c " +
62 "ELSE #{column} END, "
75 "ELSE #{column} END, "
63 end
76 end
64
77
65 def case_condition_for_parent
78 def case_condition_for_parent
66 "#{quoted_parent_column_name} = CASE " +
79 "#{quoted_parent_column_name} = CASE " +
67 "WHEN #{instance_base_class.primary_key} = :id THEN :new_parent " +
80 "WHEN #{instance_base_class.primary_key} = :id THEN :new_parent " +
68 "ELSE #{quoted_parent_column_name} END"
81 "ELSE #{quoted_parent_column_name} END"
69 end
82 end
70
83
71 def lock_nodes_between!(left_bound, right_bound)
84 def lock_nodes_between!(left_bound, right_bound)
72 # select the rows in the model between a and d, and apply a lock
85 # select the rows in the model between a and d, and apply a lock
73 instance_base_class.right_of(left_bound).left_of_right_side(right_bound).
86 instance_base_class.right_of(left_bound).left_of_right_side(right_bound).
74 select(:id).lock(true)
87 select(:id).lock(true)
75 end
88 end
76
89
77 def root
90 def root
78 position == :root
91 position == :root
79 end
92 end
80
93
81 def new_parent
94 def new_parent
82 case position
95 case position
83 when :child
96 when :child
84 target.id
97 target.id
85 else
98 else
86 target[parent_column_name]
99 target[parent_column_name]
87 end
100 end
88 end
101 end
89
102
90 def get_boundaries
103 def get_boundaries
91 if (bound = target_bound) > right
104 if (bound = target_bound) > right
92 bound -= 1
105 bound -= 1
93 other_bound = right + 1
106 other_bound = right + 1
94 else
107 else
95 other_bound = left - 1
108 other_bound = left - 1
96 end
109 end
97 [bound, other_bound]
110 [bound, other_bound]
98 end
111 end
99
112
100 def prevent_impossible_move
113 def prevent_impossible_move
101 if !root && !instance.move_possible?(target)
114 if !root && !instance.move_possible?(target)
102 raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree."
115 raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree."
103 end
116 end
104 end
117 end
105
118
106 def target_bound
119 def target_bound
107 case position
120 case position
108 when :child; right(target)
121 when :child; right(target)
109 when :left; left(target)
122 when :left; left(target)
110 when :right; right(target) + 1
123 when :right; right(target) + 1
111 else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)."
124 else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)."
112 end
125 end
113 end
126 end
114 end
127 end
115 end
128 end
116 end
129 end
117 end
130 end
General Comments 0
You need to be logged in to leave comments. Login now