closure_tree 4.6.3 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -0
- data/Appraisals +8 -8
- data/CHANGELOG.md +29 -14
- data/README.md +30 -20
- data/closure_tree.gemspec +8 -9
- data/lib/closure_tree/acts_as_tree.rb +3 -5
- data/lib/closure_tree/finders.rb +36 -48
- data/lib/closure_tree/model.rb +1 -0
- data/lib/closure_tree/support.rb +55 -2
- data/lib/closure_tree/support_attributes.rb +3 -8
- data/lib/closure_tree/support_flags.rb +2 -3
- data/lib/closure_tree/version.rb +1 -1
- data/spec/db/database.yml +2 -2
- data/spec/db/models.rb +7 -1
- data/spec/db/schema.rb +1 -0
- data/spec/hierarchy_maintenance_spec.rb +1 -0
- data/spec/label_spec.rb +48 -36
- data/spec/metal_spec.rb +50 -4
- data/spec/parallel_spec.rb +66 -49
- data/spec/spec_helper.rb +3 -0
- data/spec/support/database.rb +13 -11
- data/spec/tag_examples.rb +137 -102
- data/tests.sh +2 -4
- metadata +18 -20
- data/spec/support/helpers.rb +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92930bde62d7c1d8460fcefb8964da22b6edca2b
|
4
|
+
data.tar.gz: fe1eb67d373bda0ce3e4f7a404e521d7dce52bf7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e07b36bf6badfeb5d9035eddd650e821159a8963b71df737f8e42947401aecf7456a218fe34f787cad6f8ddbd341e11072fb4a23f1c96c8d45e0a8ba42290fae
|
7
|
+
data.tar.gz: f767928d73fefcc76338fe0edb444f6b282b7249dfa808e7f406f4b04593f63696ecabd23e3b421b39f2ea09f9883b9f4000a91c23ab3e24bcadfd8955a0f813
|
data/.travis.yml
CHANGED
data/Appraisals
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
appraise
|
2
|
-
gem 'activerecord', '~> 3.2'
|
1
|
+
appraise 'activerecord-3.2' do
|
2
|
+
gem 'activerecord', '~> 3.2.0'
|
3
3
|
gem 'strong_parameters'
|
4
4
|
end
|
5
5
|
|
6
|
-
appraise
|
7
|
-
gem
|
6
|
+
appraise 'activerecord-4.0' do
|
7
|
+
gem 'activerecord', '~> 4.0.0'
|
8
8
|
end
|
9
9
|
|
10
|
-
appraise
|
11
|
-
gem
|
10
|
+
appraise 'activerecord-4.1' do
|
11
|
+
gem 'activerecord', '~> 4.1.0'
|
12
12
|
end
|
13
13
|
|
14
|
-
appraise
|
15
|
-
gem
|
14
|
+
appraise 'activerecord-edge' do
|
15
|
+
gem 'activerecord', github: 'rails/rails'
|
16
16
|
gem 'arel', github: 'rails/arel'
|
17
17
|
end
|
data/CHANGELOG.md
CHANGED
@@ -1,33 +1,48 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### 5.0.0
|
4
|
+
|
5
|
+
#### Breaking API changes
|
6
|
+
|
7
|
+
* `find_by_path` and `find_or_create_by_path` now takes either an array of strings
|
8
|
+
or an array of attribute hashes, which can include the inheritance column for STI support.
|
9
|
+
* Removed the extraneous `base_class` `acts_as_tree` option—it needlessly duplicated ActiveRecord's method.
|
10
|
+
* Removed the unused `name` `acts_as_tree` option.
|
11
|
+
|
12
|
+
#### Improvements and bugfixes
|
13
|
+
|
14
|
+
* Cleaned up the inheritance support methods to delegate correctly to ActiveRecord
|
15
|
+
* Fixed a query generation error when ancestor paths exceeded 50 items.
|
16
|
+
* Documented the `.touch` option
|
17
|
+
|
3
18
|
### 4.6.3
|
4
19
|
|
5
|
-
|
20
|
+
* More goodness from [Abdelkader Boudih](https://github.com/seuros), including rspec 3 support.
|
6
21
|
|
7
22
|
### 4.6.2
|
8
23
|
|
9
|
-
|
10
|
-
|
11
|
-
|
24
|
+
* Pulled in [106](https://github.com/mceachen/closure_tree/pull/106) which fixed a bug introduced
|
25
|
+
in 4.6.0 which broke if the numeric ordering column wasn't named 'sort_order'. Tests have been
|
26
|
+
added. Thanks for the fix, [Fission Xuiptz](https://github.com/fissionxuiptz)!
|
12
27
|
|
13
28
|
### 4.6.1
|
14
29
|
|
15
|
-
|
16
|
-
|
17
|
-
|
30
|
+
* Address [issue 60](https://github.com/mceachen/closure_tree/issues/60) (use `.empty?` rather
|
31
|
+
than `.nil?`—thanks for the suggestion, [Leonel Galán](https://github.com/leonelgalan),
|
32
|
+
[Doug Mayer](https://github.com/doxavore) and [Samnang Chhun](https://github.com/samnang)!
|
18
33
|
|
19
34
|
### 4.6.0
|
20
35
|
|
21
|
-
|
36
|
+
* Deterministically ordered trees are guaranteed to have a sort_order now.
|
37
|
+
|
38
|
+
**This may be a breaking change if you're expecting sort_order to be nullable.**
|
22
39
|
|
23
|
-
|
40
|
+
Many thanks to [David Schmidt](https://github.com/inetdavid) for raising and
|
41
|
+
working on the issue!
|
24
42
|
|
25
|
-
|
26
|
-
working on the issue!
|
43
|
+
* Added ```append_child``` and ```prepend_child```
|
27
44
|
|
28
|
-
|
29
|
-
|
30
|
-
* All raw SQL is now ```strip_heredoc```'ed
|
45
|
+
* All raw SQL is now ```strip_heredoc```'ed
|
31
46
|
|
32
47
|
### 4.5.0
|
33
48
|
|
data/README.md
CHANGED
@@ -10,7 +10,7 @@ and tracking user referrals.
|
|
10
10
|
[![Code Climate](https://codeclimate.com/github/mceachen/closure_tree.png)](https://codeclimate.com/github/mceachen/closure_tree)
|
11
11
|
[![Dependency Status](https://gemnasium.com/mceachen/closure_tree.png)](https://gemnasium.com/mceachen/closure_tree)
|
12
12
|
|
13
|
-
|
13
|
+
Dramatically more performant than
|
14
14
|
[ancestry](https://github.com/stefankroes/ancestry) and
|
15
15
|
[acts_as_tree](https://github.com/amerine/acts_as_tree), and even more
|
16
16
|
awesome than [awesome_nested_set](https://github.com/collectiveidea/awesome_nested_set/),
|
@@ -25,16 +25,16 @@ closure_tree has some great features:
|
|
25
25
|
* __Best-in-class mutation performance__:
|
26
26
|
* 2 SQL INSERTs on node creation
|
27
27
|
* 3 SQL INSERT/UPDATEs on node reparenting
|
28
|
+
* __Support for [concurrency](#concurrency)__ (using [with_advisory_lock](https://github.com/mceachen/with_advisory_lock))
|
28
29
|
* __Support for Rails 3.2, 4.0, and 4.1__
|
29
|
-
* __Support for Ruby 1.9
|
30
|
+
* __Support for Ruby 1.9, 2.1, and jRuby 1.6.13
|
30
31
|
* Support for reparenting children (and all their descendants)
|
31
|
-
* Support for [
|
32
|
-
*
|
33
|
-
*
|
34
|
-
* Support for [deterministic ordering](#deterministic-ordering) of children
|
32
|
+
* Support for [single-table inheritance (STI)](#sti) within the hierarchy
|
33
|
+
* ```find_or_create_by_path``` for [building out heterogeneous hierarchies quickly and conveniently](#find_or_create_by_path)
|
34
|
+
* Support for [deterministic ordering](#deterministic-ordering)
|
35
35
|
* Support for [preordered](http://en.wikipedia.org/wiki/Tree_traversal#Pre-order) traversal of descendants
|
36
36
|
* Support for rendering trees in [DOT format](http://en.wikipedia.org/wiki/DOT_(graph_description_language)), using [Graphviz](http://www.graphviz.org/)
|
37
|
-
* Excellent [test coverage](#testing) in a variety of environments
|
37
|
+
* Excellent [test coverage](#testing) in a comprehensive variety of environments
|
38
38
|
|
39
39
|
See [Bill Karwin](http://karwin.blogspot.com/)'s excellent
|
40
40
|
[Models for hierarchical data presentation](http://www.slideshare.net/billkarwin/models-for-hierarchical-data)
|
@@ -153,30 +153,40 @@ child1.ancestry_path
|
|
153
153
|
|
154
154
|
### find_or_create_by_path
|
155
155
|
|
156
|
-
|
156
|
+
You can ```find``` as well as ```find_or_create``` by "ancestry paths".
|
157
|
+
|
158
|
+
If you provide an array of strings to these methods, they reference the `name` column in your
|
159
|
+
model, which can be overridden with the `:name_column` option provided to ```acts_as_tree```.
|
157
160
|
|
158
161
|
```ruby
|
159
162
|
child = Tag.find_or_create_by_path(["grandparent", "parent", "child"])
|
160
163
|
```
|
161
164
|
|
162
|
-
|
163
|
-
Ancestry paths may be built using any column in your model. The default
|
164
|
-
column is ```name```, which can be changed with the :name_column option
|
165
|
-
provided to ```acts_as_tree```.
|
165
|
+
As of v5.0.0, `find_or_create_by_path` can also take an array of attribute hashes:
|
166
166
|
|
167
|
-
|
168
|
-
|
167
|
+
```ruby
|
168
|
+
child = Tag.find_or_create_by_path([
|
169
|
+
{name: "Grandparent", title: "Sr."},
|
170
|
+
{name: "Parent", title: "Mrs."},
|
171
|
+
{name: "Child", title: "Jr."}
|
172
|
+
])
|
173
|
+
```
|
174
|
+
|
175
|
+
If you're using STI, The attribute hashes can contain the `sti_name` and things work as expected:
|
169
176
|
|
170
177
|
```ruby
|
171
|
-
child =
|
178
|
+
child = Label.find_or_create_by_path([
|
179
|
+
{type: 'DateLabel', name: '2014'},
|
180
|
+
{type: 'DateLabel', name: 'August'},
|
181
|
+
{type: 'DateLabel', name: '5'},
|
182
|
+
{type: 'EventLabel', name: 'Visit the Getty Center'}
|
183
|
+
])
|
172
184
|
```
|
173
|
-
This will pass the attribute hash of ```{:name => "home", :tag_type => "File"}``` to
|
174
|
-
```Tag.find_or_create_by_name``` if the root directory doesn't exist (and
|
175
|
-
```{:name => "chuck", :tag_type => "File"}``` if the second-level tag doesn't exist, and so on).
|
176
185
|
|
177
186
|
### Moving nodes around the tree
|
178
187
|
|
179
|
-
Nodes can be moved around to other parents, and closure_tree moves the node's descendancy to the
|
188
|
+
Nodes can be moved around to other parents, and closure_tree moves the node's descendancy to the
|
189
|
+
new parent for you:
|
180
190
|
|
181
191
|
```ruby
|
182
192
|
d = Tag.find_or_create_by_path %w(a b c d)
|
@@ -252,6 +262,7 @@ When you include ```acts_as_tree``` in your model, you can provide a hash to ove
|
|
252
262
|
* ```:destroy``` will destroy all descendant nodes (which runs the destroy hooks on each child node)
|
253
263
|
* ```:name_column``` used by #```find_or_create_by_path```, #```find_by_path```, and ```ancestry_path``` instance methods. This is primarily useful if the model only has one required field (like a "tag").
|
254
264
|
* ```:order``` used to set up [deterministic ordering](#deterministic-ordering)
|
265
|
+
* ```:touch``` delegates to the `belongs_to` annotation for the parent, so `touch`ing cascades to all children (the performance of this for deep trees isn't currently optimal).
|
255
266
|
|
256
267
|
## Accessing Data
|
257
268
|
|
@@ -529,7 +540,6 @@ end
|
|
529
540
|
|
530
541
|
```
|
531
542
|
|
532
|
-
|
533
543
|
## Testing
|
534
544
|
|
535
545
|
Closure tree is [tested under every valid combination](http://travis-ci.org/#!/mceachen/closure_tree) of
|
data/closure_tree.gemspec
CHANGED
@@ -8,27 +8,26 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.email = ['matthew-github@mceachen.org']
|
9
9
|
gem.homepage = 'http://mceachen.github.io/closure_tree/'
|
10
10
|
|
11
|
-
gem.summary
|
11
|
+
gem.summary = %q(Easily and efficiently make your ActiveRecord model support hierarchies)
|
12
12
|
gem.description = gem.summary
|
13
|
-
gem.license
|
13
|
+
gem.license = 'MIT'
|
14
14
|
|
15
|
-
gem.files
|
16
|
-
gem.test_files
|
17
|
-
gem.required_ruby_version
|
15
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
16
|
+
gem.test_files = gem.files.grep(%r{^spec/})
|
17
|
+
gem.required_ruby_version = '>= 1.9.3'
|
18
18
|
|
19
19
|
gem.add_runtime_dependency 'activerecord', '>= 3.2.0'
|
20
|
-
gem.add_runtime_dependency 'with_advisory_lock', '>= 0.0
|
20
|
+
gem.add_runtime_dependency 'with_advisory_lock', '>= 3.0.0'
|
21
21
|
|
22
|
-
gem.add_development_dependency 'rake'
|
23
22
|
gem.add_development_dependency 'yard'
|
24
23
|
gem.add_development_dependency 'rspec', '>= 3.0'
|
25
24
|
gem.add_development_dependency 'rspec-instafail'
|
25
|
+
# TODO: delete rspec-rails.
|
26
26
|
gem.add_development_dependency 'rspec-rails' # FIXME: for rspec-rails and rspec fixture support
|
27
27
|
gem.add_development_dependency 'uuidtools'
|
28
28
|
gem.add_development_dependency 'database_cleaner'
|
29
29
|
gem.add_development_dependency 'appraisal'
|
30
30
|
gem.add_development_dependency 'timecop'
|
31
|
-
|
31
|
+
gem.add_development_dependency 'parallel'
|
32
32
|
# gem.add_development_dependency 'ruby-prof' # <- don't need this normally.
|
33
|
-
|
34
33
|
end
|
@@ -4,16 +4,14 @@ module ClosureTree
|
|
4
4
|
module ActsAsTree
|
5
5
|
def acts_as_tree(options = {})
|
6
6
|
options.assert_valid_keys(
|
7
|
-
:
|
7
|
+
:parent_column_name,
|
8
8
|
:dependent,
|
9
9
|
:hierarchy_class_name,
|
10
10
|
:hierarchy_table_name,
|
11
|
-
:name,
|
12
11
|
:name_column,
|
13
12
|
:order,
|
14
|
-
:
|
15
|
-
:with_advisory_lock
|
16
|
-
:touch
|
13
|
+
:touch,
|
14
|
+
:with_advisory_lock
|
17
15
|
)
|
18
16
|
|
19
17
|
class_attribute :_ct
|
data/lib/closure_tree/finders.rb
CHANGED
@@ -2,34 +2,34 @@ module ClosureTree
|
|
2
2
|
module Finders
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
|
-
# Find a
|
5
|
+
# Find a descendant node whose +ancestry_path+ will be ```self.ancestry_path + path```
|
6
6
|
def find_by_path(path, attributes = {})
|
7
7
|
return self if path.empty?
|
8
8
|
self.class.find_by_path(path, attributes, id)
|
9
9
|
end
|
10
10
|
|
11
|
-
# Find a
|
12
|
-
def find_or_create_by_path(path, attributes = {}
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
11
|
+
# Find or create a descendant node whose +ancestry_path+ will be ```self.ancestry_path + path```
|
12
|
+
def find_or_create_by_path(path, attributes = {})
|
13
|
+
subpath = _ct.build_ancestry_attr_path(path, attributes)
|
14
|
+
return self if subpath.empty?
|
15
|
+
|
16
|
+
found = find_by_path(subpath, attributes)
|
17
|
+
return found if found
|
18
|
+
|
19
|
+
attrs = subpath.shift
|
20
|
+
_ct.with_advisory_lock do
|
21
|
+
# shenanigans because children.create is bound to the superclass
|
22
|
+
# (in the case of polymorphism):
|
23
|
+
child = self.children.where(attrs).first || begin
|
24
|
+
# Support STI creation by using base_class:
|
25
|
+
_ct.create(self.class, attrs).tap do |ea|
|
26
|
+
# We know that there isn't a cycle, because we just created it, and
|
27
|
+
# cycle detection is expensive when the node is deep.
|
28
|
+
ea._ct_skip_cycle_detection!
|
29
|
+
self.children << ea
|
30
30
|
end
|
31
|
-
child.find_or_create_by_path(subpath, attributes, false)
|
32
31
|
end
|
32
|
+
child.find_or_create_by_path(subpath, attributes)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -54,7 +54,7 @@ module ClosureTree
|
|
54
54
|
|
55
55
|
# Fix deprecation warning:
|
56
56
|
def _ct_all
|
57
|
-
(ActiveRecord::VERSION::MAJOR >= 4
|
57
|
+
(ActiveRecord::VERSION::MAJOR >= 4) ? all : scoped
|
58
58
|
end
|
59
59
|
|
60
60
|
def without(instance)
|
@@ -115,49 +115,37 @@ module ClosureTree
|
|
115
115
|
_ct.scope_with_order(s)
|
116
116
|
end
|
117
117
|
|
118
|
-
def ct_scoped_attributes(scope, attributes, target_table = table_name)
|
119
|
-
attributes.inject(scope) do |scope, pair|
|
120
|
-
scope.where("#{target_table}.#{pair.first}" => pair.last)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
118
|
# Find the node whose +ancestry_path+ is +path+
|
125
119
|
def find_by_path(path, attributes = {}, parent_id = nil)
|
126
|
-
path =
|
127
|
-
|
128
|
-
|
120
|
+
path = _ct.build_ancestry_attr_path(path, attributes)
|
121
|
+
if path.size > _ct.max_join_tables
|
122
|
+
return _ct.find_by_large_path(path, attributes, parent_id)
|
123
|
+
end
|
124
|
+
scope = where(path.pop)
|
129
125
|
last_joined_table = _ct.table_name
|
130
|
-
|
131
|
-
path.first(50).reverse.each_with_index do |ea, idx|
|
126
|
+
path.reverse.each_with_index do |ea, idx|
|
132
127
|
next_joined_table = "p#{idx}"
|
133
128
|
scope = scope.joins(<<-SQL.strip_heredoc)
|
134
129
|
INNER JOIN #{_ct.quoted_table_name} AS #{next_joined_table}
|
135
130
|
ON #{next_joined_table}.#{_ct.quoted_id_column_name} =
|
136
|
-
|
131
|
+
#{connection.quote_table_name(last_joined_table)}.#{_ct.quoted_parent_column_name}
|
137
132
|
SQL
|
138
|
-
scope =
|
139
|
-
scope = ct_scoped_attributes(scope, attributes, next_joined_table)
|
133
|
+
scope = _ct.scoped_attributes(scope, ea, next_joined_table)
|
140
134
|
last_joined_table = next_joined_table
|
141
135
|
end
|
142
|
-
|
143
|
-
if path.size > 50 && result
|
144
|
-
find_by_path(path[50..-1], attributes, result.primary_key)
|
145
|
-
else
|
146
|
-
result
|
147
|
-
end
|
136
|
+
scope.where("#{last_joined_table}.#{_ct.parent_column_name}" => parent_id).readonly(false).first
|
148
137
|
end
|
149
138
|
|
150
139
|
# Find or create nodes such that the +ancestry_path+ is +path+
|
151
140
|
def find_or_create_by_path(path, attributes = {})
|
152
|
-
|
153
|
-
|
154
|
-
|
141
|
+
attr_path = _ct.build_ancestry_attr_path(path, attributes)
|
142
|
+
find_by_path(attr_path) || begin
|
143
|
+
root_attrs = attr_path.shift
|
155
144
|
_ct.with_advisory_lock do
|
156
145
|
# shenanigans because find_or_create can't infer that we want the same class as this:
|
157
146
|
# Note that roots will already be constrained to this subclass (in the case of polymorphism):
|
158
|
-
|
159
|
-
root
|
160
|
-
root.find_or_create_by_path(subpath, attributes)
|
147
|
+
root = roots.where(root_attrs).first || _ct.create!(self, root_attrs)
|
148
|
+
root.find_or_create_by_path(attr_path)
|
161
149
|
end
|
162
150
|
end
|
163
151
|
end
|
data/lib/closure_tree/model.rb
CHANGED
data/lib/closure_tree/support.rb
CHANGED
@@ -2,6 +2,7 @@ require 'closure_tree/support_flags'
|
|
2
2
|
require 'closure_tree/support_attributes'
|
3
3
|
require 'closure_tree/numeric_order_support'
|
4
4
|
|
5
|
+
# This class and mixins are an effort to reduce the namespace pollution to models that act_as_tree.
|
5
6
|
module ClosureTree
|
6
7
|
class Support
|
7
8
|
include ClosureTree::SupportFlags
|
@@ -13,7 +14,6 @@ module ClosureTree
|
|
13
14
|
def initialize(model_class, options)
|
14
15
|
@model_class = model_class
|
15
16
|
@options = {
|
16
|
-
:base_class => model_class,
|
17
17
|
:parent_column_name => 'parent_id',
|
18
18
|
:dependent => :nullify, # or :destroy or :delete_all -- see the README
|
19
19
|
:name_column => 'name',
|
@@ -116,12 +116,65 @@ module ClosureTree
|
|
116
116
|
|
117
117
|
def with_advisory_lock(&block)
|
118
118
|
if options[:with_advisory_lock]
|
119
|
-
model_class.with_advisory_lock(
|
119
|
+
model_class.with_advisory_lock(advisory_lock_name) do
|
120
120
|
transaction { yield }
|
121
121
|
end
|
122
122
|
else
|
123
123
|
yield
|
124
124
|
end
|
125
125
|
end
|
126
|
+
|
127
|
+
def build_ancestry_attr_path(path, attributes)
|
128
|
+
path = path.is_a?(Array) ? path.dup : [path]
|
129
|
+
unless path.first.is_a?(Hash)
|
130
|
+
if subclass? && has_inheritance_column?
|
131
|
+
attributes = attributes.with_indifferent_access
|
132
|
+
attributes[inheritance_column] ||= self.sti_name
|
133
|
+
end
|
134
|
+
path = path.map { |ea| attributes.merge(name_column => ea) }
|
135
|
+
end
|
136
|
+
path
|
137
|
+
end
|
138
|
+
|
139
|
+
def scoped_attributes(scope, attributes, target_table = model_class.table_name)
|
140
|
+
table_prefixed_attributes = Hash[
|
141
|
+
attributes.map do |column_name, column_value|
|
142
|
+
["#{target_table}.#{column_name}", column_value]
|
143
|
+
end
|
144
|
+
]
|
145
|
+
scope.where(table_prefixed_attributes)
|
146
|
+
end
|
147
|
+
|
148
|
+
def max_join_tables
|
149
|
+
# MySQL doesn't support more than 61 joined tables (!!):
|
150
|
+
50
|
151
|
+
end
|
152
|
+
|
153
|
+
def find_by_large_path(path, attributes = {}, parent_id = nil)
|
154
|
+
next_parent_id = parent_id
|
155
|
+
child = nil
|
156
|
+
path.in_groups(max_join_tables, false).each do |subpath|
|
157
|
+
child = model_class.find_by_path(subpath, attributes, next_parent_id)
|
158
|
+
return nil if child.nil?
|
159
|
+
next_parent_id = child._ct_id
|
160
|
+
end
|
161
|
+
child
|
162
|
+
end
|
163
|
+
|
164
|
+
def creator_class(model_class, sti_class)
|
165
|
+
if sti_class.present?
|
166
|
+
base_class.send(:find_sti_class, sti_class)
|
167
|
+
else
|
168
|
+
model_class
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def create(model_class, attributes)
|
173
|
+
creator_class(model_class, attributes.with_indifferent_access[inheritance_column]).new(attributes)
|
174
|
+
end
|
175
|
+
|
176
|
+
def create!(model_class, attributes)
|
177
|
+
create(model_class, attributes).tap { |ea| ea.save! }
|
178
|
+
end
|
126
179
|
end
|
127
180
|
end
|