closure_tree 4.6.3 → 5.0.0
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.
- 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
|
[](https://codeclimate.com/github/mceachen/closure_tree)
|
11
11
|
[](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
|