123456789_123456789_123456789_123456789_123456789_

Class: Mongoid::PersistenceContext

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, Forwardable
Inherits: Object
Defined in: lib/mongoid/persistence_context.rb

Overview

::Object encapsulating logic for setting/getting a collection and database name and a client with particular options to use when persisting models.

Constant Summary

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(object, opts = {}) ⇒ PersistenceContext

Initialize the persistence context object.

Examples:

Create a new persistence context.

PersistenceContext.new(model, collection: 'other')

Parameters:

  • object (Object) —

    The class or model instance for which a persistence context should be created.

  • opts (Hash) (defaults to: {}) —

    The persistence context options.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 43

def initialize(object, opts = {})
  @object = object
  set_options!(opts)
end

Class Method Details

.clear(object, cluster = nil, original_context = nil)

Clear the persistence context for a particular class or model instance.

Examples:

Clear the persistence context for a class or model instance.

PersistenceContext.clear(model)

Parameters:

  • object (Class | Object) —

    The class or model instance.

  • cluster (Mongo::Cluster) (defaults to: nil) —

    The original cluster before this context was used.

  • original_context (PersistenceContext) (defaults to: nil) —

    The original persistence context that was set before this context was used.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 301

def clear(object, cluster = nil, original_context = nil)
  if (context = get(object)) && !(cluster.nil? || context.cluster.equal?(cluster)) && !context.reusable_client?
    context.client.close
  end
ensure
  store_context(object, original_context)
end

.context_store (private)

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 316

def context_store
  Threaded.get(PERSISTENCE_CONTEXT_KEY) { {} }
end

.get(object) ⇒ PersistenceContext

Get the persistence context for a particular class or model instance.

Examples:

Get the persistence context for a class or model instance.

PersistenceContext.get(model)

Parameters:

  • object (Object) —

    The class or model instance.

Returns:

  • (PersistenceContext) —

    The persistence context for the object.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 288

def get(object)
  get_context(object)
end

.get_context(object) ⇒ PersistenceContext | nil (private)

Get the persistence context for a given object from the thread local storage.

Parameters:

Returns:

  • (PersistenceContext | nil) —

    The persistence context for the object if previously stored, otherwise nil.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 329

def get_context(object)
  context_store[object.object_id]
end

.set(object, options_or_context) ⇒ PersistenceContext

::Set the persistence context for a particular class or model instance.

If there already is a persistence context set, options in the existing context are combined with options given to the set call.

Examples:

::Set the persistence context for a class or model instance.

PersistenceContext.set(model)

Parameters:

  • object (Object) —

    The class or model instance.

  • options_or_context (Hash | PersistenceContext) —

    The persistence options or a persistence context object.

Returns:

  • (PersistenceContext) —

    The persistence context for the object.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 267

def set(object, options_or_context)
  existing_context = get_context(object)
  existing_options = if existing_context
                       existing_context.options
                     else
                       {}
                     end
  options_or_context = options_or_context.options if options_or_context.is_a?(PersistenceContext)
  new_options = existing_options.merge(options_or_context)
  context = PersistenceContext.new(object, new_options)
  store_context(object, context)
end

.store_context(object, context) (private)

Store persistence context for a given object in the thread local storage.

Parameters:

  • object (Object) —

    ::Object to store the persistence context for.

  • context (PersistenceContext) —

    Context to store

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 340

def store_context(object, context)
  if context.nil?
    context_store.delete(object.object_id)
  else
    context_store[object.object_id] = context
  end
end

Instance Attribute Details

#options ⇒ Hash (readonly)

The options defining this persistence context.

Returns:

  • (Hash) —

    The persistence context options.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 18

attr_reader :options

#reusable_client? ⇒ true | false (readonly)

Whether the client of the context can be reused later, and therefore should not be closed.

If the persistence context is requested with :client option only, it means that the context should use a client configured in mongoid.yml. Such clients should not be closed when the context is cleared since they will be reused later.

Returns:

  • (true | false) —

    True if client can be reused, otherwise false.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 172

def reusable_client?
  @options.keys == [ :client ]
end

Instance Method Details

#==(other) ⇒ true | false

Determine if this persistence context is equal to another.

Examples:

Compare two persistence contexts.

context == other_context

Parameters:

  • other (Object) —

    The object to be compared with this one.

Returns:

  • (true | false) —

    Whether the two persistence contexts are equal.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 155

def ==(other)
  return false unless other.is_a?(PersistenceContext)

  options == other.options
end

#__evaluate__(name) (private)

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 198

def __evaluate__(name)
  return nil unless name

  name.respond_to?(:call) ? name.call.to_sym : name.to_sym
end

#client ⇒ Mongo::Client

Get the client for this persistence context.

Examples:

Get the client for this persistence context.

context.client

Returns:

  • (Mongo::Client) —

    The client for this persistence context.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 116

