closure_tree 6.5.0 → 7.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +96 -0
  3. data/.gitignore +2 -0
  4. data/.rspec +1 -1
  5. data/Appraisals +90 -7
  6. data/CHANGELOG.md +94 -42
  7. data/Gemfile +0 -12
  8. data/README.md +66 -23
  9. data/Rakefile +7 -10
  10. data/_config.yml +1 -0
  11. data/bin/appraisal +29 -0
  12. data/bin/rake +29 -0
  13. data/bin/rspec +29 -0
  14. data/closure_tree.gemspec +16 -9
  15. data/lib/closure_tree/finders.rb +32 -9
  16. data/lib/closure_tree/has_closure_tree.rb +4 -0
  17. data/lib/closure_tree/has_closure_tree_root.rb +5 -7
  18. data/lib/closure_tree/hash_tree_support.rb +4 -4
  19. data/lib/closure_tree/hierarchy_maintenance.rb +28 -8
  20. data/lib/closure_tree/model.rb +42 -16
  21. data/lib/closure_tree/numeric_deterministic_ordering.rb +20 -6
  22. data/lib/closure_tree/numeric_order_support.rb +7 -3
  23. data/lib/closure_tree/support.rb +18 -12
  24. data/lib/closure_tree/support_attributes.rb +10 -1
  25. data/lib/closure_tree/support_flags.rb +1 -4
  26. data/lib/closure_tree/version.rb +1 -1
  27. data/lib/generators/closure_tree/migration_generator.rb +8 -0
  28. data/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb +1 -1
  29. metadata +78 -79
  30. data/.travis.yml +0 -29
  31. data/gemfiles/activerecord_4.2.gemfile +0 -19
  32. data/gemfiles/activerecord_5.0.gemfile +0 -19
  33. data/gemfiles/activerecord_5.0_foreigner.gemfile +0 -20
  34. data/gemfiles/activerecord_edge.gemfile +0 -20
  35. data/img/example.png +0 -0
  36. data/img/preorder.png +0 -0
  37. data/spec/cache_invalidation_spec.rb +0 -39
  38. data/spec/cuisine_type_spec.rb +0 -38
  39. data/spec/db/database.yml +0 -21
  40. data/spec/db/models.rb +0 -128
  41. data/spec/db/schema.rb +0 -166
  42. data/spec/fixtures/tags.yml +0 -98
  43. data/spec/generators/migration_generator_spec.rb +0 -48
  44. data/spec/has_closure_tree_root_spec.rb +0 -154
  45. data/spec/hierarchy_maintenance_spec.rb +0 -16
  46. data/spec/label_spec.rb +0 -554
  47. data/spec/matcher_spec.rb +0 -34
  48. data/spec/metal_spec.rb +0 -55
  49. data/spec/model_spec.rb +0 -9
  50. data/spec/namespace_type_spec.rb +0 -13
  51. data/spec/parallel_spec.rb +0 -159
  52. data/spec/spec_helper.rb +0 -24
  53. data/spec/support/database.rb +0 -52
  54. data/spec/support/database_cleaner.rb +0 -14
  55. data/spec/support/exceed_query_limit.rb +0 -18
  56. data/spec/support/hash_monkey_patch.rb +0 -13
  57. data/spec/support/query_counter.rb +0 -18
  58. data/spec/support/sqlite3_with_advisory_lock.rb +0 -10
  59. data/spec/support_spec.rb +0 -14
  60. data/spec/tag_examples.rb +0 -665
  61. data/spec/tag_spec.rb +0 -6
  62. data/spec/user_spec.rb +0 -174
  63. data/spec/uuid_tag_spec.rb +0 -6
@@ -8,6 +8,8 @@ module ClosureTree
8
8
  :hierarchy_table_name,
9
9
  :name_column,
10
10
  :order,
11
+ :dont_order_roots,
12
+ :numeric_order,
11
13
  :touch,
12
14
  :with_advisory_lock
13
15
  )
@@ -29,6 +31,8 @@ module ClosureTree
29
31
 
30
32
  include ClosureTree::DeterministicOrdering if _ct.order_option?
31
33
  include ClosureTree::NumericDeterministicOrdering if _ct.order_is_numeric?
34
+
35
+ connection_pool.release_connection
32
36
  rescue StandardError => e
33
37
  raise e unless ClosureTree.configuration.database_less
34
38
  end
