awesome_nested_set 2.1.6 → 3.0.0.rc.6

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