Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

471 lines (393 sloc) 13.515 kB
# encoding: UTF-8
require 'mongo_mapper/plugins/keys/key'
require 'mongo_mapper/plugins/keys/static'
module MongoMapper
module Plugins
module Keys
extend ActiveSupport::Concern
IS_RUBY_1_9 = method(:const_defined?).arity == 1
included do
extend ActiveSupport::DescendantsTracker
key :_id, ObjectId, :default => lambda { BSON::ObjectId.new }
end
module ClassMethods
def inherited(descendant)
descendant.instance_variable_set(:@keys, keys.dup)
super
end
def keys
@keys ||= {}
end
def dynamic_keys
@dynamic_keys ||= Hash[*unaliased_keys.select {|k, v| v.dynamic? }.flatten(1)]
end
def defined_keys
@defined_keys ||= Hash[*unaliased_keys.select {|k, v| !v.dynamic? }.flatten(1)]
end
def unaliased_keys
@unaliased_keys ||= Hash[*keys.select {|k, v| k == v.name }.flatten(1)]
end
def dealias_keys(hash)
out = {}
hash.each do |k, v|
key = keys[k.to_s]
name = key && key.abbr || k
out[name] = k.to_s.match(/^\$/) && v.is_a?(Hash) ? dealias_keys(v) : v
end
out
end
def dealias_key(name)
key = keys[name.to_s]
key && key.abbr || k
end
alias_method :dealias, :dealias_keys
alias_method :unalias, :dealias_keys
def key(*args)
Key.new(*args).tap do |key|
keys[key.name] = key
keys[key.abbr] = key if key.abbr
create_accessors_for(key) if key.valid_ruby_name? && !key.reserved_name?
create_key_in_descendants(*args)
create_indexes_for(key)
create_validations_for(key)
@dynamic_keys = @defined_keys = @unaliased_keys = @object_id_keys = nil
end
end
def remove_key(name)
if key = keys[name.to_s]
keys.delete key.name
keys.delete key.abbr
remove_method key.name if respond_to? "#{key.name}"
remove_method "#{key.name}=" if respond_to? "#{key.name}="
remove_method "#{key.name}?" if respond_to? "#{key.name}?"
remove_method "#{key.name}_before_type_cast" if respond_to? "#{key.name}_before_type_cast"
remove_key_in_descendants key.name
remove_validations_for key.name
@dynamic_keys = @defined_keys = @unaliased_keys = @object_id_keys = nil
end
end
def persisted_name(name)
if key = keys[name.to_s]
key.persisted_name
else
name
end
end
alias_method :abbr, :persisted_name
def key?(key)
keys.key? key.to_s
end
def using_object_id?
object_id_key?(:_id)
end
def object_id_keys
@object_id_keys ||= unaliased_keys.keys.select { |key| keys[key].type == ObjectId }.map(&:to_sym)
end
def object_id_key?(name)
object_id_keys.include?(name.to_sym)
end
def to_mongo(instance)
instance && instance.to_mongo
end
def from_mongo(value)
value && (value.instance_of?(self) ? value : load(value))
end
# load is overridden in identity map to ensure same objects are loaded
def load(attrs, with_cast = false)
return nil if attrs.nil?
begin
attrs['_type'] ? attrs['_type'].constantize : self
rescue NameError
self
end.allocate.initialize_from_database(attrs, with_cast)
end
private
def key_accessors_module_defined?
# :nocov:
if IS_RUBY_1_9
const_defined?('MongoMapperKeys')
else
const_defined?('MongoMapperKeys', false)
end
# :nocov:
end
def accessors_module
if key_accessors_module_defined?
const_get 'MongoMapperKeys'
else
const_set 'MongoMapperKeys', Module.new
end
end
def create_accessors_for(key)
accessors = ""
if key.read_accessor?
accessors << <<-end_eval
def #{key.name}
read_key(:#{key.name})
end
def #{key.name}_before_type_cast
read_key_before_type_cast(:#{key.name})
end
end_eval
end
if key.write_accessor?
accessors << <<-end_eval
def #{key.name}=(value)
write_key(:#{key.name}, value)
end
end_eval
end
if key.predicate_accessor?
accessors << <<-end_eval
def #{key.name}?
read_key(:#{key.name}).present?
end
end_eval
end
if block_given?
accessors_module.module_eval do
yield
end
end
accessors_module.module_eval accessors
include accessors_module
end
def create_key_in_descendants(*args)
descendants.each { |descendant| descendant.key(*args) }
end
def remove_key_in_descendants(name)
descendants.each { |descendant| descendant.remove_key(name) }
end
def create_indexes_for(key)
if key.options[:index] && !key.embeddable?
warn "[DEPRECATION] :index option when defining key #{key.name.inspect} is deprecated. Put indexes in `db/indexes.rb`"
ensure_index key.name
end
end
def create_validations_for(key)
attribute = key.name.to_sym
if key.options[:required]
if key.type == Boolean
validates_inclusion_of attribute, :in => [true, false]
else
validates_presence_of(attribute)
end
end
if key.options[:unique]
validates_uniqueness_of(attribute)
end
if key.options[:numeric]
number_options = key.type == Integer ? {:only_integer => true} : {}
validates_numericality_of(attribute, number_options)
end
if key.options[:format]
validates_format_of(attribute, :with => key.options[:format])
end
if key.options[:in]
validates_inclusion_of(attribute, :in => key.options[:in])
end
if key.options[:not_in]
validates_exclusion_of(attribute, :in => key.options[:not_in])
end
if key.options[:length]
length_options = case key.options[:length]
when Integer
{:minimum => 0, :maximum => key.options[:length]}
when Range
{:within => key.options[:length]}
when Hash
key.options[:length]
end
validates_length_of(attribute, length_options)
end
end
def remove_validations_for(name)
name = name.to_sym
a_name = [name]
_validators.reject!{ |key, _| key == name }
remove_validate_callbacks a_name
end
def remove_validate_callbacks(a_name)
chain = _validate_callbacks.dup.reject do |callback|
f = callback.raw_filter
f.respond_to?(:attributes) && f.attributes == a_name
end
reset_callbacks(:validate)
chain.each do |callback|
set_callback 'validate', callback.raw_filter
end
end
end
def initialize(attrs={})
@_new = true
init_ivars
initialize_default_values(attrs)
self.attributes = attrs
yield self if block_given?
end
def initialize_from_database(attrs={}, with_cast = false)
@_new = false
init_ivars
initialize_default_values(attrs)
load_from_database(attrs, with_cast)
self
end
def persisted?
!new? && !destroyed?
end
def attributes=(attrs)
return if attrs == nil || attrs.blank?
attrs.each_pair do |key, value|
if respond_to?(:"#{key}=")
self.send(:"#{key}=", value)
else
self[key] = value
end
end
end
def to_mongo(include_abbreviatons = true)
BSON::OrderedHash.new.tap do |attrs|
self.class.unaliased_keys.each do |name, key|
value = self.read_key(key.name)
if key.type == ObjectId || !value.nil?
attrs[include_abbreviatons && key.persisted_name || name] = key.set(value)
end
end
embedded_associations.each do |association|
if documents = instance_variable_get(association.ivar)
if association.is_a?(Associations::OneAssociation)
attrs[association.name] = documents.to_mongo
else
attrs[association.name] = documents.map(&:to_mongo)
end
end
end
end
end
def attributes
to_mongo(false).with_indifferent_access
end
def assign(attrs={})
warn "[DEPRECATION] #assign is deprecated, use #attributes="
self.attributes = attrs
end
def update_attributes(attrs={})
self.attributes = attrs
save
end
def update_attributes!(attrs={})
self.attributes = attrs
save!
end
def update_attribute(name, value)
self.send(:"#{name}=", value)
save(:validate => false)
end
def id
self[:_id]
end
def id=(value)
if self.class.using_object_id?
value = ObjectId.to_mongo(value)
end
self[:_id] = value
end
def keys
self.class.keys
end
def read_key(key_name)
key_name_sym = key_name.to_sym
if @_dynamic_attributes && @_dynamic_attributes.key?(key_name_sym)
@_dynamic_attributes[key_name_sym]
elsif key = keys[key_name.to_s]
if key.ivar && instance_variable_defined?(key.ivar)
value = instance_variable_get(key.ivar)
else
if key.ivar
instance_variable_set key.ivar, key.get(nil)
else
@_dynamic_attributes[key_name_sym] = key.get(nil)
end
end
end
end
def [](key_name); read_key(key_name); end
def attribute(key_name); read_key(key_name); end
def []=(name, value)
write_key(name, value)
end
def key_names
@key_names ||= keys.keys
end
def non_embedded_keys
@non_embedded_keys ||= keys.values.select { |key| !key.embeddable? }
end
def embedded_keys
@embedded_keys ||= keys.values.select(&:embeddable?)
end
protected
def unalias_key(name)
name = name.to_s
if key = keys[name]
key.name
else
name
end
end
private
def init_ivars
@__mm_keys = self.class.keys # Not dumpable
@__mm_default_keys = @__mm_keys.values.select(&:default?) # Not dumpable
@_dynamic_attributes = {} # Dumpable
end
def load_from_database(attrs, with_cast = false)
return if attrs == nil || attrs.blank?
attrs.each do |key, value|
if !@__mm_keys.key?(key) && respond_to?(:"#{key}=")
self.send(:"#{key}=", value)
else
internal_write_key key, value, with_cast
end
end
end
def set_parent_document(key, value)
if key.type and value.instance_of?(key.type) && key.embeddable? && value.respond_to?(:_parent_document)
value._parent_document = self
end
end
# This exists to be patched over by plugins, while letting us still get to the undecorated
# version of the method.
def write_key(name, value)
init_ivars unless @__mm_keys
internal_write_key(name.to_s, value)
end
def internal_write_key(name, value, cast = true)
key = @__mm_keys[name] || dynamic_key(name)
as_mongo = cast ? key.set(value) : value
as_typecast = key.get(as_mongo)
if key.ivar
if key.embeddable?
set_parent_document(key, value)
set_parent_document(key, as_typecast)
end
instance_variable_set key.ivar, as_typecast
else
@_dynamic_attributes[key.name.to_sym] = as_typecast
end
@attributes = nil
value
end
def dynamic_key(name)
self.class.key(name, :__dynamic => true)
end
def initialize_default_values(except = {})
@__mm_default_keys.each do |key|
if !(except && except.key?(key.name))
internal_write_key key.name, key.default_value, false
end
end
end
#end private
end
end
end
Jump to Line
Something went wrong with that request. Please try again.