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