@@ -1,18 +1,14 @@
1
1
  module ClosureTree
2
2
  class MultipleRootError < StandardError; end
3
+ class RootOrderingDisabledError < StandardError; end
3
4
 
4
5
  module HasClosureTreeRoot
5
6
 
6
7
  def has_closure_tree_root(assoc_name, options = {})
7
- options.assert_valid_keys(
8
- :class_name,
9
- :foreign_key
10
- )
11
-
12
- options[:class_name] ||= assoc_name.to_s.sub(/\Aroot_/, "").classify
8
+ options[:class_name] ||= assoc_name.to_s.sub(/\Aroot_/, "").classify
13
9
  options[:foreign_key] ||= self.name.underscore << "_id"
14
10
 
15
- has_one assoc_name, -> { where(parent: nil) }, options
11
+ has_one assoc_name, -> { where(parent: nil) }, **options
16
12
 
17
13
  # Fetches the association, eager loading all children and given associations
18
14
  define_method("#{assoc_name}_including_tree") do |*args|
@@ -81,6 +77,8 @@ module ClosureTree
81
77
 
82
78
  @closure_tree_roots[assoc_name][assoc_map] = root
83
79
  end
80
+
81
+ connection_pool.release_connection
84
82
  end
85
83
  end
86
84
  end
@@ -4,17 +4,17 @@ module ClosureTree
4
4
  # Deepest generation, within limit, for each descendant
5
5
  # NOTE: Postgres requires HAVING clauses to always contains aggregate functions (!!)
6
6
  having_clause = limit_depth ? "HAVING MAX(generations) <= #{limit_depth - 1}" : ''
7
- generation_depth = <<-SQL.strip_heredoc
7
+ generation_depth = <<-SQL.squish
8
8
  INNER JOIN (
9
9
  SELECT descendant_id, MAX(generations) as depth
10
10
  FROM #{quoted_hierarchy_table_name}
11
11
  GROUP BY descendant_id
12
12
  #{having_clause}
13
- ) AS generation_depth
13
+ ) #{ t_alias_keyword } generation_depth
14
14
  ON #{quoted_table_name}.#{model_class.primary_key} = generation_depth.descendant_id
15
15
  SQL
16
16
  scope_with_order(scope.joins(generation_depth), 'generation_depth.depth')
17
- end
17
+ end
18
18
 
19
19
  def hash_tree(tree_scope, limit_depth = nil)
20
20
  limited_scope = limit_depth ? tree_scope.where("#{quoted_hierarchy_table_name}.generations <= #{limit_depth - 1}") : tree_scope
@@ -33,4 +33,4 @@ module ClosureTree
33
33
  tree
34
34
  end
35
35
  end
36
- end
36
+ end
@@ -20,7 +20,7 @@ module ClosureTree
20
20
  end
21
21
 
22
22
  def _ct_validate
23
- if !@_ct_skip_cycle_detection &&
23
+ if !(defined? @_ct_skip_cycle_detection) &&
24
24
  !new_record? && # don't validate for cycles if we're a new record
25
25
  changes[_ct.parent_column_name] && # don't validate for cycles if we didn't change our parent
26
26
  parent.present? && # don't validate if we're root
@@ -35,10 +35,13 @@ module ClosureTree
35
35
  end
36
36
 
37
37
  def _ct_after_save
38
- if changes[_ct.parent_column_name] || @was_new_record
38
+ as_5_1 = ActiveSupport.version >= Gem::Version.new('5.1.0')
39
+ changes_method = as_5_1 ? :saved_changes : :changes
40
+
41
+ if public_send(changes_method)[_ct.parent_column_name] || @was_new_record
39
42
  rebuild!
40
43
  end
41
- if changes[_ct.parent_column_name] && !@was_new_record
44
+ if public_send(changes_method)[_ct.parent_column_name] && !@was_new_record
42
45
  # Resetting the ancestral collections addresses
43
46
  # https://github.com/mceachen/closure_tree/issues/68
44
47
  ancestor_hierarchies.reload
@@ -61,10 +64,10 @@ module ClosureTree
61
64
 
62
65
  def rebuild!(called_by_rebuild = false)
63
66
  _ct.with_advisory_lock do
64
- delete_hierarchy_references unless @was_new_record
67
+ delete_hierarchy_references unless (defined? @was_new_record) && @was_new_record
65
68
  hierarchy_class.create!(:ancestor => self, :descendant => self, :generations => 0)
