lib/mongoid/attributes/dynamic.rb
# encoding: utf-8
module Mongoid
module Attributes
# This module contains the behavior for dynamic attributes.
#
# @since 4.0.0
module Dynamic
extend ActiveSupport::Concern
# Override respond_to? so it responds properly for dynamic attributes.
#
# @example Does this object respond to the method?
# person.respond_to?(:title)
#
# @param [ Array ] *args The name of the method.
#
# @return [ true, false ] True if it does, false if not.
#
# @since 4.0.0
def respond_to?(name, include_private = false)
super || (
attributes &&
attributes.has_key?(name.to_s.reader)
)
end
# Define a reader method for a dynamic attribute.
#
# @api private
#
# @example Define a reader method.
# model.define_dynamic_reader(:field)
#
# @param [ String ] name The name of the field.
#
# @since 4.0.0
def define_dynamic_reader(name)
return unless name.valid_method_name?
class_eval <<-READER
def #{name}
attribute_will_change!(#{name.inspect})
read_attribute(#{name.inspect})
end
READER
end
# Define a reader method for a dynamic attribute before type cast.
#
# @api private
#
# @example Define a reader method for an attribute.
# model.define_dynamic_before_type_cast_reader(:field)
#
# @param [ String ] name The name of the field.
#
# @since 4.0.0
def define_dynamic_before_type_cast_reader(name)
class_eval <<-READER
def #{name}_before_type_cast
attribute_will_change!(#{name.inspect})
read_attribute_before_type_cast(#{name.inspect})
end
READER
end
# Define a writer method for a dynamic attribute.
#
# @api private
#
# @example Define a writer method.
# model.define_dynamic_writer(:field)
#
# @param [ String ] name The name of the field.
#
# @since 4.0.0
def define_dynamic_writer(name)
return unless name.valid_method_name?
class_eval <<-WRITER
def #{name}=(value)
write_attribute(#{name.inspect}, value)
end
WRITER
end
# If the attribute is dynamic, add a field for it with a type of object
# and set the value.
#
# @example Process the attribute.
# document.process_attribute(name, value)
#
# @param [ Symbol ] name The name of the field.
# @param [ Object ] value The value of the field.
#
# @since 4.0.0
def process_attribute(name, value)
responds = respond_to?("#{name}=")
if !responds
write_attribute(name, value)
else
send("#{name}=", value)
end
end
# Get an array of inspected dynamic fields for the document.
#
# @example Inspect the dynamic fields.
# document.inspect_dynamic_fields
#
# @return [ String ] An array of pretty printed dynamic field values.
#
# @since 4.0.0
def inspect_dynamic_fields
keys = attributes.keys - fields.keys - relations.keys - ["_id", "_type"]
return keys.map do |name|
"#{name}: #{attributes[name].inspect}"
end
end
# Used for allowing accessor methods for dynamic attributes.
#
# @api private
#
# @example Call through method_missing.
# document.method_missing(:test)
#
# @param [ String, Symbol ] name The name of the method.
# @param [ Array ] *args The arguments to the method.
#
# @return [ Object ] The result of the method call.
#
# @since 4.0.0
def method_missing(name, *args)
attr = name.to_s
return super unless attributes.has_key?(attr.reader)
if attr.writer?
getter = attr.reader
define_dynamic_writer(getter)
write_attribute(getter, args.first)
elsif attr.before_type_cast?
define_dynamic_before_type_cast_reader(attr.reader)
attribute_will_change!(attr.reader)
read_attribute_before_type_cast(attr.reader)
else
getter = attr.reader
define_dynamic_reader(getter)
attribute_will_change!(attr.reader)
read_attribute(getter)
end
end
end
end
end