acts_as_ordered_tree 1.3.1 → 2.0.0.beta3

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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/lib/acts_as_ordered_tree.rb +22 -100
  3. data/lib/acts_as_ordered_tree/adapters.rb +17 -0
  4. data/lib/acts_as_ordered_tree/adapters/abstract.rb +23 -0
  5. data/lib/acts_as_ordered_tree/adapters/postgresql.rb +150 -0
  6. data/lib/acts_as_ordered_tree/adapters/recursive.rb +157 -0
  7. data/lib/acts_as_ordered_tree/compatibility.rb +22 -0
  8. data/lib/acts_as_ordered_tree/compatibility/active_record/association_scope.rb +9 -0
  9. data/lib/acts_as_ordered_tree/compatibility/active_record/default_scoped.rb +19 -0
  10. data/lib/acts_as_ordered_tree/compatibility/active_record/null_relation.rb +71 -0
  11. data/lib/acts_as_ordered_tree/compatibility/features.rb +153 -0
  12. data/lib/acts_as_ordered_tree/deprecate.rb +24 -0
  13. data/lib/acts_as_ordered_tree/hooks.rb +38 -0
  14. data/lib/acts_as_ordered_tree/hooks/update.rb +86 -0
  15. data/lib/acts_as_ordered_tree/instance_methods.rb +92 -453
  16. data/lib/acts_as_ordered_tree/iterators/arranger.rb +35 -0
  17. data/lib/acts_as_ordered_tree/iterators/level_calculator.rb +52 -0
  18. data/lib/acts_as_ordered_tree/iterators/orphans_pruner.rb +58 -0
  19. data/lib/acts_as_ordered_tree/node.rb +78 -0
  20. data/lib/acts_as_ordered_tree/node/attributes.rb +48 -0
  21. data/lib/acts_as_ordered_tree/node/movement.rb +62 -0
  22. data/lib/acts_as_ordered_tree/node/movements.rb +111 -0
  23. data/lib/acts_as_ordered_tree/node/predicates.rb +98 -0
  24. data/lib/acts_as_ordered_tree/node/reloading.rb +49 -0
  25. data/lib/acts_as_ordered_tree/node/siblings.rb +139 -0
  26. data/lib/acts_as_ordered_tree/node/traversals.rb +53 -0
  27. data/lib/acts_as_ordered_tree/persevering_transaction.rb +93 -0
  28. data/lib/acts_as_ordered_tree/position.rb +143 -0
  29. data/lib/acts_as_ordered_tree/relation/arrangeable.rb +33 -0
  30. data/lib/acts_as_ordered_tree/relation/iterable.rb +41 -0
  31. data/lib/acts_as_ordered_tree/relation/preloaded.rb +46 -11
  32. data/lib/acts_as_ordered_tree/transaction/base.rb +57 -0
  33. data/lib/acts_as_ordered_tree/transaction/callbacks.rb +67 -0
  34. data/lib/acts_as_ordered_tree/transaction/create.rb +68 -0
  35. data/lib/acts_as_ordered_tree/transaction/destroy.rb +34 -0
  36. data/lib/acts_as_ordered_tree/transaction/dsl.rb +214 -0
  37. data/lib/acts_as_ordered_tree/transaction/factory.rb +67 -0
  38. data/lib/acts_as_ordered_tree/transaction/move.rb +70 -0
  39. data/lib/acts_as_ordered_tree/transaction/passthrough.rb +12 -0
  40. data/lib/acts_as_ordered_tree/transaction/reorder.rb +42 -0
  41. data/lib/acts_as_ordered_tree/transaction/save.rb +64 -0
  42. data/lib/acts_as_ordered_tree/transaction/update.rb +78 -0
  43. data/lib/acts_as_ordered_tree/tree.rb +148 -0
  44. data/lib/acts_as_ordered_tree/tree/association.rb +20 -0
  45. data/lib/acts_as_ordered_tree/tree/callbacks.rb +57 -0
  46. data/lib/acts_as_ordered_tree/tree/children_association.rb +120 -0
  47. data/lib/acts_as_ordered_tree/tree/columns.rb +102 -0
  48. data/lib/acts_as_ordered_tree/tree/deprecated_columns_accessors.rb +24 -0
  49. data/lib/acts_as_ordered_tree/tree/parent_association.rb +31 -0
  50. data/lib/acts_as_ordered_tree/tree/perseverance.rb +19 -0
  51. data/lib/acts_as_ordered_tree/tree/scopes.rb +56 -0
  52. data/lib/acts_as_ordered_tree/validators.rb +1 -1
  53. data/lib/acts_as_ordered_tree/version.rb +1 -1
  54. data/spec/acts_as_ordered_tree_spec.rb +80 -909
  55. data/spec/adapters/postgresql_spec.rb +14 -0
  56. data/spec/adapters/recursive_spec.rb +12 -0
  57. data/spec/adapters/shared.rb +272 -0
  58. data/spec/callbacks_spec.rb +177 -0
  59. data/spec/counter_cache_spec.rb +31 -0
  60. data/spec/create_spec.rb +110 -0
  61. data/spec/destroy_spec.rb +57 -0
  62. data/spec/inheritance_spec.rb +176 -0
  63. data/spec/move_spec.rb +94 -0
  64. data/spec/node/movements/concurrent_movements_spec.rb +354 -0
  65. data/spec/node/movements/move_higher_spec.rb +46 -0
  66. data/spec/node/movements/move_lower_spec.rb +46 -0
  67. data/spec/node/movements/move_to_child_of_spec.rb +147 -0
  68. data/spec/node/movements/move_to_child_with_index_spec.rb +124 -0
  69. data/spec/node/movements/move_to_child_with_position_spec.rb +85 -0
  70. data/spec/node/movements/move_to_left_of_spec.rb +120 -0
  71. data/spec/node/movements/move_to_right_of_spec.rb +120 -0
  72. data/spec/node/movements/move_to_root_spec.rb +67 -0
  73. data/spec/node/predicates_spec.rb +211 -0
  74. data/spec/node/reloading_spec.rb +42 -0
  75. data/spec/node/siblings_spec.rb +193 -0
  76. data/spec/node/traversals_spec.rb +71 -0
  77. data/spec/persevering_transaction_spec.rb +98 -0
  78. data/spec/relation/arrangeable_spec.rb +88 -0
  79. data/spec/relation/iterable_spec.rb +104 -0
  80. data/spec/relation/preloaded_spec.rb +57 -0
  81. data/spec/reorder_spec.rb +83 -0
  82. data/spec/spec_helper.rb +30 -38
  83. data/spec/support/db/boot.rb +22 -0
  84. data/spec/{db → support/db}/config.travis.yml +2 -0
  85. data/spec/{db → support/db}/config.yml +1 -0
  86. data/spec/{db → support/db}/schema.rb +9 -0
  87. data/spec/support/factories.rb +2 -2
  88. data/spec/support/matchers.rb +67 -58
  89. data/spec/support/models.rb +6 -14
  90. data/spec/support/tree_factory.rb +315 -0
  91. data/spec/tree/children_association_spec.rb +72 -0
  92. data/spec/tree/columns_spec.rb +65 -0
  93. data/spec/tree/scopes_spec.rb +39 -0
  94. metadata +161 -43
  95. data/lib/acts_as_ordered_tree/adapters/postgresql_adapter.rb +0 -104
  96. data/lib/acts_as_ordered_tree/arrangeable.rb +0 -80
  97. data/lib/acts_as_ordered_tree/class_methods.rb +0 -72
  98. data/lib/acts_as_ordered_tree/relation/base.rb +0 -26
  99. data/lib/acts_as_ordered_tree/tenacious_transaction.rb +0 -30
  100. data/spec/concurrency_support_spec.rb +0 -156
