/lib/mongo_mapper/plugins/keys.rb
http://github.com/jnunemaker/mongomapper · Ruby · 469 lines · 386 code · 77 blank · 6 comment · 71 complexity · 6c696eac8cf5f9d77dc8b1e28da7f308 MD5 · raw file
- # 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
- end
- end