66
69
  unless root?
67
- _ct.connection.execute <<-SQL.strip_heredoc
70
+ _ct.connection.execute <<-SQL.squish
68
71
  INSERT INTO #{_ct.quoted_hierarchy_table_name}
69
72
  (ancestor_id, descendant_id, generations)
70
73
  SELECT x.ancestor_id, #{_ct.quote(_ct_id)}, x.generations + 1
@@ -91,7 +94,7 @@ module ClosureTree
91
94
  # It shouldn't affect performance of postgresql.
92
95
  # See http://dev.mysql.com/doc/refman/5.0/en/subquery-errors.html
93
96
  # Also: PostgreSQL doesn't support INNER JOIN on DELETE, so we can't use that.
94
- _ct.connection.execute <<-SQL.strip_heredoc
97
+ _ct.connection.execute <<-SQL.squish
95
98
  DELETE FROM #{_ct.quoted_hierarchy_table_name}
96
99
  WHERE descendant_id IN (
97
100
  SELECT DISTINCT descendant_id
@@ -99,7 +102,7 @@ module ClosureTree
99
102
  FROM #{_ct.quoted_hierarchy_table_name}
100
103
  WHERE ancestor_id = #{_ct.quote(id)}
101
104
  OR descendant_id = #{_ct.quote(id)}
102
- ) AS x )
105
+ ) #{ _ct.t_alias_keyword } x )
103
106
  SQL
104
107
  end
105
108
  end
@@ -109,11 +112,28 @@ module ClosureTree
109
112
  # Note that the hierarchy table will be truncated.
110
113
  def rebuild!
111
114
  _ct.with_advisory_lock do
112
- hierarchy_class.delete_all # not destroy_all -- we just want a simple truncate.
115
+ cleanup!
113
116
  roots.find_each { |n| n.send(:rebuild!) } # roots just uses the parent_id column, so this is safe.
114
117
  end
115
118
  nil
116
119
  end
120
+
121
+ def cleanup!
122
+ hierarchy_table = hierarchy_class.arel_table
123
+
124
+ [:descendant_id, :ancestor_id].each do |foreign_key|
125
+ alias_name = foreign_key.to_s.split('_').first + "s"
126
+ alias_table = Arel::Table.new(table_name).alias(alias_name)
127
+ arel_join = hierarchy_table.join(alias_table, Arel::Nodes::OuterJoin)
128
+ .on(alias_table[primary_key].eq(hierarchy_table[foreign_key]))
129
+ .join_sources
130
+
131
+ lonely_childs = hierarchy_class.joins(arel_join).where(alias_table[primary_key].eq(nil))
132
+ ids = lonely_childs.pluck(foreign_key)
133
+
134
+ hierarchy_class.where(hierarchy_table[foreign_key].in(ids)).delete_all
135
+ end
136
+ end
117
137
  end
118
138
  end
119
139
  end
@@ -6,20 +6,20 @@ module ClosureTree
6
6
 
7
7
  included do
8
8
 