def client
  @client ||= begin
    client = Clients.with_name(client_name)
    options = client_options

    if database_name_option
      client = client.use(database_name)
      options = options.except(:database, 'database')
    end

    client = client.with(options) unless options.empty?

    verify_encryption_schema!(client)

    client
  end
end

#client_name ⇒ Symbol

Get the client name for this persistence context.

Examples:

Get the client name for this persistence context.

context.client_name

Returns:

  • (Symbol) —

    The client name for this persistence context.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 141

def client_name
  @client_name ||= __evaluate__(options[:client]) ||
                   Threaded.client_override ||
                   __evaluate__(storage_options[:client])
end

#client_options (private)

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 237

def client_options
  @client_options ||= begin
    opts = options.select do |k, _v|
      Mongo::Client::VALID_OPTIONS.include?(k.to_sym)
    end
    opts[:read] = { mode: opts[:read] } if opts[:read].is_a?(Symbol)
    opts
  end
end

#collection(parent = nil) ⇒ Mongo::Collection

Get the collection for this persistence context.

Examples:

Get the collection for this persistence context.

context.collection

Parameters:

  • parent (Object) (defaults to: nil) —

    The parent object whose collection name is used instead of this persistence context's collection name.

Returns:

  • (Mongo::Collection) —

    The collection for this persistence context.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 78

def collection(parent = nil)
  if parent
    parent.collection.with(client_options.except(:database, 'database'))
  else
    client[collection_name.to_sym]
  end
end

#collection_name ⇒ String

Get the collection name for this persistence context.

Examples:

Get the collection name for this persistence context.

context.collection_name

Returns:

  • (String) —

    The collection name for this persistence context.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 93

def collection_name
  @collection_name ||= __evaluate__(options[:collection] ||
                         storage_options[:collection])
end

#database_name ⇒ String

Get the database name for this persistence context.

Examples:

Get the database name for this persistence context.

context.database_name

Returns:

  • (String) —

    The database name for this persistence context.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 105

def database_name
  __evaluate__(database_name_option) || client.database.name
end

#database_name_option (private)

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 247

def database_name_option
  @database_name_option ||= options[:database] ||
                            Threaded.database_override ||
                            storage_options[:database]
end

#for_child(document) ⇒ PersistenceContext

Returns a new persistence context that is consistent with the given child document, inheriting most appropriate settings.

Parameters:

Returns:

  • (PersistenceContext) —

    the new persistence context

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 56

def for_child(document)
  if document.is_a?(Class)
    return self if document == (@object.is_a?(Class) ? @object : @object.class)
  elsif document.is_a?(Mongoid::Document)
    return self if document.class == (@object.is_a?(Class) ? @object : @object.class)
  else
    raise ArgumentError, 'must specify a class or a document instance'
  end

  PersistenceContext.new(document, options.merge(document.storage_options))
end

#requested_storage_options ⇒ Hash | nil

The subset of provided options that may be used as storage options.

Returns:

  • (Hash | nil) —

    the requested storage options, or nil if none were specified.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 183

def requested_storage_options
  slice = @options.slice(*Mongoid::Clients::Validators::Storage::VALID_OPTIONS)
  slice.any? ? slice : nil
end

#set_options!(opts) (private)

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 190

def set_options!(opts)
  @options ||= opts.each.reduce({}) do |_options, (key, value)|
    raise Errors::InvalidPersistenceOption.new(key.to_sym, VALID_OPTIONS) unless VALID_OPTIONS.include?(key.to_sym)

    value ? _options.merge!(key => value) : _options
  end
end

#verify_encryption_schema!(client) (private)

Refuse to use a client that would store a model's encrypted fields in plaintext.

The automatic encryption schema is keyed by namespace and is built once, when the client is created. The driver looks the target namespace up in that schema, and when it is absent it asks the server for a schema instead; a collection without a validator then yields no encryption at all, and no error. Checking the namespace here is what turns that silent downgrade into a failure.

Parameters:

  • client (Mongo::Client) —

    The client this context resolved to.

Raises:

  • (Errors::NoEncryptionSchema) —

    if the model needs an encryption schema and the client's schema does not cover the target namespace.

[ GitHub ]

  
# File 'lib/mongoid/persistence_context.rb', line 218

def verify_encryption_schema!(client)
  klass = @object.is_a?(Class) ? @object : @object.class
  # A model whose encrypted fields all live on embedded models declares no
  # encryption of its own, and still needs a schema for its collection.
  return unless klass.respond_to?(:requires_encryption_schema?) && klass.requires_encryption_schema?
  # An embedded document is persisted as part of its parent, so the
  # parent's context is what governs the namespace.
  return if klass.embedded?
  # Collection and index management sends no document data, so it does not
  # need an encryption-capable client.
  return if Threaded.managing_collection?

  schema_map = client.options.dig(:auto_encryption_options, :schema_map)
  namespace = "#{client.database.name}.#{collection_name}"
  return if schema_map&.key?(namespace)

  raise Errors::NoEncryptionSchema.new(klass, namespace, client_name)
end