lalala 4.0.0.dev.136 → 4.0.0.dev.141

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +0 -3
  3. data/lalala.gemspec +2 -6
  4. data/lib/lalala/version.rb +1 -1
  5. data/lib/lalala.rb +0 -1
  6. metadata +19 -53
  7. data/vendor/deps/closure_tree/.gitignore +0 -12
  8. data/vendor/deps/closure_tree/.travis.yml +0 -22
  9. data/vendor/deps/closure_tree/.yardopts +0 -3
  10. data/vendor/deps/closure_tree/Gemfile +0 -2
  11. data/vendor/deps/closure_tree/MIT-LICENSE +0 -19
  12. data/vendor/deps/closure_tree/README.md +0 -641
  13. data/vendor/deps/closure_tree/Rakefile +0 -26
  14. data/vendor/deps/closure_tree/ci/Gemfile.rails-3.0.x +0 -5
  15. data/vendor/deps/closure_tree/ci/Gemfile.rails-3.1.x +0 -4
  16. data/vendor/deps/closure_tree/ci/Gemfile.rails-3.2.x +0 -4
  17. data/vendor/deps/closure_tree/closure_tree.gemspec +0 -31
  18. data/vendor/deps/closure_tree/lib/closure_tree/acts_as_tree.rb +0 -55
  19. data/vendor/deps/closure_tree/lib/closure_tree/columns.rb +0 -123
  20. data/vendor/deps/closure_tree/lib/closure_tree/deterministic_ordering.rb +0 -49
  21. data/vendor/deps/closure_tree/lib/closure_tree/model.rb +0 -386
  22. data/vendor/deps/closure_tree/lib/closure_tree/numeric_deterministic_ordering.rb +0 -93
  23. data/vendor/deps/closure_tree/lib/closure_tree/version.rb +0 -3
  24. data/vendor/deps/closure_tree/lib/closure_tree/with_advisory_lock.rb +0 -18
  25. data/vendor/deps/closure_tree/lib/closure_tree.rb +0 -8
  26. data/vendor/deps/closure_tree/spec/cuisine_type_spec.rb +0 -30
  27. data/vendor/deps/closure_tree/spec/db/database.yml +0 -19
  28. data/vendor/deps/closure_tree/spec/db/schema.rb +0 -109
  29. data/vendor/deps/closure_tree/spec/fixtures/labels.yml +0 -55
  30. data/vendor/deps/closure_tree/spec/fixtures/tags.yml +0 -98
  31. data/vendor/deps/closure_tree/spec/hash_tree_spec.rb +0 -91
  32. data/vendor/deps/closure_tree/spec/label_spec.rb +0 -356
  33. data/vendor/deps/closure_tree/spec/namespace_type_spec.rb +0 -13
  34. data/vendor/deps/closure_tree/spec/parallel_prepend_sibling_spec.rb +0 -45
  35. data/vendor/deps/closure_tree/spec/parallel_spec.rb +0 -59
  36. data/vendor/deps/closure_tree/spec/spec_helper.rb +0 -57
  37. data/vendor/deps/closure_tree/spec/support/models.rb +0 -74
  38. data/vendor/deps/closure_tree/spec/tag_spec.rb +0 -469
  39. data/vendor/deps/closure_tree/spec/user_spec.rb +0 -136
  40. data/vendor/deps/closure_tree/tests.sh +0 -19