9
- belongs_to :parent, nil, *_ct.belongs_to_with_optional_option(
9
+ belongs_to :parent, nil, **_ct.belongs_to_with_optional_option(
10
10
  class_name: _ct.model_class.to_s,
11
11
  foreign_key: _ct.parent_column_name,
12
12
  inverse_of: :children,
13
13
  touch: _ct.options[:touch],
14
14
  optional: true)
15
15
 
16
- order_by_generations = "#{_ct.quoted_hierarchy_table_name}.generations asc"
16
+ order_by_generations = -> { Arel.sql("#{_ct.quoted_hierarchy_table_name}.generations ASC") }
17
17
 
18
- has_many :children, *_ct.has_many_with_order_option(
18
+ has_many :children, *_ct.has_many_order_with_option, **{
19
19
  class_name: _ct.model_class.to_s,
20
20
  foreign_key: _ct.parent_column_name,
21
21
  dependent: _ct.options[:dependent],
22
- inverse_of: :parent) do
22
+ inverse_of: :parent } do
23
23
  # We have to redefine hash_tree because the activerecord relation is already scoped to parent_id.
24
24
  def hash_tree(options = {})
25
25
  # we want limit_depth + 1 because we don't do self_and_descendants.
@@ -28,25 +28,21 @@ module ClosureTree
28
28
  end
29
29
  end
30
30
 
31
- has_many :ancestor_hierarchies, *_ct.has_many_without_order_option(
31
+ has_many :ancestor_hierarchies, *_ct.has_many_order_without_option(order_by_generations),
32
32
  class_name: _ct.hierarchy_class_name,
33
- foreign_key: 'descendant_id',
34
- order: order_by_generations)
33
+ foreign_key: 'descendant_id'
35
34
 
36
- has_many :self_and_ancestors, *_ct.has_many_without_order_option(
35
+ has_many :self_and_ancestors, *_ct.has_many_order_without_option(order_by_generations),
37
36
  through: :ancestor_hierarchies,
38
- source: :ancestor,
39
- order: order_by_generations)
37
+ source: :ancestor
40
38
 
41
- has_many :descendant_hierarchies, *_ct.has_many_without_order_option(
39
+ has_many :descendant_hierarchies, *_ct.has_many_order_without_option(order_by_generations),
42
40
  class_name: _ct.hierarchy_class_name,
43
- foreign_key: 'ancestor_id',
44
- order: order_by_generations)
41
+ foreign_key: 'ancestor_id'
45
42
 
46
- has_many :self_and_descendants, *_ct.has_many_with_order_option(
43
+ has_many :self_and_descendants, *_ct.has_many_order_with_option(order_by_generations),
47
44
  through: :descendant_hierarchies,
48
- source: :descendant,
49
- order: order_by_generations)
45
+ source: :descendant
50
46
  end
51
47
 
52
48
  # Delegate to the Support instance on the class:
@@ -134,6 +130,36 @@ module ClosureTree
134
130
  _ct.ids_from(siblings)
135
131
  end
136
132
 
133
+ # node's parent is this record
134
+ def parent_of?(node)
135
+ self == node.parent
136
+ end
137
+
138
+ # node's root is this record
139
+ def root_of?(node)
140
+ self == node.root
141
+ end
142
+
143
+ # node's ancestors include this record
144
+ def ancestor_of?(node)
145
+ node.ancestors.include? self
146
+ end
147
+
148
+ # node is record's ancestor
149
+ def descendant_of?(node)
150
+ self.ancestors.include? node
151
+ end
152
+
153
+ # node is record's parent
154
+ def child_of?(node)
155
+ self.parent == node
156
+ end
157
+
158
+ # node and record have a same root
159
+ def family_of?(node)
160
+ self.root == node.root
161
+ end
162
+
137
163
  # Alias for appending to the children collection.
138
164
  # You can also add directly to the children collection, if you'd prefer.
139
165
  def add_child(child_node)
@@ -10,8 +10,13 @@ module ClosureTree
10
10
  end
11
11
 
12
12
  def _ct_reorder_prior_siblings_if_parent_changed
13
- if attribute_changed?(_ct.parent_column_name) && !@was_new_record
14
- was_parent_id = attribute_was(_ct.parent_column_name)
13
+ as_5_1 = ActiveSupport.version >= Gem::Version.new('5.1.0')
14
+ change_method = as_5_1 ? :saved_change_to_attribute? : :attribute_changed?
15
+
16
+ if public_send(change_method, _ct.parent_column_name) && !@was_new_record
17
+ attribute_method = as_5_1 ? :attribute_before_last_save : :attribute_was
18
+
19
+ was_parent_id = public_send(attribute_method, _ct.parent_column_name)
15
20
  _ct.reorder_with_parent_id(was_parent_id)
16
21
  end
17
22
  end
@@ -46,7 +51,7 @@ module ClosureTree
46
51
 
47
52
  # If node is nil, order the whole tree.
48
53
  def _ct_sum_order_by(node = nil)
49
- stats_sql = <<-SQL.strip_heredoc
54
+ stats_sql = <<-SQL.squish
50
55
  SELECT
51
56
  count(*) as total_descendants,
52
57
  max(generations) as max_depth
@@ -60,11 +65,16 @@ module ClosureTree
60
65
  node_score = "(1 + anc.#{_ct.quoted_order_column(false)}) * " +
61
66
  "power(#{h['total_descendants']}, #{h['max_depth'].to_i + 1} - #{depth_column})"
62
67
 
63
- "sum(#{node_score})"
68
+ # We want the NULLs to be first in case we are not ordering roots and they have NULL order.
69
+ Arel.sql("SUM(#{node_score}) IS NULL DESC, SUM(#{node_score})")
64
70
  end
65
71
 
66
72
  def roots_and_descendants_preordered
67
- join_sql = <<-SQL.strip_heredoc
73
+ if _ct.dont_order_roots
74
+ raise ClosureTree::RootOrderingDisabledError.new("Root ordering is disabled on this model")
75
+ end
76
+
77
+ join_sql = <<-SQL.squish
68
78
  JOIN #{_ct.quoted_hierarchy_table_name} anc_hier
69
79
  ON anc_hier.descendant_id = #{_ct.quoted_table_name}.#{_ct.quoted_id_column_name}
70
80
  JOIN #{_ct.quoted_table_name} anc
@@ -73,7 +83,7 @@ module ClosureTree
73
83
  SELECT descendant_id, max(generations) AS max_depth
74
84
  FROM #{_ct.quoted_hierarchy_table_name}
75
85
  GROUP BY descendant_id
76
- ) AS depths ON depths.descendant_id = anc.#{_ct.quoted_id_column_name}
86
+ ) #{ _ct.t_alias_keyword } depths ON depths.descendant_id = anc.#{_ct.quoted_id_column_name}
77
87
  SQL
