lib/mongo_mapper/plugins/keys/key.rb in mongo_mapper-0.12.0 vs lib/mongo_mapper/plugins/keys/key.rb in mongo_mapper-0.13.0.beta1

- old
+ new

@@ -1,60 +1,106 @@ # encoding: UTF-8 module MongoMapper module Plugins module Keys class Key - attr_accessor :name, :type, :options, :default_value + attr_accessor :name, :type, :options, :default, :ivar, :abbr + ID_STR = '_id' + def initialize(*args) - options = args.extract_options! + options_from_args = args.extract_options! @name, @type = args.shift.to_s, args.shift - self.options = (options || {}).symbolize_keys - self.default_value = self.options[:default] + self.options = (options_from_args || {}).symbolize_keys + @dynamic = !!options[:__dynamic] + @embeddable = type.respond_to?(:embeddable?) ? type.embeddable? : false + @is_id = @name == ID_STR + @typecast = @options[:typecast] + @has_default = !!options.key?(:default) + self.default = self.options[:default] if default? + + if abbr = @options[:abbr] || @options[:alias] || @options[:field_name] + @abbr = abbr.to_s + elsif @name.match(/^[A-Z]/) and !dynamic? + @abbr = @name + @name = @name.gsub(/^([A-Z])/) {|m| m.downcase } + Kernel.warn "Key names may not start with uppercase letters. If your field starts " + + "with an uppercase letter, use :field_name to specify the real field name. " + + "Accessors called `#{@name}` have been created instead." + end + @ivar = :"@#{name}" if valid_ruby_name? + validate_key_name! unless dynamic? end + def persisted_name + @abbr || @name + end + def ==(other) - @name == other.name && @type == other.type + @name == other.name && @type == other.type && @abbr == other.abbr end def embeddable? - return false unless type.respond_to?(:embeddable?) - type.embeddable? + @embeddable end def number? type == Integer || type == Float end + def default? + @has_default + end + + def dynamic? + @dynamic + end + def get(value) - if value.nil? && !default_value.nil? - if default_value.respond_to?(:call) - return default_value.call - else - # Using Marshal is easiest way to get a copy of mutable objects - # without getting an error on immutable objects - return Marshal.load(Marshal.dump(default_value)) - end - end + # Special Case: Generate default _id on access + value = default_value if @is_id and !value - if options[:typecast].present? - type.from_mongo(value).map! { |v| typecast_class.from_mongo(v) } + if @typecast + klass = typecast_class # Don't make this lookup on every call + type.from_mongo(value).map! { |v| klass.from_mongo(v) } else type.from_mongo(value) end end def set(value) - type.to_mongo(value).tap do |values| - if options[:typecast].present? - values.map! { |v| typecast_class.to_mongo(v) } - end + # Avoid tap here so we don't have to create a block binding. + values = type.to_mongo(value) + values.map! { |v| typecast_class.to_mongo(v) } if @typecast + values + end + + def default_value + return unless default? + if default.instance_of? Proc + type.to_mongo default.call + else + # Using Marshal is easiest way to get a copy of mutable objects + # without getting an error on immutable objects + type.to_mongo Marshal.load(Marshal.dump(default)) end end + def valid_ruby_name? + !!@name.match(/\A[a-z_][a-z0-9_]*\z/i) + end + private def typecast_class @typecast_class ||= options[:typecast].constantize + end + + def validate_key_name! + if %w( id ).include? @name + raise MongoMapper::InvalidKey.new("`#{@name}` is a reserved key name (did you mean to use _id?)") + elsif !valid_ruby_name? + raise MongoMapper::InvalidKey.new("`#{@name}` is not a valid key name. Keys must match [a-z][a-z0-9_]*") + end end end end end end