@@ -1,31 +0,0 @@
1
- $:.push File.expand_path("../lib", __FILE__)
2
- require "closure_tree/version"
3
-
4
- Gem::Specification.new do |gem|
5
- gem.name = "closure_tree"
6
- gem.version = ::ClosureTree::VERSION
7
- gem.authors = ["Matthew McEachen"]
8
- gem.email = ["matthew-github@mceachen.org"]
9
- gem.homepage = "http://matthew.mceachen.us/closure_tree"
10
-
11
- gem.summary = %q{Easily and efficiently make your ActiveRecord model support hierarchies}
12
- gem.description = %q{Easily and efficiently make your ActiveRecord model support hierarchies}
13
-
14
- gem.files = Dir["lib/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"]
15
- gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
-
17
- gem.add_runtime_dependency 'activerecord', '>= 3.0.0'
18
- gem.add_runtime_dependency 'with_advisory_lock', '>= 0.0.6' # <- to prevent duplicate roots
19
-
20
- gem.add_development_dependency 'rake'
21
- gem.add_development_dependency 'yard'
22
- gem.add_development_dependency 'rspec'
23
- gem.add_development_dependency 'rails' # FIXME: just for rspec fixture support (!!)
24
- gem.add_development_dependency 'rspec-rails' # FIXME: just for rspec fixture support (!!)
25
- gem.add_development_dependency 'mysql2'
26
- gem.add_development_dependency 'pg'
27
- gem.add_development_dependency 'sqlite3'
28
- gem.add_development_dependency 'uuidtools'
29
- gem.add_development_dependency 'strong_parameters'
30
- # TODO: gem 'activerecord-jdbcsqlite3-adapter', :platform => :jruby
31
- end
@@ -1,55 +0,0 @@
1
- require 'closure_tree/columns'
2
- require 'closure_tree/deterministic_ordering'
3
- require 'closure_tree/model'
4
- require 'closure_tree/numeric_deterministic_ordering'
5
- require 'closure_tree/with_advisory_lock'
6
-
7
- module ClosureTree
8
- module ActsAsTree
9
- def acts_as_tree(options = {})
10
-
11
- class_attribute :closure_tree_options
12
-
13
- self.closure_tree_options = {
14
- :ct_base_class => self,
15
- :parent_column_name => 'parent_id',
16
- :dependent => :nullify, # or :destroy or :delete_all -- see the README
17
- :name_column => 'name',
18
- :with_advisory_lock => true
19
- }.merge(options)
20
-
21
- raise IllegalArgumentException, "name_column can't be 'path'" if closure_tree_options[:name_column] == 'path'
22
-
23
- include ClosureTree::Columns
24
- extend ClosureTree::Columns
25
-
26
- include ClosureTree::WithAdvisoryLock
27
- extend ClosureTree::WithAdvisoryLock
28
-
29
- # Auto-inject the hierarchy table
30
- # See https://github.com/patshaughnessy/class_factory/blob/master/lib/class_factory/class_factory.rb
31
- class_attribute :hierarchy_class
32
- self.hierarchy_class = ct_class.parent.const_set short_hierarchy_class_name, Class.new(ActiveRecord::Base)
33
-
34
- self.hierarchy_class.class_eval <<-RUBY
35
- belongs_to :ancestor, :class_name => "#{ct_class.to_s}"
36
- belongs_to :descendant, :class_name => "#{ct_class.to_s}"
37
- attr_accessible :ancestor, :descendant, :generations
38
- def ==(comparison_object)
39
- comparison_object.instance_of?(self.class) &&
40
- self.attributes == comparison_object.attributes
41
- end
42
- alias :eql? :==
43
- RUBY
44
-
45
- self.hierarchy_class.table_name = hierarchy_table_name
46
-
47
- include ClosureTree::Model
48
- unless order_option.nil?
49
- include ClosureTree::DeterministicOrdering
50
- include ClosureTree::DeterministicNumericOrdering if order_is_numeric
51
- end
52
- end
53
-
54
- end
55
- end
@@ -1,123 +0,0 @@
1
- module ClosureTree
2
-
3
- # Mixed into both classes and instances to provide easy access to the column names
4
- module Columns
5
-
6
- def parent_column_name
7
- closure_tree_options[:parent_column_name]
8
- end
9
-
10
- def parent_column_sym
11
- parent_column_name.to_sym
12
- end
13
-
14
- def has_name?
15
- ct_class.new.attributes.include? closure_tree_options[:name_column]
16
- end
17
-
18
- def name_column
19
- closure_tree_options[:name_column]
20
- end
21
-
22
- def name_sym
23
- name_column.to_sym
24
- end
25
-
26
- def hierarchy_table_name
27
- # We need to use the table_name, not something like ct_class.to_s.demodulize + "_hierarchies",
28
- # because they may have overridden the table name, which is what we want to be consistent with
29
- # in order for the schema to make sense.
30
- tablename = closure_tree_options[:hierarchy_table_name] ||
31
- remove_prefix_and_suffix(ct_table_name).singularize + "_hierarchies"
32
-
33
- ActiveRecord::Base.table_name_prefix + tablename + ActiveRecord::Base.table_name_suffix
34
- end
35
-
36
- def hierarchy_class_name
37
- closure_tree_options[:hierarchy_class_name] || ct_class.to_s + "Hierarchy"
38
- end
39
-
40
-
41
- #
42
- # Returns the constant name of the hierarchy_class
43
- #
44
- # @return [String] the constant name
45
- #
46
- # @example
47
- # Namespace::Model.hierarchy_class_name # => "Namespace::ModelHierarchy"
48
- # Namespace::Model.short_hierarchy_class_name # => "ModelHierarchy"
49
- def short_hierarchy_class_name
50
- hierarchy_class_name.split('::').last
51
- end
52
-
53
- def quoted_hierarchy_table_name
54
- connection.quote_table_name hierarchy_table_name
55
- end
56
-
57
- def quoted_parent_column_name
58
- connection.quote_column_name parent_column_name
59
- end
60
-
61
- def quoted_name_column
62
- connection.quote_column_name name_column
63
- end
64
-
65
- def ct_quote(field)
66
- connection.quote(field)
67
- end
68
-
69
- def order_option
70
- closure_tree_options[:order]
71
- end
72
-
73
- def with_order_option(options)
74
- order_option ? options.merge(:order => order_option) : options
75
- end
76
-
77
- def append_order(order_by)
78
- order_option ? "#{order_by}, #{order_option}" : order_by
79
- end
80
-
81
- def order_is_numeric
82
- # The table might not exist yet (in the case of ActiveRecord::Observer use, see issue 32)
83
- return false if order_option.nil? || !self.table_exists?
84
- c = ct_class.columns_hash[order_option]
85
- c && c.type == :integer
86
- end
87
-
88
- def ct_class
89
- (self.is_a?(Class) ? self : self.class)
90
- end
91
-
92
- # This is the "topmost" class. This will only potentially not be ct_class if you are using STI.
93
- def ct_base_class
94
- ct_class.closure_tree_options[:ct_base_class]
95
- end
96
-
97
- def ct_subclass?
98
- ct_class != ct_class.base_class
99
- end
100
-
101
- def ct_attribute_names
102
- @ct_attr_names ||= ct_class.new.attributes.keys - ct_class.protected_attributes.to_a
103
- end
104
-
105
- def ct_has_type?
106
- ct_attribute_names.include? 'type'
107
- end
108
-
109
- def ct_table_name
110
- ct_class.table_name
111
- end
112
-
113
- def quoted_table_name
114
- connection.quote_table_name ct_table_name
115
- end
116
-
117
- def remove_prefix_and_suffix(table_name)
118
- prefix = Regexp.escape(ActiveRecord::Base.table_name_prefix)
119
- suffix = Regexp.escape(ActiveRecord::Base.table_name_suffix)
120
- table_name.gsub(/^#{prefix}(.+)#{suffix}$/, "\\1")
121
- end
122
- end
123
- end
@@ -1,49 +0,0 @@
1
- module ClosureTree
2
- module DeterministicOrdering
3
- extend ActiveSupport::Concern
4
-
5
- module ClassAndInstanceMethods
6
- def order_column
7
- o = order_option
8
- o.split(' ', 2).first if o
9
- end
10
-
11
- def require_order_column
12
- raise ":order value, '#{order_option}', isn't a column" if order_column.nil?
13
- end
14
-
15
- def order_column_sym
16
- require_order_column
17
- order_column.to_sym
18
- end
19
-
20
- def quoted_order_column(include_table_name = true)
21
- require_order_column
22
- prefix = include_table_name ? "#{quoted_table_name}." : ""
23
- "#{prefix}#{connection.quote_column_name(order_column)}"
24
- end
25
- end
26
-
27
- include ClassAndInstanceMethods
28
-
29
- module ClassMethods
30
- include ClassAndInstanceMethods
31
- end
32
-
33
- def order_value
34
- read_attribute(order_column_sym)
35
- end
36
-
37
- def order_value=(new_order_value)
38
- write_attribute(order_column_sym, new_order_value)
39
- end
40
-
41
- def siblings_before
42
- siblings.where(["#{quoted_order_column} < ?", order_value])
43
- end
44
-
45
- def siblings_after
46
- siblings.where(["#{quoted_order_column} > ?", order_value])
47
- end
48
- end
49
- end
@@ -1,386 +0,0 @@
1
- require 'active_support/concern'
2
-
3
- module ClosureTree
4
- module Model
5
- extend ActiveSupport::Concern
6
-
7
- included do
8
- validate :ct_validate
9
- before_save :ct_before_save
10
- after_save :ct_after_save
11
- before_destroy :ct_before_destroy
12
-
13
- belongs_to :parent,
14
- :class_name => ct_class.to_s,
15
- :foreign_key => parent_column_name
16
-
17
- unless defined?(ActiveModel::ForbiddenAttributesProtection) && ancestors.include?(ActiveModel::ForbiddenAttributesProtection)
18
- attr_accessible :parent
19
- end
20
-
21
- has_many :children, with_order_option(
22
- :class_name => ct_class.to_s,
23
- :foreign_key => parent_column_name,
24
- :dependent => closure_tree_options[:dependent]
25
- )
26
-
27
- has_many :ancestor_hierarchies,
28
- :class_name => hierarchy_class_name,
29
- :foreign_key => "descendant_id",
30
- :order => "#{quoted_hierarchy_table_name}.generations asc"
31
-
32
- has_many :self_and_ancestors,
33
- :through => :ancestor_hierarchies,
34
- :source => :ancestor,
35
- :order => "#{quoted_hierarchy_table_name}.generations asc"
36
-
37
- has_many :descendant_hierarchies,
38
- :class_name => hierarchy_class_name,
39
- :foreign_key => "ancestor_id",
40
- :order => "#{quoted_hierarchy_table_name}.generations asc"
41
- # TODO: FIXME: this collection currently ignores sort_order
42
- # (because the quoted_table_named would need to be joined in to get to the order column)
43
-
44
- has_many :self_and_descendants,
45
- :through => :descendant_hierarchies,
46
- :source => :descendant,
47
- :order => append_order("#{quoted_hierarchy_table_name}.generations asc")
48
- end
49
-
50
- # Returns true if this node has no parents.
51
- def root?
52
- ct_parent_id.nil?
53
- end
54
-
55
- # Returns true if this node has a parent, and is not a root.
56
- def child?
57
- !parent.nil?
58
- end
59
-
60
- # Returns true if this node has no children.
61
- def leaf?
62
- children.empty?
63
- end
64
-
65
- # Returns the farthest ancestor, or self if +root?+
66
- def root
67
- self_and_ancestors.where(parent_column_name.to_sym => nil).first
68
- end
69
-
70
- def leaves
71
- self_and_descendants.leaves
72
- end
73
-
74
- def depth
75
- ancestors.size
76
- end
77
-
78
- alias :level :depth
79
-
80
- def ancestors
81
- without_self(self_and_ancestors)
82
- end
83
-
84
- def ancestor_ids
85
- ids_from(ancestors)
86
- end
87
-
88
- # Returns an array, root first, of self_and_ancestors' values of the +to_s_column+, which defaults
89
- # to the +name_column+.
90
- # (so child.ancestry_path == +%w{grandparent parent child}+
91
- def ancestry_path(to_s_column = name_column)
92
- self_and_ancestors.reverse.collect { |n| n.send to_s_column.to_sym }
93
- end
94
-
95
- def child_ids
96
- ids_from(children)
97
- end
98
-
99
- def descendants
100
- without_self(self_and_descendants)
101
- end
102
-
103
- def descendant_ids
104
- ids_from(descendants)
105
- end
106
-
107
- def self_and_siblings
108
- s = ct_base_class.where(parent_column_sym => parent)
109
- order_option.present? ? s.order(quoted_order_column) : s
110
- end
111
-
112
- def siblings
113
- without_self(self_and_siblings)
114
- end
115
-
116
- def sibling_ids
117
- ids_from(siblings)
118
- end
119
-
120
- # Alias for appending to the children collection.
121
- # You can also add directly to the children collection, if you'd prefer.
122
- def add_child(child_node)
123
- children << child_node
124
- child_node
125
- end
126
-
127
- # Find a child node whose +ancestry_path+ minus self.ancestry_path is +path+.
128
- def find_by_path(path)
129
- return self if path.empty?
130
- parent_constraint = "#{quoted_parent_column_name} = #{ct_quote(id)}"
131
- ct_class.ct_scoped_to_path(path, parent_constraint).first
132
- end
133
-
134
- # Find a child node whose +ancestry_path+ minus self.ancestry_path is +path+
135
- def find_or_create_by_path(path, attributes = {}, find_before_lock = true)
136
- (find_before_lock && find_by_path(path)) || begin
137
- ct_with_advisory_lock do
138
- subpath = path.is_a?(Enumerable) ? path.dup : [path]
139
- child_name = subpath.shift
140
- return self unless child_name
141
- child = transaction do
142
- attrs = {name_sym => child_name}
143
- attrs[:type] = self.type if ct_subclass? && ct_has_type?
144
- self.children.where(attrs).first || begin
145
- child = self.class.new(attributes.merge(attrs))
146
- self.children << child
147
- child
148
- end
149
- end
150
- child.find_or_create_by_path(subpath, attributes, false)
151
- end
152
- end
153
- end
154
-
155
- def find_all_by_generation(generation_level)
156
- s = ct_base_class.joins(<<-SQL)
157
- INNER JOIN (
158
- SELECT descendant_id
159
- FROM #{quoted_hierarchy_table_name}
160
- WHERE ancestor_id = #{ct_quote(self.id)}
161
- GROUP BY 1
162
- HAVING MAX(#{quoted_hierarchy_table_name}.generations) = #{generation_level.to_i}
163
- ) AS descendants ON (#{quoted_table_name}.#{ct_base_class.primary_key} = descendants.descendant_id)
164
- SQL
165
- order_option ? s.order(order_option) : s
166
- end
167
-
168
- def hash_tree_scope(limit_depth = nil)
169
- scope = self_and_descendants
170
- if limit_depth
171
- scope.where("#{quoted_hierarchy_table_name}.generations <= #{limit_depth - 1}")
172
- else
173
- scope
174
- end
175
- end
176
-
177
- def hash_tree(options = {})
178
- self.class.build_hash_tree(hash_tree_scope(options[:limit_depth]))
179
- end
180
-
181
- def ct_parent_id
182
- read_attribute(parent_column_sym)
183
- end
184
-
185
- def ct_validate
186
- if changes[parent_column_name] &&
187
- parent.present? &&
188
- parent.self_and_ancestors.include?(self)
189
- errors.add(parent_column_sym, "You cannot add an ancestor as a descendant")
190
- end
191
- end
192
-
193
- def ct_before_save
194
- @was_new_record = new_record?
195
- true # don't cancel the save
196
- end
197
-
198
- def ct_after_save
199
- rebuild! if changes[parent_column_name] || @was_new_record
200
- @was_new_record = false # we aren't new anymore.
201
- true # don't cancel anything.
202
- end
203
-
204
- def rebuild!
205
- ct_with_advisory_lock do
206
- delete_hierarchy_references unless @was_new_record
207
- hierarchy_class.create!(:ancestor => self, :descendant => self, :generations => 0)
208
- unless root?
209
- sql = <<-SQL
210
- INSERT INTO #{quoted_hierarchy_table_name}
211
- (ancestor_id, descendant_id, generations)
212
- SELECT x.ancestor_id, #{ct_quote(id)}, x.generations + 1
213
- FROM #{quoted_hierarchy_table_name} x
214
- WHERE x.descendant_id = #{ct_quote(self.ct_parent_id)}
215
- SQL
216
- connection.execute sql.strip
217
- end
218
- children.each { |c| c.rebuild! }
219
- end
220
- end
221
-
222
- def ct_before_destroy
223
- delete_hierarchy_references
224
- if closure_tree_options[:dependent] == :nullify
225
- children.each { |c| c.rebuild! }
226
- end
227
- end
228
-
229
- def delete_hierarchy_references
230
- # The crazy double-wrapped sub-subselect works around MySQL's limitation of subselects on the same table that is being mutated.
231
- # It shouldn't affect performance of postgresql.
232
- # See http://dev.mysql.com/doc/refman/5.0/en/subquery-errors.html
233
- # Also: PostgreSQL doesn't support INNER JOIN on DELETE, so we can't use that.
234
- connection.execute <<-SQL
235
- DELETE FROM #{quoted_hierarchy_table_name}
236
- WHERE descendant_id IN (
237
- SELECT DISTINCT descendant_id
238
- FROM ( SELECT descendant_id
239
- FROM #{quoted_hierarchy_table_name}
240
- WHERE ancestor_id = #{ct_quote(id)}
241
- ) AS x )
242
- OR descendant_id = #{ct_quote(id)}
243
- SQL
244
- end
245
-
246
- def without_self(scope)
247
- scope.where(["#{quoted_table_name}.#{ct_base_class.primary_key} != ?", self])
248
- end
249
-
250
- def ids_from(scope)
251
- if scope.respond_to? :pluck
252
- scope.pluck(:id)
253
- else
254
- scope.select(:id).collect(&:id)
255
- end
256
- end
257
-
258
- # TODO: _parent_id will be removed in the next major version
259
- alias :_parent_id :ct_parent_id
260
-
261
- module ClassMethods
262
- def roots
263
- where(parent_column_name => nil)
264
- end
265
-
266
- # Returns an arbitrary node that has no parents.
267
- def root
268
- roots.first
269
- end
270
-
271
- # There is no default depth limit. This might be crazy-big, depending
272
- # on your tree shape. Hash huge trees at your own peril!
273
- def hash_tree(options = {})
274
- build_hash_tree(hash_tree_scope(options[:limit_depth]))
275
- end
276
-
277
- def leaves
278
- s = joins(<<-SQL)
279
- INNER JOIN (
280
- SELECT ancestor_id
281
- FROM #{quoted_hierarchy_table_name}
282
- GROUP BY 1
283
- HAVING MAX(#{quoted_hierarchy_table_name}.generations) = 0
284
- ) AS leaves ON (#{quoted_table_name}.#{primary_key} = leaves.ancestor_id)
285
- SQL
286
- order_option ? s.order(order_option) : s
287
- end
288
-
289
- # Rebuilds the hierarchy table based on the parent_id column in the database.
290
- # Note that the hierarchy table will be truncated.
291
- def rebuild!
292
- ct_with_advisory_lock do
293
- hierarchy_class.delete_all # not destroy_all -- we just want a simple truncate.
294
- roots.each { |n| n.send(:rebuild!) } # roots just uses the parent_id column, so this is safe.
295
- end
296
- nil
297
- end
298
-
299
- def find_all_by_generation(generation_level)
300
- s = joins(<<-SQL)
301
- INNER JOIN (
302
- SELECT #{primary_key} as root_id
303
- FROM #{quoted_table_name}
304
- WHERE #{quoted_parent_column_name} IS NULL
305
- ) AS roots ON (1 = 1)
306
- INNER JOIN (
307
- SELECT ancestor_id, descendant_id
308
- FROM #{quoted_hierarchy_table_name}
309
- GROUP BY 1, 2
310
- HAVING MAX(generations) = #{generation_level.to_i}
311
- ) AS descendants ON (
312
- #{quoted_table_name}.#{primary_key} = descendants.descendant_id
313
- AND roots.root_id = descendants.ancestor_id
314
- )
315
- SQL
316
- order_option ? s.order(order_option) : s
317
- end
318
-
319
- # Find the node whose +ancestry_path+ is +path+
320
- def find_by_path(path)
321
- parent_constraint = "#{quoted_parent_column_name} IS NULL"
322
- ct_scoped_to_path(path, parent_constraint).first
323
- end
324
-
325
- def ct_scoped_to_path(path, parent_constraint)
326
- path = path.is_a?(Enumerable) ? path.dup : [path]
327
- scope = scoped.where(name_sym => path.last).readonly(false)
328
- path[0..-2].reverse.each_with_index do |ea, idx|
329
- subtable = idx == 0 ? quoted_table_name : "p#{idx - 1}"
330
- scope = scope.joins(<<-SQL)
331
- INNER JOIN #{quoted_table_name} AS p#{idx} ON p#{idx}.id = #{subtable}.#{parent_column_name}
332
- SQL
333
- scope = scope.where("p#{idx}.#{quoted_name_column} = #{ct_quote(ea)}")
334
- end
335
- root_table_name = path.size > 1 ? "p#{path.size - 2}" : quoted_table_name
336
- scope.where("#{root_table_name}.#{parent_constraint}")
337
- end
338
-
339
- # Find or create nodes such that the +ancestry_path+ is +path+
340
- def find_or_create_by_path(path, attributes = {})
341
- find_by_path(path) || begin
342
- subpath = path.dup
343
- root_name = subpath.shift
344
- ct_with_advisory_lock do
345
- # shenanigans because find_or_create can't infer we want the same class as this:
346
- # Note that roots will already be constrained to this subclass (in the case of polymorphism):
347
- root = roots.where(name_sym => root_name).first
348
- root ||= create!(attributes.merge(name_sym => root_name))
349
- root.find_or_create_by_path(subpath, attributes)
350
- end
351
- end
352
- end
353
-
354
- def hash_tree_scope(limit_depth = nil)
355
- # Deepest generation, within limit, for each descendant
356
- # NOTE: Postgres requires HAVING clauses to always contains aggregate functions (!!)
357
- generation_depth = <<-SQL
358
- INNER JOIN (
359
- SELECT descendant_id, MAX(generations) as depth
360
- FROM #{quoted_hierarchy_table_name}
361
- GROUP BY descendant_id
362
- #{limit_depth ? "HAVING MAX(generations) <= #{limit_depth - 1}" : ""}
363
- ) AS generation_depth
364
- ON #{quoted_table_name}.#{primary_key} = generation_depth.descendant_id
365
- SQL
366
- scoped.joins(generation_depth).order(append_order("generation_depth.depth"))
367
- end
368
-
369
- # Builds nested hash structure using the scope returned from the passed in scope
370
- def build_hash_tree(tree_scope)
371
- tree = ActiveSupport::OrderedHash.new
372
- id_to_hash = {}
373
-
374
- tree_scope.each do |ea|
375
- h = id_to_hash[ea.id] = ActiveSupport::OrderedHash.new
376
- if ea.root? || tree.empty? # We're at the top of the tree.
377
- tree[ea] = h
378
- else
379
- id_to_hash[ea.ct_parent_id][ea] = h
380
- end
381
- end
382
- tree
383
- end
384
- end
385
- end
386
- end