78
88
  joins(join_sql)
79
89
  .group("#{_ct.quoted_table_name}.#{_ct.quoted_id_column_name}")
@@ -108,6 +118,10 @@ module ClosureTree
108
118
  def add_sibling(sibling, add_after = true)
109
119
  fail "can't add self as sibling" if self == sibling
110
120
 
121
+ if _ct.dont_order_roots && parent.nil?
122
+ raise ClosureTree::RootOrderingDisabledError.new("Root ordering is disabled on this model")
123
+ end
124
+
111
125
  # Make sure self isn't dirty, because we're going to call reload:
112
126
  save
113
127
 
@@ -14,13 +14,14 @@ module ClosureTree
14
14
 
15
15
  module MysqlAdapter
16
16
  def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil)
17
+ return if parent_id.nil? && dont_order_roots
17
18
  min_where = if minimum_sort_order_value
18
19
  "AND #{quoted_order_column} >= #{minimum_sort_order_value}"
19
20
  else
20
21
  ""
21
22
  end
22
23
  connection.execute 'SET @i = 0'
23
- connection.execute <<-SQL.strip_heredoc
24
+ connection.execute <<-SQL.squish
24
25
  UPDATE #{quoted_table_name}
25
26
  SET #{quoted_order_column} = (@i := @i + 1) + #{minimum_sort_order_value.to_i - 1}
26
27
  WHERE #{where_eq(parent_column_name, parent_id)} #{min_where}
@@ -31,12 +32,13 @@ module ClosureTree
31
32
 
32
33
  module PostgreSQLAdapter
33
34
  def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil)
35
+ return if parent_id.nil? && dont_order_roots
34
36
  min_where = if minimum_sort_order_value
35
37
  "AND #{quoted_order_column} >= #{minimum_sort_order_value}"
36
38
  else
37
39
  ""
38
40
  end
39
- connection.execute <<-SQL.strip_heredoc
41
+ connection.execute <<-SQL.squish
40
42
  UPDATE #{quoted_table_name}
41
43
  SET #{quoted_order_column(false)} = t.seq + #{minimum_sort_order_value.to_i - 1}
42
44
  FROM (
@@ -44,7 +46,8 @@ module ClosureTree
44
46
  FROM #{quoted_table_name}
45
47
  WHERE #{where_eq(parent_column_name, parent_id)} #{min_where}
46
48
  ) AS t
47
- WHERE #{quoted_table_name}.#{quoted_id_column_name} = t.id
49
+ WHERE #{quoted_table_name}.#{quoted_id_column_name} = t.id and
50
+ #{quoted_table_name}.#{quoted_order_column(false)} is distinct from t.seq + #{minimum_sort_order_value.to_i - 1}
48
51
  SQL
49
52
  end
50
53
 
@@ -55,6 +58,7 @@ module ClosureTree
55
58
 
56
59
  module GenericAdapter
57
60
  def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil)
61
+ return if parent_id.nil? && dont_order_roots
58
62
  scope = model_class.
59
63
  where(parent_column_sym => parent_id).
60
64
  order(nulls_last_order_by)
@@ -22,7 +22,8 @@ module ClosureTree
22
22
  :parent_column_name => 'parent_id',
23
23
  :dependent => :nullify, # or :destroy or :delete_all -- see the README
