lib/mongoid/persistable.rb
# encoding: utf-8
require "mongoid/persistable/creatable"
require "mongoid/persistable/deletable"
require "mongoid/persistable/destroyable"
require "mongoid/persistable/incrementable"
require "mongoid/persistable/logical"
require "mongoid/persistable/poppable"
require "mongoid/persistable/pullable"
require "mongoid/persistable/pushable"
require "mongoid/persistable/renamable"
require "mongoid/persistable/savable"
require "mongoid/persistable/settable"
require "mongoid/persistable/updatable"
require "mongoid/persistable/upsertable"
require "mongoid/persistable/unsettable"
module Mongoid
# Contains general behaviour for persistence operations.
#
# @since 2.0.0
module Persistable
extend ActiveSupport::Concern
include Creatable
include Deletable
include Destroyable
include Incrementable
include Logical
include Poppable
include Pullable
include Pushable
include Renamable
include Savable
include Settable
include Updatable
include Upsertable
include Unsettable
# The atomic operations that deal with arrays or sets in the db.
#
# @since 4.0.0
LIST_OPERATIONS = [ "$addToSet", "$push", "$pull", "$pullAll" ].freeze
# Execute operations atomically (in a single database call) for everything
# that would happen inside the block.
#
# @example Execute the operations atomically.
# document.atomically do
# document.set(name: "Tool").inc(likes: 10)
# end
#
# @return [ true, false ] If the operation succeeded.
#
# @since 4.0.0
def atomically
begin
@atomic_updates_to_execute = {}
yield(self) if block_given?
persist_atomic_operations(@atomic_updates_to_execute)
true
ensure
@atomic_updates_to_execute = nil
end
end
# Raise an error if validation failed.
#
# @example Raise the validation error.
# Person.fail_due_to_validation!(person)
#
# @param [ Document ] document The document to fail.
#
# @raise [ Errors::Validations ] The validation error.
#
# @since 4.0.0
def fail_due_to_validation!
raise Errors::Validations.new(self)
end
# Raise an error if a callback failed.
#
# @example Raise the callback error.
# Person.fail_due_to_callback!(person, :create!)
#
# @param [ Document ] document The document to fail.
# @param [ Symbol ] method The method being called.
#
# @raise [ Errors::Callback ] The callback error.
#
# @since 4.0.0
def fail_due_to_callback!(method)
raise Errors::Callback.new(self.class, method)
end
private
# Are we executing an atomically block on the current document?
#
# @api private
#
# @example Are we executing atomically?
# document.executing_atomically?
#
# @return [ true, false ] If we are current executing atomically.
#
# @since 4.0.0
def executing_atomically?
!@atomic_updates_to_execute.nil?
end
# Post process the persistence operation.
#
# @api private
#
# @example Post process the persistence operation.
# document.post_process_persist(true)
#
# @param [ Object ] result The result of the operation.
# @param [ Hash ] options The options.
#
# @return [ true ] true.
#
# @since 4.0.0
def post_process_persist(result, options = {})
post_persist unless result == false
errors.clear unless performing_validations?(options)
true
end
# Prepare an atomic persistence operation. Yields an empty hash to be sent
# to the update.
#
# @api private
#
# @example Prepare the atomic operation.
# document.prepare_atomic_operation do |coll, selector, opts|
# ...
# end
#
# @return [ Object ] The result of the operation.
#
# @since 4.0.0
def prepare_atomic_operation
operations = yield({})
persist_or_delay_atomic_operation(operations)
self
end
# Process the atomic operations - this handles the common behaviour of
# iterating through each op, getting the aliased field name, and removing
# appropriate dirty changes.
#
# @api private
#
# @example Process the atomic operations.
# document.process_atomic_operations(pulls) do |field, value|
# ...
# end
#
# @param [ Hash ] operations The atomic operations.
#
# @return [ Hash ] The operations.
#
# @since 4.0.0
def process_atomic_operations(operations)
operations.each do |field, value|
unless attribute_writable?(field)
raise Errors::ReadonlyAttribute.new(field, value)
end
normalized = database_field_name(field)
yield(normalized, value)
remove_change(normalized)
end
end
# If we are in an atomically block, add the operations to the delayed group,
# otherwise persist immediately.
#
# @api private
#
# @example Persist immediately or delay the operations.
# document.persist_or_delay_atomic_operation(ops)
#
# @param [ Hash ] operation The operation.
#
# @since 4.0.0
def persist_or_delay_atomic_operation(operation)
if executing_atomically?
operation.each do |(name, hash)|
@atomic_updates_to_execute[name] ||= {}
@atomic_updates_to_execute[name].merge!(hash)
end
else
persist_atomic_operations(operation)
end
end
# Persist the atomic operations.
#
# @api private
#
# @example Persist the atomic operations.
# persist_atomic_operations(ops)
#
# @param [ Hash ] operations The atomic operations.
#
# @since 4.0.0
def persist_atomic_operations(operations)
if persisted?
selector = atomic_selector
_root.collection.find(selector).update_one(operations)
end
end
end
end