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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: afbd40867cadc58befec5d4aeda0c0f2684906b7
4
- data.tar.gz: 27f8b0415ba7f10e84f50918f8fb4a3304d5d3fa
3
+ metadata.gz: 92930bde62d7c1d8460fcefb8964da22b6edca2b
4
+ data.tar.gz: fe1eb67d373bda0ce3e4f7a404e521d7dce52bf7
5
5
  SHA512:
6
- metadata.gz: e1b47e639ab23e6eec46d1ede597da1bfc2c3a1f2a7b82d9024272695f985ebd8704b9230ab44b5507ebb1226bc098ef5386e7e267f15ca50e9d31f5d7605bab
7
- data.tar.gz: ab3224ef55cee5cac66106d80aadf8b144abf132ac1a56ce7fb4b45fc37f38ff72060c336a9ee3ca696785529c2ce872d9b4fcfbb63ded55fbd4171cc111d491
6
+ metadata.gz: e07b36bf6badfeb5d9035eddd650e821159a8963b71df737f8e42947401aecf7456a218fe34f787cad6f8ddbd341e11072fb4a23f1c96c8d45e0a8ba42290fae
7
+ data.tar.gz: f767928d73fefcc76338fe0edb444f6b282b7249dfa808e7f406f4b04593f63696ecabd23e3b421b39f2ea09f9883b9f4000a91c23ab3e24bcadfd8955a0f813
@@ -19,6 +19,9 @@ env:
19
19
  - DB=mysql
20
20
  - DB=postgresql
21
21
 
22
+ addons:
23
+ postgresql: "9.3"
24
+
22
25
  script: WITH_ADVISORY_LOCK_PREFIX=$TRAVIS_JOB_ID bundle exec rake --trace all_spec_flavors
23
26
 
24
27
  matrix:
data/Appraisals CHANGED
@@ -1,17 +1,17 @@
1
- appraise "activerecord-3.2" do
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 "activerecord-4.0" do
7
- gem "activerecord", "~> 4.0"
6
+ appraise 'activerecord-4.0' do
7
+ gem 'activerecord', '~> 4.0.0'
8
8
  end
9
9
 
10
- appraise "activerecord-4.1" do
11
- gem "activerecord", "~> 4.1"
10
+ appraise 'activerecord-4.1' do
11
+ gem 'activerecord', '~> 4.1.0'
12
12
  end
13
13
 
14
- appraise "activerecord-edge" do
15
- gem "activerecord", github: "rails/rails"
14
+ appraise 'activerecord-edge' do
15
+ gem 'activerecord', github: 'rails/rails'
16
16
  gem 'arel', github: 'rails/arel'
17
17
  end
