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

Sign up to get free protection for your applications and to get access to all the features.
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