nobrainer 0.8.0 → 0.9.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/lib/no_brainer/config.rb +9 -16
- data/lib/no_brainer/connection.rb +32 -26
- data/lib/no_brainer/criteria/chainable/core.rb +17 -20
- data/lib/no_brainer/criteria/chainable/limit.rb +2 -2
- data/lib/no_brainer/criteria/chainable/order_by.rb +24 -7
- data/lib/no_brainer/criteria/chainable/raw.rb +11 -3
- data/lib/no_brainer/criteria/chainable/scope.rb +11 -7
- data/lib/no_brainer/criteria/chainable/where.rb +32 -15
- data/lib/no_brainer/criteria/termination/cache.rb +13 -9
- data/lib/no_brainer/criteria/termination/eager_loading.rb +12 -26
- data/lib/no_brainer/document.rb +2 -6
- data/lib/no_brainer/document/association.rb +8 -7
- data/lib/no_brainer/document/association/belongs_to.rb +27 -22
- data/lib/no_brainer/document/association/core.rb +8 -8
- data/lib/no_brainer/document/association/eager_loader.rb +57 -0
- data/lib/no_brainer/document/association/has_many.rb +49 -26
- data/lib/no_brainer/document/association/has_many_through.rb +31 -0
- data/lib/no_brainer/document/association/has_one.rb +13 -0
- data/lib/no_brainer/document/association/has_one_through.rb +10 -0
- data/lib/no_brainer/document/attributes.rb +4 -3
- data/lib/no_brainer/document/core.rb +1 -1
- data/lib/no_brainer/document/criteria.rb +2 -3
- data/lib/no_brainer/document/index.rb +3 -3
- data/lib/no_brainer/document/polymorphic.rb +1 -1
- data/lib/no_brainer/document/serialization.rb +1 -1
- data/lib/no_brainer/document/store_in.rb +5 -3
- data/lib/no_brainer/error.rb +1 -0
- data/lib/no_brainer/query_runner.rb +2 -1
- data/lib/no_brainer/query_runner/driver.rb +1 -4
- data/lib/no_brainer/query_runner/missing_index.rb +11 -0
- data/lib/no_brainer/query_runner/run_options.rb +2 -3
- data/lib/no_brainer/railtie.rb +0 -1
- data/lib/no_brainer/version.rb +1 -1
- data/lib/nobrainer.rb +5 -5
- metadata +28 -24
- data/lib/no_brainer/database.rb +0 -41
@@ -17,21 +17,25 @@ module NoBrainer::Criteria::Termination::Cache
|
|
17
17
|
msg
|
18
18
|
end
|
19
19
|
|
20
|
-
def merge!(criteria)
|
20
|
+
def merge!(criteria, options={})
|
21
21
|
super
|
22
22
|
self._with_cache = criteria._with_cache unless criteria._with_cache.nil?
|
23
|
-
self.reload
|
23
|
+
self.reload unless options[:keep_cache]
|
24
24
|
self
|
25
25
|
end
|
26
26
|
|
27
27
|
def with_cache?
|
28
|
-
@_with_cache
|
28
|
+
@_with_cache != false
|
29
29
|
end
|
30
30
|
|
31
31
|
def reload
|
32
32
|
@cache = nil
|
33
33
|
end
|
34
34
|
|
35
|
+
def cached?
|
36
|
+
!!@cache
|
37
|
+
end
|
38
|
+
|
35
39
|
def each(options={}, &block)
|
36
40
|
return super unless with_cache? && !options[:no_cache] && block
|
37
41
|
return @cache.each(&block) if @cache
|
@@ -41,7 +45,7 @@ module NoBrainer::Criteria::Termination::Cache
|
|
41
45
|
block.call(instance)
|
42
46
|
cache << instance
|
43
47
|
end
|
44
|
-
@cache = cache
|
48
|
+
@cache = cache.freeze
|
45
49
|
self
|
46
50
|
end
|
47
51
|
|
@@ -49,19 +53,19 @@ module NoBrainer::Criteria::Termination::Cache
|
|
49
53
|
@cache = cache
|
50
54
|
end
|
51
55
|
|
52
|
-
def self.
|
56
|
+
def self.use_cache_for(*methods)
|
53
57
|
methods.each do |method|
|
54
58
|
define_method(method) do |*args, &block|
|
55
|
-
|
56
|
-
super(*args, &block).tap { reload }
|
59
|
+
@cache ? @cache.__send__(method, *args, &block) : super(*args, &block)
|
57
60
|
end
|
58
61
|
end
|
59
62
|
end
|
60
63
|
|
61
|
-
def self.
|
64
|
+
def self.reload_on(*methods)
|
62
65
|
methods.each do |method|
|
63
66
|
define_method(method) do |*args, &block|
|
64
|
-
|
67
|
+
reload
|
68
|
+
super(*args, &block).tap { reload }
|
65
69
|
end
|
66
70
|
end
|
67
71
|
end
|
@@ -9,13 +9,17 @@ module NoBrainer::Criteria::Termination::EagerLoading
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def includes(*values)
|
12
|
-
|
13
|
-
chain { |criteria| criteria._includes = values }
|
12
|
+
chain(:keep_cache => true) { |criteria| criteria._includes = values }
|
14
13
|
end
|
15
14
|
|
16
|
-
def merge!(criteria)
|
15
|
+
def merge!(criteria, options={})
|
17
16
|
super
|
18
17
|
self._includes = self._includes + criteria._includes
|
18
|
+
|
19
|
+
# XXX Not pretty hack
|
20
|
+
if criteria._includes.present? && cached?
|
21
|
+
perform_eager_loading(@cache)
|
22
|
+
end
|
19
23
|
end
|
20
24
|
|
21
25
|
def each(options={}, &block)
|
@@ -23,7 +27,7 @@ module NoBrainer::Criteria::Termination::EagerLoading
|
|
23
27
|
|
24
28
|
docs = []
|
25
29
|
super(options.merge(:no_eager_loading => true)) { |doc| docs << doc }
|
26
|
-
|
30
|
+
perform_eager_loading(docs)
|
27
31
|
docs.each(&block)
|
28
32
|
self
|
29
33
|
end
|
@@ -35,30 +39,12 @@ module NoBrainer::Criteria::Termination::EagerLoading
|
|
35
39
|
end
|
36
40
|
|
37
41
|
def get_one(criteria)
|
38
|
-
super.tap { |doc|
|
42
|
+
super.tap { |doc| perform_eager_loading([doc]) }
|
39
43
|
end
|
40
44
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
association = docs.first.root_class.association_metadata[association_name.to_sym]
|
45
|
-
raise "Unknown association #{association_name}" unless association
|
46
|
-
association.eager_load(docs, criteria)
|
47
|
-
end
|
48
|
-
|
49
|
-
def eager_load(docs, includes)
|
50
|
-
case includes
|
51
|
-
when Hash then includes.each do |k,v|
|
52
|
-
if v.is_a?(NoBrainer::Criteria)
|
53
|
-
v = v.dup
|
54
|
-
nested_includes, v._includes = v._includes, []
|
55
|
-
eager_load(eager_load_association(docs, k, v), nested_includes)
|
56
|
-
else
|
57
|
-
eager_load(eager_load_association(docs, k), v)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
when Array then includes.each { |v| eager_load(docs, v) }
|
61
|
-
else eager_load_association(docs, includes)
|
45
|
+
def perform_eager_loading(docs)
|
46
|
+
if should_eager_load? && docs.present?
|
47
|
+
NoBrainer::Document::Association::EagerLoader.new.eager_load(docs, self._includes)
|
62
48
|
end
|
63
49
|
end
|
64
50
|
end
|
data/lib/no_brainer/document.rb
CHANGED
@@ -6,13 +6,9 @@ module NoBrainer::Document
|
|
6
6
|
|
7
7
|
autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Persistance, :Dirty,
|
8
8
|
:Id, :Association, :Serialization, :Criteria, :Validation,
|
9
|
-
:Polymorphic, :Index
|
9
|
+
:Polymorphic, :Index, :Timestamps
|
10
10
|
|
11
|
-
autoload :DynamicAttributes
|
12
|
-
|
13
|
-
included do
|
14
|
-
include Timestamps if NoBrainer::Config.auto_include_timestamps
|
15
|
-
end
|
11
|
+
autoload :DynamicAttributes
|
16
12
|
|
17
13
|
singleton_class.delegate :all, :to => Core
|
18
14
|
end
|
@@ -1,17 +1,16 @@
|
|
1
1
|
module NoBrainer::Document::Association
|
2
2
|
extend NoBrainer::Autoload
|
3
|
-
autoload :Core, :BelongsTo, :HasMany
|
3
|
+
autoload :Core, :BelongsTo, :HasMany, :HasManyThrough, :HasOne, :HasOneThrough, :EagerLoader
|
4
4
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
-
|
8
|
+
singleton_class.send(:attr_accessor, :association_metadata)
|
9
9
|
self.association_metadata = {}
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
@associations ||= {}
|
14
|
-
@associations[metadata] ||= metadata.new(self)
|
12
|
+
def associations
|
13
|
+
@associations ||= Hash.new { |h, metadata| h[metadata] = metadata.new(self) }
|
15
14
|
end
|
16
15
|
|
17
16
|
module ClassMethods
|
@@ -20,14 +19,16 @@ module NoBrainer::Document::Association
|
|
20
19
|
subclass.association_metadata = self.association_metadata.dup
|
21
20
|
end
|
22
21
|
|
23
|
-
[:belongs_to, :has_many].each do |association|
|
22
|
+
[:belongs_to, :has_many, :has_one].each do |association|
|
24
23
|
define_method(association) do |target, options={}|
|
25
24
|
target = target.to_sym
|
26
25
|
|
27
26
|
if r = self.association_metadata[target]
|
27
|
+
raise "Cannot change the :through option" unless r.options[:through] == options[:through]
|
28
28
|
r.options.merge!(options)
|
29
29
|
else
|
30
|
-
|
30
|
+
klass_name = (options[:through] ? "#{association}_through" : association.to_s).camelize
|
31
|
+
metadata_klass = NoBrainer::Document::Association.const_get(klass_name).const_get(:Metadata)
|
31
32
|
r = metadata_klass.new(self, target, options)
|
32
33
|
([self] + descendants).each do |klass|
|
33
34
|
klass.association_metadata[target] = r
|
@@ -2,8 +2,9 @@ class NoBrainer::Document::Association::BelongsTo
|
|
2
2
|
include NoBrainer::Document::Association::Core
|
3
3
|
|
4
4
|
class Metadata
|
5
|
-
|
5
|
+
VALID_OPTIONS = [:foreign_key, :class_name, :index]
|
6
6
|
include NoBrainer::Document::Association::Core::Metadata
|
7
|
+
extend NoBrainer::Document::Association::EagerLoader::Generic
|
7
8
|
|
8
9
|
def foreign_key
|
9
10
|
# TODO test :foreign_key
|
@@ -17,47 +18,51 @@ class NoBrainer::Document::Association::BelongsTo
|
|
17
18
|
|
18
19
|
def hook
|
19
20
|
super
|
20
|
-
options.assert_valid_keys(*VALID_BELONGS_TO_OPTIONS)
|
21
21
|
|
22
22
|
owner_klass.field foreign_key, :index => options[:index]
|
23
23
|
delegate("#{foreign_key}=", :assign_foreign_key, :call_super => true)
|
24
24
|
add_callback_for(:after_validation)
|
25
|
+
# TODO test if we are not overstepping on another foreign_key
|
25
26
|
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
target_criteria = target_criteria.merge(criteria) if criteria
|
30
|
-
docs_fks = Hash[docs.map { |doc| [doc, doc.read_attribute(foreign_key)] }]
|
31
|
-
fks = docs_fks.values.compact.uniq
|
32
|
-
fk_targets = Hash[target_criteria.where(:id.in => fks).map { |doc| [doc.id, doc] }]
|
33
|
-
docs_fks.each { |doc, fk| doc.association(self)._write(fk_targets[fk]) if fk_targets[fk] }
|
34
|
-
fk_targets.values
|
35
|
-
end
|
28
|
+
eager_load_with :owner_key => ->{ foreign_key }, :target_key => ->{ :id },
|
29
|
+
:unscoped => true
|
36
30
|
end
|
37
31
|
|
32
|
+
# Note:
|
33
|
+
# @target_container is an array to distinguish the following cases:
|
34
|
+
# * target is not loaded, but perhaps present in the db.
|
35
|
+
# * we already tried to load target, but it wasn't present in the db.
|
36
|
+
|
38
37
|
def assign_foreign_key(value)
|
39
|
-
@
|
38
|
+
@target_container = nil
|
40
39
|
end
|
41
40
|
|
42
41
|
def read
|
43
|
-
return @
|
44
|
-
if fk = instance.read_attribute(foreign_key)
|
45
|
-
@target = target_klass.find(fk)
|
46
|
-
end
|
47
|
-
end
|
42
|
+
return @target_container.first if loaded?
|
48
43
|
|
49
|
-
|
50
|
-
|
44
|
+
if fk = owner.read_attribute(foreign_key)
|
45
|
+
preload(target_klass.find(fk))
|
46
|
+
end
|
51
47
|
end
|
52
48
|
|
53
49
|
def write(target)
|
54
50
|
assert_target_type(target)
|
55
|
-
|
56
|
-
|
51
|
+
owner.write_attribute(foreign_key, target.try(:id))
|
52
|
+
preload(target)
|
53
|
+
end
|
54
|
+
|
55
|
+
def preload(target)
|
56
|
+
@target_container = [*target] # the * is for the generic eager loading code
|
57
|
+
@target_container.first
|
58
|
+
end
|
59
|
+
|
60
|
+
def loaded?
|
61
|
+
!@target_container.nil?
|
57
62
|
end
|
58
63
|
|
59
64
|
def after_validation_callback
|
60
|
-
if @
|
65
|
+
if loaded? && @target_container.first && !@target_container.first.persisted?
|
61
66
|
raise NoBrainer::Error::AssociationNotSaved.new("#{target_name} must be saved first")
|
62
67
|
end
|
63
68
|
end
|
@@ -16,8 +16,8 @@ module NoBrainer::Document::Association::Core
|
|
16
16
|
@association_klass ||= self.class.name.deconstantize.constantize
|
17
17
|
end
|
18
18
|
|
19
|
-
def new(
|
20
|
-
association_klass.new(self,
|
19
|
+
def new(owner)
|
20
|
+
association_klass.new(self, owner)
|
21
21
|
end
|
22
22
|
|
23
23
|
def delegate(method_name, target, options={})
|
@@ -25,12 +25,13 @@ module NoBrainer::Document::Association::Core
|
|
25
25
|
owner_klass.inject_in_layer :associations do
|
26
26
|
define_method(method_name) do |*args, &block|
|
27
27
|
super(*args, &block) if options[:call_super]
|
28
|
-
|
28
|
+
associations[metadata].__send__(target, *args, &block)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
33
|
def hook
|
34
|
+
options.assert_valid_keys(*self.class.const_get(:VALID_OPTIONS))
|
34
35
|
delegate("#{target_name}=", :write)
|
35
36
|
delegate("#{target_name}", :read)
|
36
37
|
end
|
@@ -39,20 +40,19 @@ module NoBrainer::Document::Association::Core
|
|
39
40
|
instance_eval <<-RUBY, __FILE__, __LINE__+1
|
40
41
|
if !@added_#{what}
|
41
42
|
metadata = self
|
42
|
-
owner_klass.#{what} {
|
43
|
+
owner_klass.#{what} { associations[metadata].#{what}_callback }
|
43
44
|
@added_#{what} = true
|
44
45
|
end
|
45
46
|
RUBY
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
49
|
-
included { attr_accessor :metadata, :
|
50
|
+
included { attr_accessor :metadata, :owner }
|
50
51
|
|
51
52
|
delegate :foreign_key, :target_name, :target_klass, :to => :metadata
|
52
53
|
|
53
|
-
def initialize(metadata,
|
54
|
-
@metadata = metadata
|
55
|
-
@instance = instance
|
54
|
+
def initialize(metadata, owner)
|
55
|
+
@metadata, @owner = metadata, owner
|
56
56
|
end
|
57
57
|
|
58
58
|
def assert_target_type(value)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class NoBrainer::Document::Association::EagerLoader
|
2
|
+
module Generic
|
3
|
+
# Used in associations to declare generic eager loading capabilities
|
4
|
+
# The association should implement loaded? and preload.
|
5
|
+
def eager_load_with(options={})
|
6
|
+
define_method(:eager_load) do |docs, additional_criteria=nil|
|
7
|
+
owner_key = instance_exec(&options[:owner_key])
|
8
|
+
target_key = instance_exec(&options[:target_key])
|
9
|
+
|
10
|
+
criteria = target_klass.all
|
11
|
+
criteria = criteria.merge(additional_criteria) if additional_criteria
|
12
|
+
criteria = criteria.unscoped if options[:unscoped]
|
13
|
+
|
14
|
+
unloaded_docs = docs.reject { |doc| doc.associations[self].loaded? }
|
15
|
+
|
16
|
+
owner_keys = unloaded_docs.map(&owner_key).compact.uniq
|
17
|
+
if owner_keys.present?
|
18
|
+
targets = criteria.where(target_key.in => owner_keys)
|
19
|
+
.map { |target| [target.read_attribute(target_key), target] }
|
20
|
+
.reduce(Hash.new { |k,v| k[v] = [] }) { |h,(k,v)| h[k] << v; h }
|
21
|
+
|
22
|
+
unloaded_docs.each do |doc|
|
23
|
+
doc_targets = targets[doc.read_attribute(owner_key)]
|
24
|
+
doc.associations[self].preload(doc_targets)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
docs.map { |doc| doc.associations[self].read }.flatten.compact.uniq
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def eager_load_association(docs, association_name, criteria=nil)
|
34
|
+
docs = docs.compact
|
35
|
+
return [] if docs.empty?
|
36
|
+
association = docs.first.root_class.association_metadata[association_name.to_sym]
|
37
|
+
raise "Unknown association #{association_name}" unless association
|
38
|
+
association.eager_load(docs, criteria)
|
39
|
+
end
|
40
|
+
|
41
|
+
def eager_load(docs, includes)
|
42
|
+
case includes
|
43
|
+
when Hash then includes.each do |k,v|
|
44
|
+
if v.is_a?(NoBrainer::Criteria)
|
45
|
+
v = v.dup
|
46
|
+
nested_includes, v._includes = v._includes, []
|
47
|
+
eager_load(eager_load_association(docs, k, v), nested_includes)
|
48
|
+
else
|
49
|
+
eager_load(eager_load_association(docs, k), v)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
when Array then includes.each { |v| eager_load(docs, v) }
|
53
|
+
else eager_load_association(docs, includes)
|
54
|
+
end
|
55
|
+
true
|
56
|
+
end
|
57
|
+
end
|
@@ -2,12 +2,13 @@ class NoBrainer::Document::Association::HasMany
|
|
2
2
|
include NoBrainer::Document::Association::Core
|
3
3
|
|
4
4
|
class Metadata
|
5
|
-
|
5
|
+
VALID_OPTIONS = [:foreign_key, :class_name, :dependent]
|
6
6
|
include NoBrainer::Document::Association::Core::Metadata
|
7
|
+
extend NoBrainer::Document::Association::EagerLoader::Generic
|
7
8
|
|
8
9
|
def foreign_key
|
9
10
|
# TODO test :foreign_key
|
10
|
-
options[:foreign_key].try(:to_sym) ||
|
11
|
+
options[:foreign_key].try(:to_sym) || owner_klass.name.foreign_key.to_sym
|
11
12
|
end
|
12
13
|
|
13
14
|
def target_klass
|
@@ -15,47 +16,69 @@ class NoBrainer::Document::Association::HasMany
|
|
15
16
|
(options[:class_name] || target_name.to_s.singularize.camelize).constantize
|
16
17
|
end
|
17
18
|
|
19
|
+
def inverses
|
20
|
+
# We can always infer the inverse association of a has_many relationship,
|
21
|
+
# because a belongs_to association cannot have a scope applied on the
|
22
|
+
# selector.
|
23
|
+
# XXX Without caching, this is going to get CPU intensive quickly, but
|
24
|
+
# caching is hard (rails console reload, etc.).
|
25
|
+
target_klass.association_metadata.values.select do |assoc|
|
26
|
+
assoc.is_a?(NoBrainer::Document::Association::BelongsTo::Metadata) and
|
27
|
+
assoc.foreign_key == self.foreign_key and
|
28
|
+
assoc.target_klass.root_class == owner_klass.root_class
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
18
32
|
def hook
|
19
33
|
super
|
20
|
-
|
21
|
-
add_callback_for(:before_destroy)
|
34
|
+
add_callback_for(:before_destroy) if options[:dependent]
|
22
35
|
end
|
23
36
|
|
24
|
-
|
25
|
-
target_criteria = target_klass.all
|
26
|
-
target_criteria = target_criteria.merge(criteria) if criteria
|
27
|
-
docs_ids = Hash[docs.map { |doc| [doc, doc.id] }]
|
28
|
-
fk_targets = target_criteria
|
29
|
-
.where(foreign_key.in => docs_ids.values)
|
30
|
-
.reduce({}) do |hash, doc|
|
31
|
-
fk = doc.read_attribute(foreign_key)
|
32
|
-
hash[fk] ||= []
|
33
|
-
hash[fk] << doc
|
34
|
-
hash
|
35
|
-
end
|
36
|
-
docs_ids.each { |doc, id| doc.association(self)._write(fk_targets[id]) if fk_targets[id] }
|
37
|
-
fk_targets.values.flatten(1)
|
38
|
-
end
|
37
|
+
eager_load_with :owner_key => ->{ :id }, :target_key => ->{ foreign_key }
|
39
38
|
end
|
40
39
|
|
41
|
-
def
|
42
|
-
@
|
40
|
+
def target_criteria
|
41
|
+
@target_criteria ||= target_klass.where(foreign_key => owner.id)
|
42
|
+
._after_instantiate(set_inverse_proc)
|
43
43
|
end
|
44
44
|
|
45
45
|
def read
|
46
|
-
|
46
|
+
target_criteria
|
47
47
|
end
|
48
48
|
|
49
49
|
def write(new_children)
|
50
|
-
raise "You can't assign
|
50
|
+
raise "You can't assign #{target_name}. " +
|
51
|
+
"Instead, you must modify delete and create #{target_klass} manually."
|
52
|
+
end
|
53
|
+
|
54
|
+
def loaded?
|
55
|
+
target_criteria.cached?
|
56
|
+
end
|
57
|
+
|
58
|
+
def preload(new_targets)
|
59
|
+
set_inverses_of(new_targets)
|
60
|
+
target_criteria._override_cache(new_targets)
|
61
|
+
end
|
62
|
+
|
63
|
+
def set_inverses_of(new_targets)
|
64
|
+
@inverses ||= metadata.inverses
|
65
|
+
return if @inverses.blank?
|
66
|
+
|
67
|
+
new_targets.each do |target|
|
68
|
+
# We don't care if target is a parent class where the inverse association
|
69
|
+
# is defined, we set the association regardless.
|
70
|
+
# The user won't be able to access it since the association accessors are
|
71
|
+
# not defined on the parent class.
|
72
|
+
@inverses.each { |inverse| target.associations[inverse].preload(self.owner) }
|
73
|
+
end
|
51
74
|
end
|
52
75
|
|
53
|
-
def
|
54
|
-
|
76
|
+
def set_inverse_proc
|
77
|
+
lambda { |target| set_inverses_of([target]) if target.is_a?(NoBrainer::Document) }
|
55
78
|
end
|
56
79
|
|
57
80
|
def before_destroy_callback
|
58
|
-
criteria =
|
81
|
+
criteria = target_criteria.unscoped.without_cache
|
59
82
|
case metadata.options[:dependent]
|
60
83
|
when nil then
|
61
84
|
when :destroy then criteria.destroy_all
|