Permalink
Browse files

Add key aliasing

Add spec for key names which don't map to Ruby names easily.

Add :field_name as alternate for :abbr/:alias

Closes #353
  • Loading branch information...
cheald committed Jul 7, 2013
1 parent 9b3bd27 commit f001de06ca5487787862b8a94f898e5a88656a6a
@@ -24,7 +24,7 @@ def reload
get_proxy(association).reset
end
instance_variables.each { |ivar| remove_instance_variable(ivar) }
load_from_database(doc)
initialize_from_database(doc)
self
else
raise DocumentNotFound, "Document match #{_id.inspect} does not exist in #{collection.name} collection"
@@ -23,16 +23,26 @@ def keys
@keys ||= {}
end

def unaliased_keys
keys.inject({}) {|h, (n, k)| h[k.name] = k if n == k.name; h }
end

def key(*args)
Key.new(*args).tap do |key|
keys[key.name] = key
keys[key.abbr] = key if key.abbr
create_accessors_for(key)
create_key_in_descendants(*args)
create_indexes_for(key)
create_validations_for(key)
end
end

def persisted_name(name)
keys[name.to_s].persisted_name
end
alias_method :abbr, :persisted_name

def key?(key)
keys.key? key.to_s
end
@@ -42,7 +52,7 @@ def using_object_id?
end

def object_id_keys
@object_id_keys ||= keys.keys.select { |key| keys[key].type == ObjectId }.map(&:to_sym)
@object_id_keys ||= unaliased_keys.keys.select { |key| keys[key].type == ObjectId }.map(&:to_sym)
end

def object_id_key?(name)
@@ -169,13 +179,17 @@ def create_validations_for(key)

def initialize(attrs={})
@_new = true
@_mm_keys = self.class.keys

initialize_default_values(attrs)
self.attributes = attrs
yield self if block_given?
end

def initialize_from_database(attrs={})
@_new = false
@_mm_keys = self.class.keys

initialize_default_values(attrs)
load_from_database(attrs)
self
@@ -197,12 +211,11 @@ def attributes=(attrs)
end
end

def to_mongo
def to_mongo(include_abbreviatons = true)
BSON::OrderedHash.new.tap do |attrs|
keys.each do |name, key|
self.class.unaliased_keys.each do |name, key|
if key.type == ObjectId || !self[key.name].nil?
value = key.set(self[key.name])
attrs[name] = value
attrs[include_abbreviatons && key.persisted_name || name] = key.set(self[key.name])
end
end

@@ -219,7 +232,7 @@ def to_mongo
end

def attributes
to_mongo.with_indifferent_access
to_mongo(false).with_indifferent_access
end

def assign(attrs={})
@@ -290,9 +303,6 @@ def embedded_keys
def load_from_database(attrs)
return if attrs == nil || attrs.blank?

# Init the keys ivar. Due to the volume of times this method is called, we don't want it in a method.
@_mm_keys ||= self.class.keys

attrs.each do |key, value|
if !@_mm_keys.key?(key) && respond_to?(:"#{key}=")
self.send(:"#{key}=", value)
@@ -315,7 +325,6 @@ def write_key(name, value)
end

def internal_write_key(name, value, cast = true)
@_mm_keys ||= self.class.keys
key = @_mm_keys[name] || self.class.key(name)
as_mongo = cast ? key.set(value) : value
as_typecast = key.get(as_mongo)
@@ -3,7 +3,7 @@ module MongoMapper
module Plugins
module Keys
class Key
attr_accessor :name, :type, :options, :default, :ivar
attr_accessor :name, :type, :options, :default, :ivar, :abbr

ID_STR = '_id'

@@ -15,6 +15,9 @@ def initialize(*args)
self.options = (options_from_args || {}).symbolize_keys
@ivar = :"@#{name}" # Optimization - used to avoid spamming #intern from internal_write_keys
@embeddable = nil
if abbr = @options[:abbr] || @options[:alias] || @options[:field_name]
@abbr = abbr.to_s
end

# We'll use this to reduce the number of operations #get has to perform, which improves load speeds
@is_id = @name == ID_STR
@@ -25,8 +28,12 @@ def initialize(*args)
end
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?
@@ -54,7 +54,7 @@ def has_many(*args, &extension)
end

def column_names
keys.keys
unaliased_keys.keys
end

# Returns returns an ActiveRecordAssociationAdapter for an association. This adapter has an API that is a
@@ -0,0 +1,103 @@
require 'spec_helper'

describe "Keys" do
describe "with aliases" do
AliasedKeyModel = Doc do
key :foo, :abbr => :f
key :with_underscores, :alias => "with-hyphens"
key :field_name, :field_name => "alternate_field_name"
key :bar
end

before { AliasedKeyModel.collection.drop }

context "standard key operations" do
before do
AliasedKeyModel.create(:foo => "whee!", :bar => "whoo!")
end

it "should serialize with aliased keys" do
AliasedKeyModel.collection.find_one.keys.should =~ %w(_id f bar)

AliasedKeyModel.first.tap do |d|
d.foo.should == "whee!"
d.bar.should == "whoo!"
end
end

it "should permit querying via aliases" do
AliasedKeyModel.where(AliasedKeyModel.abbr(:f) => "whee!").first.foo.should == "whee!"
end

it "should serialize to JSON with full keys" do
AliasedKeyModel.first.as_json.tap do |json|
json.should have_key "foo"
json.should_not have_key "f"
end
end
end

context "given field which are not valid Ruby method names" do
before { AliasedKeyModel.create(:with_underscores => "foobar") }
it "should work" do
AliasedKeyModel.first.with_underscores.should == "foobar"
AliasedKeyModel.collection.find_one["with-hyphens"].should == "foobar"
end
end

context "given a field aliased with :field_name" do
before { AliasedKeyModel.create(:field_name => "foobar") }
it "should work" do
AliasedKeyModel.first.field_name.should == "foobar"
AliasedKeyModel.collection.find_one["alternate_field_name"].should == "foobar"
end
end

context "associations" do
AssociatedKeyWithAlias = Doc do
set_collection_name "associated_documents"
key :name, String, :abbr => :n
key :association_id, ObjectId, :abbr => :aid
end

OwnerDocWithKeyAliases = Doc do
set_collection_name "owner_documents"
key :name, String, :abbr => :n
many :associated_documents, :class_name => "AssociatedKeyWithAlias", :foreign_key => AssociatedKeyWithAlias.abbr(:association_id)
many :other_documents, :class_name => "EmbeddedDocWithAliases"
end

EmbeddedDocWithAliases = EDoc do
key :embedded_name, String, :abbr => :en
end

before do
AssociatedKeyWithAlias.collection.drop
OwnerDocWithKeyAliases.collection.drop
end

it "should work" do
owner = OwnerDocWithKeyAliases.create(:name => "Big Boss")

associated_documents = 3.times.map {|i| AssociatedKeyWithAlias.new(:name => "Associated Record #{i}") }
owner.associated_documents = associated_documents
owner.save

owner.reload
owner.associated_documents.should =~ associated_documents

AssociatedKeyWithAlias.collection.find_one.keys.should =~ %w(_id n aid)
end

it "should work with embedded documents" do
owner = OwnerDocWithKeyAliases.create(:name => "Big Boss")
owner.other_documents << EmbeddedDocWithAliases.new(:embedded_name => "Underling")
owner.save

owner.reload
owner.other_documents[0].embedded_name.should == "Underling"
owner.collection.find_one["other_documents"][0]["en"].should == "Underling"
end
end
end
end

0 comments on commit f001de0

Please sign in to comment.