Project

General



Profile

« Previous | Next » 

Revision 13292

Added by Toshi MARUYAMA about 11 years ago

Gemfile: use awesome_nested_set gem

View differences:

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]
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff