lib/mongoid/shardable.rb
# frozen_string_literal: true
# rubocop:todo all
module Mongoid
# This module contains behavior for adding shard key fields to updates.
module Shardable
extend ActiveSupport::Concern
included do
# Returns the list of shard key fields, if shard key was declared on
# this model. If no shard key was declared, returns an empty array.
#
# @return [ Array<Symbol> ] List of shard key fields.
# @api public
cattr_accessor :shard_key_fields
self.shard_key_fields = []
# Returns the shard configuration, which is a hash with the following
# (symbol) keys:
#
# - keys: A hash mapping (symbol) field names to values, defining the
# shard key. Values can be either the integer 1 for ranged sharding
# or the string "hashed" for hashed sharding.
# - options: A hash containing options for shardCollections command.
#
# If shard key was not declared via the +shard_key+ macro, +shard_config+
# attribute is nil.
#
# @example Get the shard configuration.
# Model.shard_config
# # => {key: {foo: 1, bar: 1}, options: {unique: true}}
#
# @return [ Hash | nil ] Shard configuration.
# @api public
cattr_accessor :shard_config
end
# Get the shard key fields.
#
# @note Refactored from using delegate for class load performance.
#
# @example Get the shard key fields.
# model.shard_key_fields
#
# @return [ Array<String> ] The shard key field names.
def shard_key_fields
self.class.shard_key_fields
end
# Returns the selector that would match the defined shard keys. If
# `prefer_persisted` is false (the default), it uses the current values
# of the specified shard keys, otherwise, it will try to use whatever value
# was most recently persisted.
#
# @param [ true | false ] prefer_persisted Whether to use the current
# value of the shard key fields, or to use their most recently persisted
# values.
#
# @return [ Hash ] The shard key selector.
#
# @api private
def shard_key_selector(prefer_persisted: false)
shard_key_fields.each_with_object({}) do |field, selector|
selector[field.to_s] = shard_key_field_value(field.to_s, prefer_persisted: prefer_persisted)
end
end
# Returns the selector that would match the existing version of this
# document in the database.
#
# If the document is not persisted, this method uses the current values
# of the shard key fields. If the document is persisted, this method
# uses the values retrieved from the database.
#
# @return [ Hash ] The shard key selector.
#
# @api private
def shard_key_selector_in_db
shard_key_selector(prefer_persisted: true)
end
# Returns the value for the named shard key. If the field identifies
# an embedded document, the key will be parsed and recursively evaluated.
# If `prefer_persisted` is true, the value last persisted to the database
# will be returned, regardless of what the current value of the attribute
# may be.
#
# @param [String] field The name of the field to evaluate
# @param [ true|false ] prefer_persisted Whether or not to prefer the
# persisted value over the current value.
#
# @return [ Object ] The value of the named field.
#
# @api private
def shard_key_field_value(field, prefer_persisted:)
if field.include?(".")
relation, remaining = field.split(".", 2)
send(relation)&.shard_key_field_value(remaining, prefer_persisted: prefer_persisted)
elsif prefer_persisted && !new_record?
attribute_was(field)
else
send(field)
end
end
module ClassMethods
# Specifies a shard key with the field(s) specified.
#
# @example Specify the shard key.
#
# class Person
# include Mongoid::Document
# field :first_name, :type => String
# field :last_name, :type => String
#
# shard_key first_name: 1, last_name: 1
# end
def shard_key(*args)
unless args.first.is_a?(Hash)
# Shorthand syntax
if args.last.is_a?(Hash)
raise ArgumentError, 'Shorthand shard_key syntax does not permit options'
end
spec = Hash[args.map do |name|
[name, 1]
end]
return shard_key(spec)
end
if args.length > 2
raise ArgumentError, 'Full shard_key syntax requires 1 or 2 arguments'
end
spec, options = args
spec = Hash[spec.map do |name, value|
if value.is_a?(Symbol)
value = value.to_s
end
[database_field_name(name).to_sym, value]
end]
self.shard_key_fields = spec.keys
self.shard_config = {
key: spec.freeze,
options: (options || {}).dup.freeze,
}.freeze
end
end
end
end