@@ -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
- * More goodness from from [Abdelkader Boudih](https://github.com/seuros), including rspec 3 support.
20
+ * More goodness from [Abdelkader Boudih](https://github.com/seuros), including rspec 3 support.
6
21
 
7
22
  ### 4.6.2
8
23
 
9
- * Pulled in [106](https://github.com/mceachen/closure_tree/pull/106) which fixed a bug introduced
10
- in 4.6.0 which broke if the numeric ordering column wasn't named 'sort_order'. Tests have been
11
- added. Thanks for the fix, [Fission Xuiptz](https://github.com/fissionxuiptz)!
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
- * Address [issue 60](https://github.com/mceachen/closure_tree/issues/60) (use `.empty?` rather
16
- than `.nil?`—thanks for the suggestion, [Leonel Galán](https://github.com/leonelgalan),
17
- [Doug Mayer](https://github.com/doxavore) and [Samnang Chhun](https://github.com/samnang)!
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
- * Deterministically ordered trees are guaranteed to have a sort_order now.
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
- **This may be a breaking change if you're expecting sort_order to be nullable.**
40
+ Many thanks to [David Schmidt](https://github.com/inetdavid) for raising and
41
+ working on the issue!
24
42
 
25
- Many thanks to [David Schmidt](https://github.com/inetdavid) for raising and
26
- working on the issue!
43
+ * Added ```append_child``` and ```prepend_child```
27
44
 
28
- * Added ```append_child``` and ```prepend_child```
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
- Substantially more efficient than
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 and 2.1 (jRuby and Rubinius are still in development)__
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 [concurrency](#concurrency) (using [with_advisory_lock](https://github.com/mceachen/with_advisory_lock))
32
- * Support for polymorphism [STI](#sti) within the hierarchy
33
- * ```find_or_create_by_path``` for [building out hierarchies quickly and conveniently](#find_or_create_by_path)
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
- We can do all the node creation and add_child calls with one method call:
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
- You can ```find``` as well as ```find_or_create``` by "ancestry paths".
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
- Note that any other AR fields can be set with the second, optional ```attributes``` argument,
168
- and as of version 4.2.0, these attributes are added to the where clause as selection criteria.
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 = Tag.find_or_create_by_path(%w{home chuck Photos"}, {:tag_type => "File"})
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 new parent for you:
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
@@ -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 = %q(Easily and efficiently make your ActiveRecord model support hierarchies)
11
+ gem.summary = %q(Easily and efficiently make your ActiveRecord model support hierarchies)
12
12
  gem.description = gem.summary
13
- gem.license = 'MIT'
13
+ gem.license = 'MIT'
14
14
 
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'
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.9' # <- to prevent duplicate roots
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
- :base_class,
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
- :parent_column_name,
15
- :with_advisory_lock,
16
- :touch
13
+ :touch,
14
+ :with_advisory_lock
17
15
  )
18
16
 
19
17
  class_attribute :_ct
@@ -2,34 +2,34 @@ module ClosureTree
2
2
  module Finders
3
3
  extend ActiveSupport::Concern
4
4
 
5
- # Find a child node whose +ancestry_path+ minus self.ancestry_path is +path+.
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 child node whose +ancestry_path+ minus self.ancestry_path is +path+
12
- def find_or_create_by_path(path, attributes = {}, find_before_lock = true)
13
- attributes[:type] ||= self.type if _ct.subclass? && _ct.has_type?
14
- # only bother trying to find_by_path on the first call:
15
- (find_before_lock && find_by_path(path, attributes)) || begin
16
- subpath = path.is_a?(Enumerable) ? path.dup : [path]
17
- return self if subpath.empty?
18
- child_name = subpath.shift
19
- attrs = attributes.merge(_ct.name_sym => child_name)
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
- self.class.new(attrs).tap do |ea|
25
- # We know that there isn't a cycle, because we just created it, and
26
- # cycle detection is expensive when the node is deep.
27
- ea._ct_skip_cycle_detection!
28
- self.children << ea
29
- end
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 ) ? all : scoped
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 = path.is_a?(Enumerable) ? path.dup : [path]
127
- scope = where(_ct.name_sym => path.pop).readonly(false)
128
- scope = ct_scoped_attributes(scope, attributes)
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
- # MySQL doesn't support more than 61 joined tables (!!):
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
- #{connection.quote_table_name(last_joined_table)}.#{_ct.quoted_parent_column_name}
131
+ #{connection.quote_table_name(last_joined_table)}.#{_ct.quoted_parent_column_name}
137
132
  SQL
138
- scope = scope.where("#{next_joined_table}.#{_ct.name_column}" => ea)
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
- result = scope.where("#{last_joined_table}.#{_ct.parent_column_name}" => parent_id).first
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
- find_by_path(path, attributes) || begin
153
- subpath = path.dup
154
- root_name = subpath.shift
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
- attrs = attributes.merge(_ct.name_sym => root_name)
159
- root = roots.where(attrs).first || roots.create!(attrs)
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
@@ -80,6 +80,7 @@ module ClosureTree
80
80
 
81
81
  alias_method :level, :depth
82
82
 
83
+ # enumerable of ancestors, immediate parent is first, root is last.
83
84
  def ancestors
84
85
  without_self(self_and_ancestors)
85
86
  end
@@ -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("closure_tree") do
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