Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion lib/mongoid/association/accessors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,10 @@ def self.define_getter!(association)
association.inverse_class.tap do |klass|
klass.re_define_method(name) do |reload = false|
value = get_relation(name, association, nil, reload)
value = send("build_#{name}") if value.nil? && association.autobuilding? && !without_autobuild?
next value unless value.nil? && !without_autobuild?
next association.fallback if association.fallback?
next send("build_#{name}") if association.autobuilding?

value
end
end
Expand Down Expand Up @@ -341,6 +344,11 @@ def self.define_setter!(association)
association.inverse_class.tap do |klass|
klass.re_define_method("#{name}=") do |object|
without_autobuild do
if association.fallback?
klass = association.polymorphic? ? Mongoid::Document : association.relation_class
object = nil unless object.is_a?(klass)
end

if value = get_relation(name, association, object)
value = __build__(name, value, association) unless value.respond_to?(:substitute)

Expand Down
12 changes: 6 additions & 6 deletions lib/mongoid/association/depending.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,17 @@ def apply_destroy_dependencies!
private

def _dependent_delete_all!(association)
return unless relation = send(association.name)
return unless relation = without_autobuild { send(association.name) }

if relation.respond_to?(:dependents) && relation.dependents.blank?
relation.clear
else
::Array.wrap(send(association.name)).each { |rel| rel.delete }
::Array.wrap(relation).each { |rel| rel.delete }
end
end

def _dependent_destroy!(association)
return unless relation = send(association.name)
return unless relation = without_autobuild { send(association.name) }

if relation.is_a?(Enumerable)
relation.entries
Expand All @@ -117,19 +117,19 @@ def _dependent_destroy!(association)
end

def _dependent_nullify!(association)
return unless relation = send(association.name)
return unless relation = without_autobuild { send(association.name) }

relation.nullify
end

def _dependent_restrict_with_exception!(association)
if (relation = send(association.name)) && !relation.blank?
if (relation = without_autobuild { send(association.name) }) && !relation.blank?
raise Errors::DeleteRestriction.new(relation, association.name)
end
end

def _dependent_restrict_with_error!(association)
return unless (relation = send(association.name)) && !relation.blank?
return unless (relation = without_autobuild { send(association.name) }) && !relation.blank?

errors.add(association.name, :destroy_restrict_with_error_dependencies_exist)
throw(:abort, false)
Expand Down
1 change: 1 addition & 0 deletions lib/mongoid/association/embedded/embeds_one.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class EmbedsOne
as
cascade_callbacks
cyclic
fallback
store_as
]

Expand Down
7 changes: 6 additions & 1 deletion lib/mongoid/association/nested/one.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ class One
def build(parent)
return if reject?(parent, attributes)

@existing = parent.send(association.name)
@existing =
if association.fallback?
parent.send(:without_autobuild) { parent.send(association.name) }
else
parent.send(association.name)
end
if update?
delete_id(attributes)
existing.assign_attributes(attributes)
Expand Down
17 changes: 17 additions & 0 deletions lib/mongoid/association/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@ def autobuilding?
!!@options[:autobuild]
end

# Invokes the :fallback Proc and returns the null object that stands in
# for the association when its actual value is nil. The Proc is invoked
# on every access; identity is the user's responsibility (return a fresh
# instance, a shared constant, or whatever the Proc chooses).
#
# @return [ Object | nil ] The Proc's return value, or nil if not set.
def fallback
@options[:fallback]&.call
end

# Whether the association has a :fallback (null object) option set.
#
# @return [ true | false ]
def fallback?
!@options[:fallback].nil?
end

# Is the association cyclic.
#
# @return [ true | false ] Whether the association is cyclic.
Expand Down
1 change: 1 addition & 0 deletions lib/mongoid/association/referenced/belongs_to.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class BelongsTo
autosave
counter_cache
dependent
fallback
foreign_key
index
polymorphic
Expand Down
6 changes: 3 additions & 3 deletions lib/mongoid/association/referenced/counter_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def self.define_callbacks!(association)
end
end

if (record = __send__(name)) && !current.nil?
if (record = without_autobuild { __send__(name) }) && !current.nil?
record[cache_column] = (record[cache_column] || 0) + 1
record.class.with(record.persistence_context) do |_class|
_class.increment_counter(cache_column, current) if record.persisted?
Expand All @@ -120,7 +120,7 @@ def self.define_callbacks!(association)
end

klass.after_create do
if record = __send__(name)
if record = without_autobuild { __send__(name) }
record[cache_column] = (record[cache_column] || 0) + 1

if record.persisted?
Expand All @@ -133,7 +133,7 @@ def self.define_callbacks!(association)
end

klass.before_destroy do
if record = __send__(name)
if record = without_autobuild { __send__(name) }
record[cache_column] = (record[cache_column] || 0) - 1 unless record.frozen?

if record.persisted?
Expand Down
1 change: 1 addition & 0 deletions lib/mongoid/association/referenced/has_one.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class HasOne
autobuild
autosave
dependent
fallback
foreign_key
primary_key
scope
Expand Down
14 changes: 14 additions & 0 deletions lib/mongoid/association/relatable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,20 @@ def validate!
end
end

if @options.key?(:fallback)
unless @options[:fallback].respond_to?(:call)
raise ArgumentError,
"The :fallback option for association '#{name}' on " \
"#{@owner_class} must be a Proc or lambda."
end

if @options.key?(:autobuild)
raise ArgumentError,
"The :fallback option for association '#{name}' on " \
"#{@owner_class} cannot be combined with :autobuild."
end
end

[ name, :"#{name}?", :"#{name}=" ].each do |n|
raise Errors::InvalidRelation.new(@owner_class, n) if Mongoid.destructive_fields.include?(n)
end
Expand Down
16 changes: 12 additions & 4 deletions lib/mongoid/serializable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,18 @@ def serialize_relations(attributes = {}, options = {})
inclusions = options[:include]
relation_names(inclusions).each do |name|
association = relations[name.to_s]
if association && relation = send(association.name)
attributes[association.name.to_s] =
relation.serializable_hash(relation_options(inclusions, options, name))
end
next unless association

relation =
if association.fallback?
without_autobuild { send(association.name) }
else
send(association.name)
end
next unless relation

attributes[association.name.to_s] =
relation.serializable_hash(relation_options(inclusions, options, name))
end
end

Expand Down
Loading
Loading