acts_as_ordered_tree 1.3.1 → 2.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+
3
+ require 'acts_as_ordered_tree/iterators/arranger'
4
+ require 'acts_as_ordered_tree/iterators/orphans_pruner'
5
+
6
+ module ActsAsOrderedTree
7
+ module Relation
8
+ # This AR::Relation extension allows to arrange collection into
9
+ # Hash of nested Hashes
10
+ module Arrangeable
11
+ # Arrange associated collection into a nested hash of the form
12
+ # {node => children}, where children = {} if the node has no children.
13
+ #
14
+ # It is possible to discard orphaned nodes (nodes which don't have
15
+ # corresponding parent node in this collection) by passing `:orphans => :discard`
16
+ # as option.
17
+ #
18
+ # @param [Hash] options
19
+ # @option options [:discard, nil] :orphans
20
+ # @return [Hash<ActiveRecord::Base => Hash>]
21
+ def arrange(options = {})
22
+ collection = self
23
+
24
+ if options && options[:orphans] == :discard
25
+ collection = Iterators::OrphansPruner.new(self)
26
+ end
27
+
28
+ @arranger ||= Iterators::Arranger.new(collection)
29
+ @arranger.arrange
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,41 @@
1
+ # coding: utf-8
2
+
3
+ require 'acts_as_ordered_tree/iterators/level_calculator'
4
+ require 'acts_as_ordered_tree/iterators/orphans_pruner'
5
+
6
+ module ActsAsOrderedTree
7
+ module Relation
8
+ module Iterable
9
+ # Iterates over tree elements and determines the current level in the tree.
10
+ # Only accepts default ordering, no orphans allowed (they considered as root elements).
11
+ # This method is efficient on trees that don't cache level.
12
+ #
13
+ # @example
14
+ # node.descendants.each_with_level do |descendant, level|
15
+ # end
16
+ #
17
+ # @return [Enumerator] if block is not given
18
+ def each_with_level(&block)
19
+ Iterators::LevelCalculator.new(self).each(&block)
20
+ end
21
+
22
+ # Iterates over tree elements but discards any orphaned nodes (e.g. nodes
23
+ # which have a parent, but parent isn't in current collection).
24
+ #
25
+ # @example Collection with orphaned nodes
26
+ # # Assume we have following tree:
27
+ # # root 1
28
+ # # child 1
29
+ # # root 2
30
+ # # child 2
31
+ #
32
+ # MyModel.where('id != ?', root_1.id).extending(Iterable).each_without_orphans.to_a
33
+ # # => [root_2, child_2]
34
+ #
35
+ # @return [Enumerator] if block is not given
36
+ def each_without_orphans(&block)
37
+ Iterators::OrphansPruner.new(self).each(&block)
38
+ end
39
+ end # module Iterable
40
+ end # module Relation
41
+ end # module ActsAsOrderedTree
@@ -1,16 +1,51 @@
1
- require "acts_as_ordered_tree/relation/base"
1
+ # coding: utf-8
2
2
 
3
3
  module ActsAsOrderedTree
4
4
  module Relation
5
- # Common relation, but with already loaded records
6
- class Preloaded < Base
7
- # Set loaded records to +records+
5
+ # AR::Relation extension which adds ability to explicitly set records
6
+ #
7
+ # @example
8
+ # records = MyModel.where(:parent_id => nil).to_a
9
+ # relation = MyModel.where(:parent_id => nil).
10
+ # extending(ActsAsOrderedTree::Relation::Preloaded).
11
+ # records(records)
12
+ # relation.to_a.should be records
13
+ module Preloaded
8
14
  def records(records)
9
- relation = clone
10
- relation.instance_variable_set :@records, records
11
- relation.instance_variable_set :@loaded, true
12
- relation
15
+ @loaded = false
16
+ @records = records
17
+
18
+ build_where!
19
+
20
+ @loaded = true
21
+
22
+ self
23
+ end
24
+
25
+ # Reverse the existing order of records on the relation.
26
+ def reverse_order
27
+ (respond_to?(:spawn) ? spawn : clone).records(@records.reverse)
28
+ end
29
+
30
+ def reverse_order!
31
+ @records = @records.reverse
32
+
33
+ self
34
+ end
35
+
36
+ # Extending relation is not really intrusive operation, so we can save preloaded records
37
+ def extending(*)
38
+ super.tap { |relation| relation.records(@records) if loaded? }
39
+ end
40
+
41
+ private
42
+ def record_ids
43
+ @records.map { |r| r.id if r }.compact
44
+ end
45
+
46
+ def build_where!
47
+ self.where_values = build_where(:id => record_ids)
13
48
  end