@@ -1,104 +0,0 @@
1
- require 'acts_as_ordered_tree/arrangeable'
2
- require 'acts_as_ordered_tree/relation/preloaded'
3
-
4
- module ActsAsOrderedTree
5
- module Adapters
6
- module PostgreSQLAdapter
7
- # Recursive ancestors fetcher
8
- def self_and_ancestors
9
- if persisted? && !send("#{parent_column}_changed?")
10
- query = <<-QUERY
11
- SELECT id, #{parent_column}, 1 AS _depth
12
- FROM #{self.class.quoted_table_name}
13
- WHERE #{arel[:id].eq(id).to_sql}
14
- UNION ALL
15
- SELECT alias1.id, alias1.#{parent_column}, _depth + 1
16
- FROM #{self.class.quoted_table_name} alias1
17
- INNER JOIN self_and_ancestors ON alias1.id = self_and_ancestors.#{parent_column}
18
- QUERY
19
-
20
- with_recursive_join(query, 'self_and_ancestors').
21
- order('self_and_ancestors._depth DESC').
22
- extending(Arrangeable)
23
- else
24
- (ancestors + [self]).tap { |ary| ary.extend(Arrangeable) }
25
- end
26
- end
27
-
28
- # Recursive ancestors fetcher
29
- def ancestors
30
- query = <<-QUERY
31
- SELECT id, #{parent_column}, 1 AS _depth
32
- FROM #{self.class.quoted_table_name}
33
- WHERE #{arel[:id].eq(parent.try(:id)).to_sql}
34
- UNION ALL
35
- SELECT alias1.id, alias1.#{parent_column}, _depth + 1
36
- FROM #{self.class.quoted_table_name} alias1
37
- INNER JOIN ancestors ON alias1.id = ancestors.#{parent_column}
38
- QUERY
39
-
40
- with_recursive_join(query, 'ancestors').
41
- order('ancestors._depth DESC').
42
- extending(Arrangeable)
43
- end
44
-
45
- def root
46
- root? ? self : ancestors.first
47
- end
48
-
49
- def self_and_descendants
50
- query = <<-QUERY
51
- SELECT id, #{parent_column}, ARRAY[#{position_column}] AS _positions
52
- FROM #{self.class.quoted_table_name}
53
- WHERE #{arel[:id].eq(id).to_sql}
54
- UNION ALL
55
- SELECT alias1.id, alias1.#{parent_column}, _positions || alias1.#{position_column}
56
- FROM descendants INNER JOIN
57
- #{self.class.quoted_table_name} alias1 ON alias1.parent_id = descendants.id
58
- QUERY
59
-
60
- with_recursive_join(query, 'descendants').
61
- order('descendants._positions ASC').
62
- extending(Arrangeable)
63
- end
64
-
65
- def descendants
66
- self_and_descendants.where(arel[:id].not_eq(id))
67
- end
68
-
69
- private
70
- def recursive_scope
71
- ActsAsOrderedTree::Relation::Recursive.new(ordered_tree_scope)
72
- end
73
-
74
- def with_recursive_join(recursive_query_sql, aliaz)
75
- join_sql = 'INNER JOIN (' +
76
- "WITH RECURSIVE #{aliaz} AS (" +
77
- recursive_query_sql +
78
- ") SELECT * FROM #{aliaz} " +
79
- ") #{aliaz} ON #{aliaz}.id = #{self.class.quoted_table_name}.id"
80
-
81
- ordered_tree_scope.joins(join_sql)
82
- end
83
-
84
- # Rails 3.0 does not support update_all with joins, so we patch it :(
85
- if ActiveRecord::VERSION::STRING <= '3.1.0'
86
- module Rails30UpdateAllPatch
87
- def update_all(updates, conditions = nil, options = {})
88
- relation = except(:joins, :where).
89
- where(:id => select(klass.arel_table[:id]).except(:order, :limit).arel)
90
- relation.update_all(updates, conditions, options)
91
- end
92
- end
93
-
94
- def with_recursive_join_30(recursive_query_sql, aliaz)
95
- relation = with_recursive_join_31(recursive_query_sql, aliaz)
96
- relation.extend(Rails30UpdateAllPatch)
97
- relation
98
- end
99
- alias_method :with_recursive_join_31, :with_recursive_join
100
- alias_method :with_recursive_join, :with_recursive_join_30
101
- end
102
- end
103
- end
104
- end
@@ -1,80 +0,0 @@
1
- module ActsAsOrderedTree
2
- module Arrangeable
3
- # @api private
4
- class Arranger
5
- attr_reader :collection, :cache
6
-
7
- def initialize(collection, options = {})
8
- @collection = collection
9
- @discard_orphans = options[:orphans] == :discard
10
- @min_level = nil
11
-
12
- if discard_orphans? && !collection.klass.depth_column && ActiveRecord::Base.logger
13
- ActiveRecord::Base.logger.warn {
14
- '%s model has no `depth` column, '\
15
- 'it can lead to N+1 queries during #arrange method invocation' % collection.klass
16
- }
17
- end
18
-
19
- @cache = Hash.new
20
- @prepared = false
21
- end
22
-
23
- def arrange
24
- prepare unless prepared?
25
-
26
- @arranged ||= collection.each_with_object(Hash.new) do |node, result|
27
- ancestors = ancestors(node)
28
-
29
- if discard_orphans?
30
- root = ancestors.first || node
31
-
32
- next if root.level > @min_level
33
- end
34
-
35
- insertion_point = result
36
-
37
- ancestors.each { |a| insertion_point = (insertion_point[a] ||= {}) }
38
-
39
- insertion_point[node] = {}
40
- end
41
- end
42
-
43
- private
44
- def prepare
45
- collection.each do |node|
46
- cache[node.id] = node if node.id
47
- @min_level = [@min_level, node.level].compact.min
48
- end
49
-
50
- @prepared = true
51
- end
52
-
53
- def discard_orphans?
54
- @discard_orphans
55
- end
56
-
57
- def prepared?
58
- @prepared
59
- end
60
-
61
- # get parent node of +node+
62
- def parent(node)
63
- cache[node[node.parent_column]]
64
- end
65
-
66
- def ancestors(node)
67
- parent = parent(node)
68
- parent ? ancestors(parent) + [parent] : []
69
- end
70
- end
71
- private_constant :Arranger
72
-
73
- # Arrange associated collection into a nested hash of the form
74
- # {node => children}, where children = {} if the node has no children.
75
- def arrange(options = {})
76
- @arranger ||= Arranger.new(self, options)
77
- @arranger.arrange
78
- end
79
- end
80
- end
@@ -1,72 +0,0 @@
1
- require "acts_as_ordered_tree/adapters/postgresql_adapter"
2
-
3
- module ActsAsOrderedTree
4
- module ClassMethods
5
- extend ActiveSupport::Concern
6
-
7
- included do
8
- scope :preorder, -> { order(arel_table[position_column].asc) }
9
- scope :roots, -> { where(arel_table[parent_column].eq(nil)).preorder }
10
-
11
- # add +leaves+ scope only if counter_cache column present
12
- scope :leaves, -> { where(arel_table[children_counter_cache_column].eq(0)) } if
13
- children_counter_cache?
14
-
15
- # when default value for counter_cache is absent we should set it manually
16
- before_create "self.#{children_counter_cache_column} = 0" if children_counter_cache?
17
- end
18
-
19
- module ClassMethods
20
- # Returns the first root
21
- def root
22
- roots.first
23
- end
24
-
25
- private
26
- def children_counter_cache? #:nodoc:
27
- children_counter_cache_column && columns_hash.key?(children_counter_cache_column.to_s)
28
- end
29
-
30
- def setup_ordered_tree_adapter #:nodoc:
31
- include "ActsAsOrderedTree::Adapters::#{connection.class.name.demodulize}".constantize
32
- rescue NameError, LoadError
33
- # ignore
34
- end
35
-
36
- def setup_ordered_tree_callbacks #:nodoc:
37
- define_model_callbacks :move, :reorder
38
-
39
- if depth_column
40
- before_create :set_depth!
41
- before_save :set_depth!, :if => "#{parent_column}_changed?".to_sym
42
- around_move :update_descendants_depth
43
- end
44
-
45
- if children_counter_cache_column
46
- around_move :update_counter_cache
47
- end
48
-
49
- unless scope_column_names.empty?
50
- before_save :set_scope!, :unless => :root?
51
- end
52
-
53
- after_save :move_to_root, :unless => [position_column, parent_column]
54
- after_save 'move_to_child_of(parent)', :if => parent_column, :unless => position_column
55
- after_save "move_to_child_with_index(parent, #{position_column})",
56
- :if => "#{position_column} && (#{position_column}_changed? || #{parent_column}_changed?)"
57
-
58
- before_destroy :flush_descendants
59
- after_destroy "decrement_lower_positions(#{parent_column}_was, #{position_column}_was)", :if => position_column
60
- end
61
-
62
- def setup_ordered_tree_validations #:nodoc:
63
- unless scope_column_names.empty?
64
- validates_with Validators::ScopeValidator, :on => :update, :unless => :root?
65
- end
66
-
67
- # setup validations
68
- validates_with Validators::CyclicReferenceValidator, :on => :update, :if => :parent
69
- end
70
- end # module ClassMethods
71
- end # module ClassMethods
72
- end # module ActsAsOrderedTree
@@ -1,26 +0,0 @@
1
- module ActsAsOrderedTree
2
- module Relation
3
- class Base < ActiveRecord::Relation
4
- EMPTY_SCOPE_METHOD = ActiveRecord::VERSION::STRING < '4.0.0' ? :scoped : :all
5
-
6
- # Create from existing +relation+ or from +class+ and +table+
7
- def initialize(class_or_relation, table = nil)
8
- relation = class_or_relation
9
-
10
- if class_or_relation.is_a?(Class)
11
- relation = class_or_relation.send(EMPTY_SCOPE_METHOD)
12
- table ||= class_or_relation.arel_table
13
-
14
- super(class_or_relation, table)
15
- else
16
- super(class_or_relation.klass, class_or_relation.table)
17
- end
18
-
19
- # copy instance variables from real relation
20
- relation.instance_variables.each do |ivar|
21
- instance_variable_set(ivar, relation.instance_variable_get(ivar))
22
- end
23
- end
24
- end
25
- end
26
- end
@@ -1,30 +0,0 @@
1
- module ActsAsOrderedTree
2
- module TenaciousTransaction
3
- DEADLOCK_MESSAGES = /Deadlock found when trying to get lock|Lock wait timeout exceeded|deadlock detected/.freeze
4
- RETRY_COUNT = 10
5
-
6
- # Partially borrowed from awesome_nested_set
7
- def tenacious_transaction(&block) #:nodoc:
8
- return transaction(&block) if @in_tenacious_transaction
9
-
10
- @in_tenacious_transaction = true
11
- retry_count = 0
12
- begin
13
- transaction(&block)
14
- rescue ActiveRecord::StatementInvalid => error
15
- raise unless self.class.connection.open_transactions.zero?
16
- raise unless error.message =~ DEADLOCK_MESSAGES
17
- raise unless retry_count < RETRY_COUNT
18
- retry_count += 1
19
-
20
- logger.info "Deadlock detected on retry #{retry_count}, restarting transaction"
21
-
22
- sleep(rand(retry_count)*0.1) # Aloha protocol
23
-
24
- retry
25
- ensure
26
- @in_tenacious_transaction = false
27
- end
28
- end
29
- end
30
- end
@@ -1,156 +0,0 @@
1
- require "spec_helper"
2
-
3
- # FIXME: These tests are buggy on Rails 3.0
4
- # Sqlite is not concurrent database
5
- if ActiveRecord::VERSION::STRING >= "3.1" && ENV['DB'] != 'sqlite3'
6
- describe ActsAsOrderedTree, :non_transactional do
7
- module Concurrency
8
- # run block in its own thread, create +size+ threads
9
- def pool(size)
10
- size.times.map { |x|
11
- Thread.new do
12
- ActiveRecord::Base.connection_pool.with_connection { yield x }
13
- end
14
- }.each(&:join)
15
- end
16
- end
17
- include Concurrency
18
-
19
- let!(:root) { create :default }
20
-
21
- it "should not create nodes with same position" do
22
- pool(3) do
23
- create :default, :parent => root
24
- end
25
-
26
- root.children.map(&:position).should eq [1, 2, 3]
27
- end
28
-
29
- it "should not move nodes to same position when moving to child of certain node" do
30
- nodes = create_list :default, 3
31
-
32
- pool(3) do |x|
33
- nodes[x].move_to_child_of(root)
34
- end
35
-
36
- root.children.map(&:position).should eq [1, 2, 3]
37
- end
38
-
39
- it "should not move nodes to same position when moving to left of root node" do
40
- nodes = create_list :default, 3, :parent => root
41
-
42
- pool(3) do |x|
43
- nodes[x].move_to_left_of(root)
44
- end
45
-
46
- Default.roots.map(&:position).should eq [1, 2, 3, 4]
47
- end
48
-
49
- it "should not move nodes to same position when moving to left of child node" do
50
- child = create :default, :parent => root
51
- nodes = create_list :default, 3, :parent => child
52
-
53
- pool(3) do |x|
54
- nodes[x].move_to_left_of(child)
55
- end
56
-
57
- root.children.map(&:position).should eq [1, 2, 3, 4]
58
- root.children.last.should eq child
59
- end
60
-
61
- it "should not move nodes to same position when moving to right of child node" do
62
- child = create :default, :parent => root
63
- nodes = create_list :default, 3, :parent => child
64
-
65
- pool(3) do |x|
66
- nodes[x].move_to_right_of(child)
67
- end
68
-
69
- root.children.map(&:position).should eq [1, 2, 3, 4]
70
- root.children.first.should eq child
71
- end
72
-
73
- it "should not move nodes to same position when moving to root" do
74
- nodes = create_list :default, 3, :parent => root
75
-
76
- pool(3) do |x|
77
- nodes[x].move_to_root
78
- end
79
-
80
- Default.roots.map(&:position).should eq [1, 2, 3, 4]
81
- end
82
-
83
- # checking deadlock also
84
- it "should not move nodes to same position when moving to specified index" do
85
- # root
86
- # * child1
87
- # * nodes1_1
88
- # * nodes1_2
89
- # * child2
90
- # * nodes2_1
91
- # * nodes2_2
92
- child1, child2 = create_list :default, 2, :parent => root
93
-
94
- nodes1, nodes2 = create_list(:default, 2, :parent => child1),
95
- create_list(:default, 2, :parent => child2)
96
-
97
- nodes1_1, nodes1_2 = nodes1
98
- nodes2_1, nodes2_2 = nodes2
99
-
100
- # nodes2_2 -> child1[0]
101
- thread1 = Thread.new do
102
- ActiveRecord::Base.connection_pool.with_connection do
103
- nodes2_2.move_to_child_with_index(child1, 0)
104
- end
105
- end
106
- # nodes1_1 -> child2[2]
107
- thread2 = Thread.new do
108
- ActiveRecord::Base.connection_pool.with_connection do
109
- nodes1_1.move_to_child_with_index(child2, 2)
110
- end
111
- end
112
- [thread1, thread2].map(&:join)
113
-
114
- child1.children.reload.should == [nodes2_2, nodes1_2]
115
- child2.children.reload.should == [nodes2_1, nodes1_1]
116
- end
117
-
118
- it "should not move nodes to same position when moving higher" do
119
- child1, child2, child3 = create_list :default, 3, :parent => root
120
-
121
- thread1 = Thread.new do
122
- ActiveRecord::Base.connection_pool.with_connection do
123
- child2.move_higher
124
- end
125
- end
126
- thread2 = Thread.new do
127
- ActiveRecord::Base.connection_pool.with_connection do
128
- child3.move_higher
129
- end
130
- end
131
-
132
- [thread1, thread2].map(&:join)
133
-
134
- root.children.map(&:position).should eq [1, 2, 3]
135
- end
136
-
137
- it "should not move nodes to same position when moving lower" do
138
- child1, child2, child3 = create_list :default, 3, :parent => root
139
-
140
- thread1 = Thread.new do
141
- ActiveRecord::Base.connection_pool.with_connection do
142
- child1.move_lower
143
- end
144
- end
145
- thread2 = Thread.new do
146
- ActiveRecord::Base.connection_pool.with_connection do
147
- child2.move_lower
148
- end
149
- end
150
-
151
- [thread1, thread2].map(&:join)
152
-
153
- root.children.map(&:position).should eq [1, 2, 3]
154
- end
155
- end
156
- end