mongoid/mongoid

View on GitHub
lib/mongoid/persistable.rb

Summary

Maintainability
A
0 mins
Test Coverage
# 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