loyal_awesome_nested_set 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
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
+ str << "AND #{connection.quote_column_name(column_name)} = #{connection.quote(node.send(column_name))} "
26
+ }
27
+ }
28
+ end
29
+ scope
30
+ end
31
+
32
+ def order_for_rebuild
33
+ "#{quoted_left_column_full_name}, #{quoted_right_column_full_name}, #{primary_key}"
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,121 @@
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.find(parent_id)
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
@@ -0,0 +1,27 @@
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 connection.open_transactions.zero?
14
+ raise unless error.message =~ /Deadlock found when trying to get lock|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
@@ -0,0 +1,69 @@
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})").
21
+ group("#{scope_string}#{column}", quoted_primary_key_column_full_name).
22
+ having("COUNT(#{column}) > 1").
23
+ first.nil?
24
+ end
25
+ end
26
+
27
+ # Wrapper for each_root_valid? that can deal with scope.
28
+ def all_roots_valid?
29
+ if acts_as_nested_set_options[:scope]
30
+ all_roots_valid_by_scope?(roots)
31
+ else
32
+ each_root_valid?(roots)
33
+ end
34
+ end
35
+
36
+ def all_roots_valid_by_scope?(roots_to_validate)
37
+ roots_grouped_by_scope(roots_to_validate).all? do |scope, grouped_roots|
38
+ each_root_valid?(grouped_roots)
39
+ end
40
+ end
41
+
42
+ def each_root_valid?(roots_to_validate)
43
+ left = right = 0
44
+ roots_to_validate.all? do |root|
45
+ (root.left > left && root.right > right).tap do
46
+ left = root.left
47
+ right = root.right
48
+ end
49
+ end
50
+ end
51
+
52
+ private
53
+ def roots_grouped_by_scope(roots_to_group)
54
+ roots_to_group.group_by {|record|
55
+ scope_column_names.collect {|col| record.send(col) }
56
+ }
57
+ end
58
+
59
+ def scope_string
60
+ Array(acts_as_nested_set_options[:scope]).map do |c|
61
+ connection.quote_column_name(c)
62
+ end.push(nil).join(", ")
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,117 @@
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)).
28
+ update_all(conditions(a, b, c, d))
29
+ end
30
+
31
+ private
32
+
33
+ delegate :left, :right, :left_column_name, :right_column_name,
34
+ :quoted_left_column_name, :quoted_right_column_name,
35
+ :quoted_parent_column_name, :parent_column_name, :nested_set_scope,
36
+ :to => :instance
37
+
38
+ delegate :arel_table, :class, :to => :instance, :prefix => true
39
+ delegate :base_class, :to => :instance_class, :prefix => :instance
40
+
41
+ def where_statement(left_bound, right_bound)
42
+ 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
+ end
45
+
46
+ def conditions(a, b, c, d)
47
+ [
48
+ case_condition_for_direction(:quoted_left_column_name) +
49
+ case_condition_for_direction(:quoted_right_column_name) +
50
+ case_condition_for_parent,
51
+ {:a => a, :b => b, :c => c, :d => d, :id => instance.id, :new_parent => new_parent}
52
+ ]
53
+ end
54
+
55
+ def case_condition_for_direction(column_name)
56
+ column = send(column_name)
57
+ "#{column} = CASE " +
58
+ "WHEN #{column} BETWEEN :a AND :b " +
59
+ "THEN #{column} + :d - :b " +
60
+ "WHEN #{column} BETWEEN :c AND :d " +
61
+ "THEN #{column} + :a - :c " +
62
+ "ELSE #{column} END, "
63
+ end
64
+
65
+ def case_condition_for_parent
66
+ "#{quoted_parent_column_name} = CASE " +
67
+ "WHEN #{instance_base_class.primary_key} = :id THEN :new_parent " +
68
+ "ELSE #{quoted_parent_column_name} END"
69
+ end
70
+
71
+ def lock_nodes_between!(left_bound, right_bound)
72
+ # 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).
74
+ select(:id).lock(true)
75
+ end
76
+
77
+ def root
78
+ position == :root
79
+ end
80
+
81
+ def new_parent
82
+ case position
83
+ when :child
84
+ target.id
85
+ else
86
+ target[parent_column_name]
87
+ end
88
+ end
89
+
90
+ def get_boundaries
91
+ if (bound = target_bound) > right
92
+ bound -= 1
93
+ other_bound = right + 1
94
+ else
95
+ other_bound = left - 1
96
+ end
97
+ [bound, other_bound]
98
+ end
99
+
100
+ def prevent_impossible_move
101
+ if !root && !instance.move_possible?(target)
102
+ raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree."
103
+ end
104
+ end
105
+
106
+ def target_bound
107
+ case position
108
+ when :child; right(target)
109
+ when :left; left(target)
110
+ when :right; right(target) + 1
111
+ else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)."
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ 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_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,
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_key].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]).
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