24
24
  :name_column => 'name',
25
- :with_advisory_lock => true
25
+ :with_advisory_lock => true,
26
+ :numeric_order => false
26
27
  }.merge(options)
27
28
  raise ArgumentError, "name_column can't be 'path'" if options[:name_column] == 'path'
28
29
  if order_is_numeric?
@@ -31,13 +32,15 @@ module ClosureTree
31
32
  end
32
33
 
33
34
  def hierarchy_class_for_model
34
- hierarchy_class = model_class.parent.const_set(short_hierarchy_class_name, Class.new(ActiveRecord::Base))
35
+ parent_class = ActiveSupport::VERSION::MAJOR >= 6 ? model_class.module_parent : model_class.parent
36
+ hierarchy_class = parent_class.const_set(short_hierarchy_class_name, Class.new(ActiveRecord::Base))
35
37
  use_attr_accessible = use_attr_accessible?
36
38
  include_forbidden_attributes_protection = include_forbidden_attributes_protection?
37
- hierarchy_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
39
+ model_class_name = model_class.to_s
40
+ hierarchy_class.class_eval do
38
41
  include ActiveModel::ForbiddenAttributesProtection if include_forbidden_attributes_protection
39
- belongs_to :ancestor, :class_name => "#{model_class}"
40
- belongs_to :descendant, :class_name => "#{model_class}"
42
+ belongs_to :ancestor, class_name: model_class_name
43
+ belongs_to :descendant, class_name: model_class_name
41
44
  attr_accessible :ancestor, :descendant, :generations if use_attr_accessible
42
45
  def ==(other)
43
46
  self.class == other.class && ancestor_id == other.ancestor_id && descendant_id == other.descendant_id
@@ -46,7 +49,7 @@ module ClosureTree
46
49
  def hash
47
50
  ancestor_id.hash << 31 ^ descendant_id.hash
48
51
  end
49
- RUBY
52
+ end
50
53
  hierarchy_class.table_name = hierarchy_table_name
51
54
  hierarchy_class
52
55
  end
@@ -77,17 +80,20 @@ module ClosureTree
77
80
  end
78
81
 
79
82
  def belongs_to_with_optional_option(opts)
80
- [ActiveRecord::VERSION::MAJOR < 5 ? opts.except(:optional) : opts]
83
+ ActiveRecord::VERSION::MAJOR < 5 ? opts.except(:optional) : opts
81
84
  end
82
85
 
83
86
  # lambda-ize the order, but don't apply the default order_option
84
- def has_many_without_order_option(opts)
85
- [lambda { order(opts[:order]) }, opts.except(:order)]
87
+ def has_many_order_without_option(order_by_opt)
88
+ [lambda { order(order_by_opt.call) }]
86
89
  end
87
90
 
88
- def has_many_with_order_option(opts)
89
- order_options = [opts[:order], order_by].compact
90
- [lambda { order(order_options) }, opts.except(:order)]
91
+ def has_many_order_with_option(order_by_opt=nil)
92
+ order_options = [order_by_opt, order_by].compact
93
+ [lambda {
94
+ order_options = order_options.map { |o| o.is_a?(Proc) ? o.call : o }
95
+ order(order_options)
96
+ }]
91
97
  end
92
98
 
93
99
  def ids_from(scope)
@@ -75,8 +75,12 @@ module ClosureTree
75
75
  options[:order]
76
76
  end
77
77
 
78
+ def dont_order_roots
79
+ options[:dont_order_roots] || false
80
+ end
81
+
78
82
  def nulls_last_order_by
79
- "-#{quoted_order_column} #{order_by_order(reverse = true)}"
83
+ Arel.sql "-#{quoted_order_column} #{order_by_order(true)}"
80
84
  end
81
85
 
82
86
  def order_by_order(reverse = false)
@@ -110,5 +114,10 @@ module ClosureTree
110
114
  prefix = include_table_name ? "#{quoted_table_name}." : ""
111
115
  "#{prefix}#{connection.quote_column_name(order_column)}"
112
116
  end
117
+
118
+ # table_name alias keyword , like "AS". When used on table name alias, Oracle Database don't support used 'AS'
119
+ def t_alias_keyword
120
+ (ActiveRecord::Base.connection.adapter_name.to_sym == :OracleEnhanced) ? "" : "AS"
121
+ end
113
122
  end
114
123
  end