14
- end
15
- end
16
- end
49
+ end # module Preloaded
50
+ end # module Relation
51
+ end # module ActsAsOrderedTree
@@ -0,0 +1,57 @@
1
+ # coding: utf-8
2
+
3
+ require 'acts_as_ordered_tree/persevering_transaction'
4
+ require 'acts_as_ordered_tree/transaction/callbacks'
5
+
6
+ module ActsAsOrderedTree
7
+ module Transaction
8
+ # Persevering transaction, which restarts on deadlock
9
+ #
10
+ # Here we have a tree of possible transaction types:
11
+ #
12
+ # Base (abstract)
13
+ # Save (abstract)
14
+ # Create
15
+ # Update (abstract)
16
+ # Move
17
+ # Reorder
18
+ # Destroy
19
+ #
20
+ # @api private
21
+ class Base
22
+ extend Callbacks
23
+
24
+ attr_reader :node
25
+
26
+ delegate :record, :tree, :to => :node
27
+ delegate :connection, :to => :klass
28
+
29
+ # @param [ActsAsOrderedTree::Node] node
30
+ def initialize(node)
31
+ @node = node
32
+ end
33
+
34
+ # Start persevering transaction, which will restart on deadlock
35
+ def start(&block)
36
+ transaction.start do
37
+ run_callbacks(:transaction, &block)
38
+ end
39
+ end
40
+
41
+ protected
42
+ def klass
43
+ record.class
44
+ end
45
+
46
+ # Returns underlying transaction object
47
+ def transaction
48
+ @transaction ||= PerseveringTransaction.new(connection)
49
+ end
50
+
51
+ # Trigger tree callback (before_add, after_add, before_remove, after_remove)
52
+ def trigger_callback(kind, owner)
53
+ tree.callbacks.send(kind, owner, record) if owner.present?
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,67 @@
1
+ require 'active_support/callbacks'
2
+
3
+ module ActsAsOrderedTree
4
+ module Transaction
5
+ module Callbacks
6
+ def self.extended(base)
7
+ base.send(:include, ActiveSupport::Callbacks)
8
+ base.define_callbacks :transaction
9
+ end
10
+
11
+ def before(filter, *options, &block)
12
+ set_callback :transaction, :before, filter, *options, &block
13
+ end
14
+
15
+ def after(filter, *options, &block)
16
+ set_callback :transaction, :after, filter, *options, &block
17
+ end
18
+
19
+ def around(filter, *options, &block)
20
+ set_callback :transaction, :around, filter, *options, &block
21
+ end
22
+
23
+ # This method should be called in concrete transaction classes to prevent
24
+ # race conditions in multi-threaded environments.
25
+ #
26
+ # @api private
27
+ def finalize
28
+ finalize_callbacks :transaction
29
+ end
30
+
31
+ private
32
+ Compatibility.version '< 3.2.0' do
33
+ def finalize_callbacks(kind)
34
+ __define_runner(kind)
35
+ end
36
+ end
37
+
38
+ Compatibility.version '>= 3.2.0', '< 4.0.0' do
39
+ def finalize_callbacks(kind)
40
+ __reset_runner(kind)
41
+
42
+ object = allocate
43
+
44
+ name = __callback_runner_name(nil, kind)
45
+ unless object.respond_to?(name, true)
46
+ str = object.send("_#{kind}_callbacks").compile(nil, object)
47
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
48
+ def #{name}() #{str} end
49
+ protected :#{name}
50
+ RUBY_EVAL
51
+ end
52
+ end
53
+ end
54
+
55
+ Compatibility.version '>= 4.0.0', '< 4.1.0' do
56
+ def finalize_callbacks(kind)
57
+ __define_callbacks(kind, allocate)
58
+ end
59
+ end
60
+
61
+ # Rails 4.1 is thread safe
62
+ Compatibility.version '>= 4.1.0' do
63
+ def finalize_callbacks(kind) end
64
+ end
65
+ end # module Callbacks
66
+ end # module Transaction
67
+ end # module ActsAsOrderedTree
@@ -0,0 +1,68 @@
1
+ # coding: utf-8
2
+
3
+ require 'acts_as_ordered_tree/transaction/save'
4
+ require 'acts_as_ordered_tree/transaction/dsl'
5
+
6
+ module ActsAsOrderedTree
7
+ module Transaction
8
+ # Create transaction (for new records only)
9
+ # @api private
10
+ class Create < Save
11
+ include DSL
12
+
13
+ before :push_to_bottom_after_commit, :if => 'push_to_bottom? && to.root?'
14
+ before :set_counter_cache, :if => 'tree.columns.counter_cache?'
15
+ before :increment_lower_positions, :unless => :push_to_bottom?
16
+ before 'trigger_callback(:before_add, to.parent)'
17
+
18
+ after 'to.increment_counter'
19
+ after 'node.reload'
20
+ after 'trigger_callback(:after_add, to.parent)'
21
+
22
+ finalize
23
+
24
+ private
25
+ def set_counter_cache
26
+ record[tree.columns.counter_cache] = 0
27
+ end
28
+
29
+ def increment_lower_positions
30
+ to.lower.update_all set position => position + 1
31
+ end
32
+
33
+ # If record was created as root there is a chance that position will collide,
34
+ # but this callback will force record to placed at the bottom of tree.
35
+ #
36
+ # Yep, concurrency is a tough thing.
37
+ #
38
+ # @see https://github.com/take-five/acts_as_ordered_tree/issues/24
39
+ def push_to_bottom_after_commit
40
+ transaction.after_commit do
41
+ connection.logger.debug { "Forcing new record (id=#{record.id}, position=#{node.position}) to be placed to bottom" }
42
+
43
+ connection.transaction do
44
+ # lock new siblings
45
+ to.siblings.lock.reload
46
+
47
+ if positions_collided?
48
+ update_created set position => siblings.select(coalesce(max(position), 0) + 1)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ # Checks if there is +position_column+ collision within new parent
55
+ def positions_collided?
56
+ to.siblings.where(id.not_eq(record.id).and(position.eq(to.position))).exists?
57
+ end
58
+
59
+ def update_created(*args)
60
+ to.siblings.where(id.eq(record.id)).update_all(*args)
61
+ end
62
+
63
+ def siblings
64
+ to.siblings.where(id.not_eq(record.id))
65
+ end
66
+ end # class Create
67
+ end # module Transaction
68
+ end # module ActsAsOrderedTree
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+
3
+ require 'acts_as_ordered_tree/transaction/base'
4
+ require 'acts_as_ordered_tree/transaction/dsl'
5
+
6
+ module ActsAsOrderedTree
7
+ module Transaction
8
+ class Destroy < Base
9
+ include DSL
10
+
11
+ attr_reader :from
12
+
13
+ before 'trigger_callback(:before_remove, from.parent)'
14
+
15
+ after :decrement_lower_positions
16
+ after 'from.decrement_counter'
17
+ after 'trigger_callback(:after_remove, from.parent)'
18
+
19
+ finalize
20
+
21
+ # @param [ActsAsOrderedTree::Node] node
22
+ # @param [ActsAsOrderedTree::Position] from from which position given +node+ is destroyed
23
+ def initialize(node, from)
24
+ super(node)
25
+ @from = from
26
+ end
27
+
28
+ private
29
+ def decrement_lower_positions
30
+ from.lower.update_all set position => position - 1
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,214 @@
1
+ # coding: utf-8
2
+
3
+ require 'active_support/core_ext/string/inflections'
4
+
5
+ module Arel
6
+ module Nodes
7
+ # Case node
8
+ #
9
+ # @example
10
+ # switch.when(table[:x].gt(1), table[:y]).else(table[:z])
11
+ # # CASE WHEN "table"."x" > 1 THEN "table"."y" ELSE "table"."z" END
12
+ # switch.when(table[:x].gt(1)).then(table[:y]).else(table[:z])
13
+ class Case < Arel::Nodes::Node
14
+ include Arel::OrderPredications
15
+ include Arel::Predications
16
+
17
+ attr_reader :conditions, :default
18
+
19
+ def initialize
20
+ @conditions = []
21
+ @default = nil
22
+ end
23
+
24
+ def when(condition, expression = nil)
25
+ @conditions << When.new(condition, expression)
26
+ self
27
+ end
28
+
29
+ def then(expression)
30
+ @conditions.last.right = expression
31
+ self
32
+ end
33
+
34
+ def else(expression)
35
+ @default = Else.new(expression)
36
+ self
37
+ end
38
+ end
39
+
40
+ class When < Arel::Nodes::Binary
41
+ end
42
+
43
+ class Else < Arel::Nodes::Unary
44
+ end
45
+ end
46
+
47
+ module Visitors
48
+ class ToSql < Arel::Visitors::ToSql.superclass
49
+ private
50
+ def visit_Arel_Nodes_Case o, *a
51
+ conditions = o.conditions.map { |x| visit x, *a }.join(' ')
52
+ default = o.default && visit(o.default, *a)
53
+
54
+ "CASE #{[conditions, default].compact.join(' ')} END"
55
+ end
56
+
57
+ def visit_Arel_Nodes_When o, *a
58
+ "WHEN #{visit o.left, *a} THEN #{visit o.right, *a}"
59
+ end
60
+
61
+ def visit_Arel_Nodes_Else o, *a
62
+ "ELSE #{visit o.expr, *a}"
63
+ end
64
+
65
+ if Arel::VERSION >= '6.0.0'
66
+ def visit_Arel_Nodes_Case o, collector
67
+ collector << 'CASE '
68
+ o.conditions.each do |x|
69
+ visit x, collector
70
+ collector << ' '
71
+ end
72
+ if o.default
73
+ visit o.default, collector
74
+ collector << ' '
75
+ end
76
+ collector << 'END'
77
+ end
78
+
79
+ def visit_Arel_Nodes_When o, collector
80
+ collector << 'WHEN '
81
+ visit o.left, collector
82
+ collector << ' THEN '
83
+ visit o.right, collector
84
+ end
85
+
86
+ def visit_Arel_Nodes_Else o, collector
87
+ collector << 'ELSE'
88
+ visit o.expr, collector
89
+ end
90
+
91
+ def visit_NilClass o, collector
92
+ collector << 'NULL'
93
+ end
94
+ end
95
+ end
96
+
97
+ class DepthFirst < Arel::Visitors::Visitor
98
+ def visit_Arel_Nodes_Case o, *a
99
+ visit o.conditions, *a
100
+ visit o.default, *a
101
+ end
102
+ alias :visit_Arel_Nodes_When :binary
103
+ alias :visit_Arel_Nodes_Else :unary
104
+ end
105
+ end
106
+ end
107
+
108
+ module ActsAsOrderedTree
109
+ module Transaction
110
+ # Simple DSL to generate complex UPDATE queries.
111
+ # Requires +record+ method.
112
+ #
113
+ # @api private
114
+ module DSL
115
+ module Shortcuts
116
+ INFIX_OPERATIONS = Hash[
117
+ :== => Arel::Nodes::Equality,
118
+ :'!=' => Arel::Nodes::NotEqual,
119
+ :> => Arel::Nodes::GreaterThan,
120
+ :>= => Arel::Nodes::GreaterThanOrEqual,
121
+ :< => Arel::Nodes::LessThan,
122
+ :<= => Arel::Nodes::LessThanOrEqual,
123
+ :=~ => Arel::Nodes::Matches,
124
+ :'!~' => Arel::Nodes::DoesNotMatch,
125
+ :| => Arel::Nodes::Or
126
+ ]
127
+
128
+ # generate subclasses and methods
129
+ INFIX_OPERATIONS.each do |operator, klass|
130
+ subclass = Class.new(klass) { include Shortcuts }
131
+ const_set(klass.name.demodulize, subclass)
132
+ INFIX_OPERATIONS[operator] = subclass
133
+
134
+ define_method(operator) do |arg|
135
+ subclass.new(self, arg)
136
+ end
137
+ end
138
+
139
+ And = Class.new(Arel::Nodes::And) { include Shortcuts }
140
+
141
+ def &(arg)
142
+ And.new [self, arg]
143
+ end
144
+ end
145
+
146
+ Attribute = Class.new(Arel::Attributes::Attribute) { include Shortcuts }
147
+ SqlLiteral = Class.new(Arel::Nodes::SqlLiteral) { include Shortcuts }
148
+
149
+ NamedFunction = Class.new(Arel::Nodes::NamedFunction) {
150
+ include Shortcuts
151
+ include Arel::Math
152
+ }
153
+
154
+ # Create Arel::Nodes::Case node
155
+ def switch
156
+ Arel::Nodes::Case.new
157
+ end
158
+
159
+ # Create assignments expression for UPDATE statement
160
+ #
161
+ # @example
162
+ # Model.where(:parent_id => nil).update_all(set :name => switch.when(x < 10).then('OK').else('TOO LARGE'))
163
+ #
164
+ # @param [Hash] assignments
165
+ def set(assignments)
166
+ assignments.map do |attr, value|
167
+ next unless attr.present?
168
+
169
+ name = attr.is_a?(Arel::Attributes::Attribute) ? attr.name : attr.to_s
170
+
171
+ quoted = record.class.connection.quote_column_name(name)
172
+ "#{quoted} = (#{value.to_sql})"
173
+ end.join(', ')
174
+ end
175
+
176
+ def attribute(name)
177
+ name && Attribute.new(table, name.to_sym)
178
+ end
179
+
180
+ def expression(expr)
181
+ SqlLiteral.new(expr.to_s)
182
+ end
183
+
184
+ def id
185
+ attribute(record.ordered_tree.columns.id)
186
+ end
187
+
188
+ def parent_id
189
+ attribute(record.ordered_tree.columns.parent)
190
+ end
191
+
192
+ def position
193
+ attribute(record.ordered_tree.columns.position)
194
+ end
195
+
196
+ def depth
197
+ attribute(record.ordered_tree.columns.depth)
198
+ end
199
+
200
+ def table
201
+ record.class.arel_table
202
+ end
203
+
204
+ def method_missing(id, *args)
205
+ if args.length > 0
206
+ # function
207
+ NamedFunction.new(id.to_s.upcase, args)
208
+ else
209
+ super
210
+ end
211
+ end
212
+ end # module DSL
213
+ end # module Transaction
214
+ end # module ActsAsOrderedTree