Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Key abbreviation #353

Closed
wants to merge 10 commits into from

8 participants

Dan Spinosa Daniel Berkompas John Nunemaker Brian Hempel Brandon Keepers Steve Shreeve Justin MacCarthy Chris Heald
Dan Spinosa

Thanks for taking the time to look over the first pull request. Appreciate and agree with the advice.

The key option is now :abbr and I've overrode keys#to_mongo instead of keys#attributes(true/false).

Thoughts?

...because Mongo stores the entire key with each document, and that's terrible with large datasets.

This patch allows you to add the :abbr option to any key which will define what string is actually stored in Mongo as the key. This is done transparently, so your code can continue to use the human-readable long key while persisting in mongo with the (presumably shorter) abbreviation.

For example:

class User
  include MongoMapper::Document
  key :email_address, String, :abbr => :e
end

u = User.find_by_email_address("spinosa@gmail.com")
u.email_address = "dan@shelby.tv" 
u.save

#user in db looks like {_id: 'NormalObjectId', e: 'dan@shelby.tv' }
John Nunemaker

Wouldn't it be easier to just iterate through keys and create the hash. Then you never have to set the instance var when inherited and such.

I was following the pattern already in place... Since we already receive each key (just below) and build the @keys hash at that time, I figured it was easier to build & understand the abbreviation hash if it followed in the same footsteps (hence dup when inherited).

If not, I'm curious how/when you would iterate the keys to build the abbreviation hash?

John Nunemaker

You already have the key at this point. I don't see any purpose to do another method call just to get the abbreviation. I would add a method to key that is persisted_name or something that returns abbreviation if there is one otherwise just returns the key name. Then you can just do key.persisted_name instead of doing a method call and such.

Daniel Berkompas

@jnunemaker Have you all decided whether you're going to accept this pull request? Seeing that MongoDB doesn't have an easy way to rename keys other than iterating over all the documents in a collection and updating, this feature would make renaming keys much more painless.

This way, if developers decide another name is better for a key, they can just change it and leave the abbreviation the same. MongoDB doesn't need migrations to add/remove "fields", but renames are hard compared to databases with schemas. This would make actually renaming the keys in the database unnecessary.

Dan Spinosa

@danielberkompas check out https://github.com/spinosa/mongomapper/tree/key_abbreviation

Even though my feature branch hasn't made it into master, I merged mongomapper/master back into there about a month ago, so it's fairly up to date.

fyi: have also been using this in production w/o any problems

John Nunemaker
Owner

Nice. I missed the new commits. We'll take a look

Brian Hempel brianhempel commented on the diff
lib/mongo_mapper/plugins/keys.rb
@@ -204,7 +226,25 @@ def attributes
end
end
end
- alias :to_mongo :attributes

Is #attributes still around?

nm I was confused, #attributes is still around.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Brian Hempel

@jnunemaker is this the kind of translation layer you were looking for in #397 that we could leverage for, say, converting date queries?

(On that note, I can't think of a case where using to_mongo to convert a query value would cause problems, unless you have legacy data in the database with weird types. Similarly, I can't think of a case where you'd want a query converter more fancy than to_mongo. If that's true, I can work on the query value date translation thing once this is pulled...)

lib/mongo_mapper/plugins/querying/decorator.rb
@@ -20,7 +20,21 @@ def find!(*ids)
end
end
end
-
+
+ %w(first last all where find_each exist? exists? count).each do |meth|

I'm not sure this list is complete (paginate,fine_one, distinct, amend, filter, ignore, only, and order also take criteria and/or field names) . If you override Pluck::Query#amend you'll get several of these methods for free, but there needs to be a more robust way to get all of the plucky methods...something like the Plucky::Methods (which I need to update anyway for #387) but one for methods that take criteria and another for methods that take options ... or have abbreviations handled inside of plucky. Plucky already handles aliasing :id to :_id (though in two places in the code, once for criteria and once for options.)

Thoughts, @jnunemaker @bkeepers ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Brandon Keepers

I would love to get this merged. My one concern is the rewriting of query options seems incomplete. It doesn't override all of the plucky methods, and it won't work with embedded docs.

I would be tempted to just remove the rewriting of keys, and force people to use the abbreviations when querying. If you're using scopes or defining custom model methods for querying, it won't be that weird.

John Nunemaker
Owner
John Nunemaker
Owner

If we pull the rewriting of keys on querying and change to just have a keys class/instance method I'll happily pull this. I'd just make a Key#persisted_name method, as I did in toystore, and use that rather than have two separate locations for keys (keys and keys_by_abbr).

Steve Shreeve

Any update on this?

Dan Spinosa spinosa Merge branch 'master' into key_abbreviation
Conflicts:
	lib/mongo_mapper/plugins/keys.rb
	lib/mongo_mapper/plugins/keys/key.rb
6239dd5
Justin MacCarthy

+1 for this. In very large collections the key lengths really get to be important.
Abbreviated keys in the data would be very useful.

Chris Heald
Owner

I took a stab at this one --

https://github.com/cheald/mongomapper/compare/alias_keys

I'm not doing any key translation, but I provide a Model::persisted_name (aliased to abbr) method that allows people to use full field names in queries:

class Model
  key :full_field_name, String, :abbr => :ffn
end

# These two are functionally equivalent:
Model.where(Model.abbr(:full_field_name) => "whatever")
Model.where(:ffn => "whatever")

(This is somewhat shades of Arel's schema reflection)

Association names aren't aliasable, though foreign keys obviously are. Seems to work nicely, and it's a very small change from current master. Thoughts?

John Nunemaker
Owner

@cheald your stuff looks fine to me.

Chris Heald cheald closed this pull request from a commit
Chris Heald cheald 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
f001de0
Chris Heald cheald closed this in f001de0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 21, 2011
  1. Dan Spinosa

    basic key aliasing when saving/loading, transparent to code (ie. mode…

    spinosa authored
    …l attributes/serialization use full keys, aliases only stored in DB). TODO: implement querying, finder options and associations w/ aliases.
Commits on Nov 22, 2011
  1. Dan Spinosa

    use Plucky decorator to intercept keys and re-write them using aliase…

    spinosa authored
    …s, so Plucky is given the correct key to use in the DB.
  2. Dan Spinosa
  3. Dan Spinosa
Commits on Nov 23, 2011
  1. Dan Spinosa
  2. Dan Spinosa
Commits on Nov 30, 2011
  1. Dan Spinosa

    no need for method call since we already have the key, just use the k…

    spinosa authored
    …ey's abbr property (if it has one)
  2. Dan Spinosa
Commits on Feb 17, 2012
  1. Dan Spinosa

    Merge branch 'master' into key_abbreviation

    spinosa authored
    Conflicts:
    	lib/mongo_mapper/plugins/keys.rb
Commits on Jan 14, 2013
  1. Dan Spinosa

    Merge branch 'master' into key_abbreviation

    spinosa authored
    Conflicts:
    	lib/mongo_mapper/plugins/keys.rb
    	lib/mongo_mapper/plugins/keys/key.rb
This page is out of date. Refresh to see the latest.
44 examples/key_abbreviation.rb
View
@@ -0,0 +1,44 @@
+$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
+require 'mongo_mapper'
+require 'pp'
+
+MongoMapper.database = 'testing'
+
+class Goat
+ include MongoMapper::Document
+ belongs_to :user
+
+ key :user_id, ObjectId, :abbr => :g_id
+end
+
+class User
+ include MongoMapper::Document
+ key :email_address, String, :abbr => :e
+ key :hate_to_store_this_long_key_for_each_document, String, :abbr => :'l'
+
+ many :goats, :class_name => 'Goat', :foreign_key => :g_id
+end
+
+# create, update objects like normal
+user = User.create(:hate_to_store_this_long_key_for_each_document => "tiny")
+puts MongoMapper.connection['testing']['users'].find({"_id" => user.id}).first
+
+user.update_attribute(:email_address, 'IHeartSmallDB@gmail.com')
+puts MongoMapper.connection['testing']['users'].find({"_id" => user.id}).first
+
+# query like normal, object returned will respond to human readable keys
+u = User.find(user.id)
+puts "email address is: #{u.email_address}"
+puts "long-key is: #{u.hate_to_store_this_long_key_for_each_document}"
+puts "obj inspect looks like: #{u.inspect}"
+puts "raw mongo looks like: #{MongoMapper.connection['testing']['users'].find({"_id" => user.id}).first}"
+
+#dynamic finders work as expected
+u2 = User.find_by_email_address('IHeartSmallDB@gmail.com')
+puts u2.inspect
+
+#abbreviations are useful for associations, just need to set the foreign_key in the parent
+u2.goats << Goat.new()
+u2.reload
+puts "the users goats: #{u2.goats.inspect}"
+puts "and the goats' owner: #{u2.goats[0].user.inspect}"
2  lib/mongo_mapper/plugins/dynamic_querying.rb
View
@@ -13,7 +13,7 @@ def dynamic_find(finder, args)
finder.attributes.each_with_index do |attr, index|
attributes[attr] = args[index]
end
-
+
options = args.extract_options!.merge(attributes)
if result = send(finder.finder, options)
70 lib/mongo_mapper/plugins/keys.rb
View
@@ -16,16 +16,25 @@ module Keys
module ClassMethods
def inherited(descendant)
descendant.instance_variable_set(:@keys, keys.dup)
+ descendant.instance_variable_set(:@keys_by_abbr, keys_by_abbr.dup)
super
end
def keys
@keys ||= {}
end
+
+ # Added To Master by DS.
+ def keys_by_abbr
+ @keys_by_abbr ||= {}
+ end
+ # Altered From Master by DS.
+ # Need to store abbreviation map
def key(*args)
Key.new(*args).tap do |key|
keys[key.name] = key
+ keys_by_abbr[key.abbr.to_s] = key if key.abbr
create_accessors_for(key)
create_key_in_descendants(*args)
create_indexes_for(key)
@@ -66,6 +75,22 @@ def load(attrs)
self
end.allocate.initialize_from_database(attrs)
end
+
+ def key_name_for_abbr(key_abbr)
+ if key = keys_by_abbr[key_abbr.to_s]
+ key.name
+ else
+ key_abbr
+ end
+ end
+
+ def abbr_for_key_name(key_name)
+ if key = keys[key_name.to_s]
+ key.abbr || key.name
+ else
+ key_name
+ end
+ end
private
def key_accessors_module_defined?
@@ -210,7 +235,30 @@ def attributes
end
end
end
- alias :to_mongo :attributes

Is #attributes still around?

nm I was confused, #attributes is still around.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ # Altered From Master by DS.
+ # MongoMapper aliases this to attributes. But when storing, we need to store using abbreviation.
+ # So there is a small tweak when creating the Hash for simple keys.
+ def to_mongo
+ HashWithIndifferentAccess.new.tap do |attrs|
+ keys.each do |name, key|
+ if key.type == ObjectId || !self[key.name].nil?
+ value = key.set(self[key.name])
+ attrs[key.abbr || name] = 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 assign(attrs={})
warn "[DEPRECATION] #assign is deprecated, use #attributes="
@@ -243,7 +291,7 @@ def id=(value)
self[:_id] = value
end
-
+
def read_key(key_name)
instance_key = :"@#{key_name}"
if instance_variable_defined? instance_key
@@ -264,6 +312,11 @@ def []=(name, value)
def keys
self.class.keys
end
+
+ # Added To Master by DS.
+ def keys_by_abbr
+ self.class.keys_by_abbr
+ end
def key_names
@key_names ||= keys.keys
@@ -278,9 +331,12 @@ def embedded_keys
end
private
+ # Altered From Master by DS.
+ # The key read from the DB is the abbreviation. Need to store it as the full key in the model.
def load_from_database(attrs)
return if attrs == nil or attrs.blank?
attrs.each do |key, value|
+ key = key_name_for_abbr(key)
if respond_to?(:"#{key}=") && !self.class.key?(key)
self.send(:"#{key}=", value)
else
@@ -299,6 +355,16 @@ def set_parent_document(key, value)
value._parent_document = self
end
end
+
+ # Added To Master by DS.
+ def key_name_for_abbr(key)
+ self.class.key_name_for_abbr(key)
+ end
+
+ # Added To Master by DS.
+ def abbr_for_key_name(key)
+ self.class.abbr_for_key_name(key)
+ end
def write_key(name, value)
key = keys[name.to_s]
13 lib/mongo_mapper/plugins/keys/key.rb
View
@@ -3,10 +3,15 @@ module MongoMapper
module Plugins
module Keys
class Key
- attr_accessor :name, :type, :options, :default
+
+ # Altered From Master by DS.
+ # Storing :abbr, the key's abbreviation when stored in the DB
+ attr_accessor :name, :type, :options, :default, :abbr
ID_STR = '_id'
+ # Altered From Master by DS.
+ # Need to set self.abbr if included in key's optoins.
def initialize(*args)
options_from_args = args.extract_options!
@name, @type = args.shift.to_s, args.shift
@@ -15,10 +20,14 @@ def initialize(*args)
if options.key?(:default)
self.default = self.options[:default]
end
+
+ if options.key?(:abbr)
+ self.abbr = self.options[:abbr]
+ end
end
def ==(other)
- @name == other.name && @type == other.type
+ @name == other.name && @type == other.type && @abbr == other.abbr
end
def embeddable?
61 lib/mongo_mapper/plugins/querying/decorator.rb
View
@@ -38,7 +38,34 @@ def find!(*ids)
end
end
end
-
+
+ # Added To Master by DS.
+ # These methods interact with driver, need to use abbreviation, not full attribute name.
+ # Plucky performs the following aliases (we don't need the aliased method):
+ # alias_method :each, :find_each
+ # alias_method :first, :find_one
+ # alias_method :size, :count
+ # alias_method :exist?, :exists?
+ # alias_method :filter, :where
+ # alias_method :to_a, :all
+ %w(last all where find_one find_each exists? count).each do |meth|
+ define_method(meth.to_sym) do |*args, &block|
+ rewrite_keys_with_abbr(args)
+ super(*args, &block)
+ end
+ end
+
+ # Added To Master by DS.
+ # These methods interact with driver, need to use abbreviation, not full attribute name.
+ # Plucky performs the following aliases (we don't need the aliased method):
+ # alias_method :order, :sort
+ %w(fields sort).each do |meth|
+ define_method(meth.to_sym) do |*args, &block|
+ args = rewrite_args_with_abbr(args)
+ super(*args, &block)
+ end
+ end
+
private
def method_missing(method, *args, &block)
return super unless model.respond_to?(method)
@@ -49,6 +76,38 @@ def method_missing(method, *args, &block)
result
end
end
+
+ # Added To Master by DS.
+ # These methods interact with driver, need to use abbreviation, not full attribute name.
+ def rewrite_keys_with_abbr(args)
+ if args[0].is_a? Hash and model.respond_to? :abbr_for_key_name
+ abbreviated_attrs = {}
+ args[0].each do |k,v|
+ if k.is_a? SymbolOperator
+ abbreviated_attrs[SymbolOperator.new( model.abbr_for_key_name(k.field.to_s).to_sym, k.operator )] = v
+ else
+ abbreviated_attrs[model.abbr_for_key_name(k)] = v
+ end
+ end
+ args[0] = abbreviated_attrs
+ end
+ end
+
+ # Added To Master by DS.
+ # These methods interact with driver, need to use abbreviation, not full attribute name.
+ def rewrite_args_with_abbr(args)
+ if args.is_a? Array and model.respond_to? :abbr_for_key_name
+ abbreviated_args = []
+ args.each do |a|
+ if a.is_a? SymbolOperator
+ abbreviated_args << SymbolOperator.new( model.abbr_for_key_name(a.field.to_s).to_sym, a.operator )
+ else
+ abbreviated_args << model.abbr_for_key_name(a)
+ end
+ end
+ end
+ abbreviated_args
+ end
end
end
end
32 test/functional/test_associations.rb
View
@@ -43,4 +43,36 @@ class AwesomePost
post1 = post1.reload
post1.tags.should == [tag1]
end
+
+ should "interoperate with abbreviated keys" do
+ class AnotherUser
+ include MongoMapper::Document
+
+ many :goats, :class_name => 'AssociationsTest::AwesomeGoat', :foreign_key => :u_id
+ end
+ AnotherUser.collection.remove
+
+ class AwesomeGoat
+ include MongoMapper::Document
+
+ key :name, String, :abbr => :n
+ key :user_id, ObjectId, :abbr => :u_id
+
+ belongs_to :user, :class_name => 'AssociationsTest::AnotherUser'
+ end
+ AwesomeGoat.collection.remove
+
+ user = AnotherUser.create
+ user.goats << goat1 = AwesomeGoat.new(:name => "g6")
+ user.goats << goat2 = AwesomeGoat.new(:name => "g7")
+
+ user.reload
+ user.goats.should == [goat1, goat2]
+
+ goat1.reload
+ goat1.user.should == user
+
+ goat2.reload
+ goat2.user.should == user
+ end
end
164 test/functional/test_key_abbreviation.rb
View
@@ -0,0 +1,164 @@
+require 'test_helper'
+require 'models'
+
+class KeyAbbreviationTest < Test::Unit::TestCase
+ def setup
+ @document = Doc do
+ key :first_name, String, :abbr => :f
+ key :middle_name, String, :abbr => :F
+ key :last_name, String, :abbr => :l
+ key :unabbreviated, String
+ key :age, Integer, :abbr => :a
+ end
+
+ @doc = @document.create(:first_name => 'John', :middle_name => "Harold", :last_name => 'Nunemaker', :unabbreviated => "foo", :age => 27)
+ @abe = @document.create(:first_name => 'Abe', :last_name => 'Lincoln', :age => 200)
+ end
+
+ context "basic i/o of abbreviated keys" do
+ should "store key abbreviation in db, not full keys" do
+ raw_object = MongoMapper.connection[@document.database.name][@document.collection.name].find({"_id" => @doc.id}).first
+ raw_object.include?("first_name").should == false
+ raw_object["f"].should == "John"
+ raw_object.include?("middle_name").should == false
+ raw_object["F"].should == "Harold"
+ raw_object.include?("last_name").should == false
+ raw_object["l"].should == "Nunemaker"
+ raw_object["unabbreviated"].should == "foo"
+ end
+
+ should "create model with long keys when loaded from abbreviated model in db" do
+ @doc.reload
+ @doc.first_name.should == "John"
+ @doc.last_name.should == "Nunemaker"
+ @doc.unabbreviated.should == "foo"
+ end
+
+ should "serialize to_json with long keys" do
+ json = ActiveSupport::JSON.encode(@doc)
+ assert_no_match %r{"f"}, json
+ assert_match %r{"first_name":"John"}, json
+ assert_no_match %r{"l"}, json
+ assert_match %r{"last_name":"Nunemaker"}, json
+ assert_match %r{"unabbreviated":"foo"}, json
+ end
+
+ should "serialize to_xml with long keys" do
+ xml = @doc.to_xml
+ assert_no_match %r{<f>}, xml
+ assert_match %r{<first-name>John</first-name>}, xml
+ assert_no_match %r{<l>}, xml
+ assert_match %r{<last-name>Nunemaker</last-name>}, xml
+ assert_match %r{<unabbreviated>foo</unabbreviated>}, xml
+ end
+ end
+
+ context "dynamic finders" do
+ should "accept query on long key and perform mongo query on abbreviations" do
+ d = @document.find_by_first_name('John')
+ d.should == @doc
+ end
+
+ should "accept query on multiple abbreviated keys" do
+ d = @document.find_by_first_name_and_last_name('John', 'Nunemaker')
+ d.should == @doc
+ end
+
+ should "accept query on abbreviated and non-abbreviated keys" do
+ d = @document.find_by_first_name_and_unabbreviated('John', 'foo')
+ d.should == @doc
+ end
+
+ should "find all on abbreviated key" do
+ d = @document.find_all_by_first_name('John')
+ d.size.should == 1
+ d[0].should == @doc
+ end
+ end
+
+ context "querying with abbreviated keys" do
+ should "find first from abbreviated key" do
+ d = @document.first(:first_name => 'John')
+ d.should == @doc
+ end
+
+ should "find last from abbreviated key" do
+ d = @document.last(:first_name => 'John')
+ d.should == @doc
+ end
+
+ should "count with abbreviated key" do
+ @document.count(:first_name => 'John').should == 1
+ end
+
+ should "find all with abbreviated key" do
+ d = @document.all(:first_name => 'John')
+ d.size.should == 1
+ d[0].should == @doc
+ end
+
+ should "accept abbreviated keys in where clause" do
+ d = @document.where(:first_name => 'John').first
+ d.should == @doc
+ end
+
+ should "accept abbreviated keys in where clause with symbol operators" do
+ d = @document.where(:age.gt => 100).first
+ d.should == @abe
+
+ d2 = @document.where(:age.lt => 100).first
+ d2.should == @doc
+ end
+
+ should "accept key abbreviation sorting w/ with symbol operators" do
+ @document.sort(:last_name.asc).first.should == @abe
+ @document.sort(:last_name.desc).first.should == @doc
+ end
+
+ should "accept key abbreviation with exist?" do
+ @document.exist?(:first_name => 'John').should == true
+ end
+
+ should "accept key abbreviation with exists?" do
+ @document.exists?(:first_name => 'John').should == true
+ end
+
+ should "accept key abbreviations when selecting fields" do
+ d = @document.where(:first_name => 'John').fields(:last_name, :unabbreviated).first
+ d.first_name.should == nil
+ d.last_name.should == 'Nunemaker'
+ d.unabbreviated.should == 'foo'
+ end
+
+ should "accept key abbreviation for normal sort operations" do
+ d = @document.sort(:last_name).first
+ d.should == @abe
+ end
+
+ should "accept key abbreviation for sort operations with symbol operators" do
+ d = @document.sort(:age.asc).first
+ d.should == @doc
+ end
+
+ should "accept mongo conditional operators on abbreviated keys" do
+ d = @document.where(:age => {:$gt => 100, :$lt => 500}).first
+ d.should == @abe
+ end
+
+ should "be able to string together query parts, all using abbreviations" do
+ @document.sort(:age).where(:first_name => 'John').count(:last_name => 'Nunemaker').should == 1
+ @document.sort(:age).where(:first_name => 'John').where(:last_name => 'Nunemaker').first(:unabbreviated => "foo").should == @doc
+ end
+
+ should "be able to find each using abbreviated keys in query" do
+ count = 0
+ @document.find_each(:age.gt => 1) do |d|
+ count += 1
+ (d == @doc || d == @abe).should == true
+ end
+ count.should == 2
+ end
+
+
+ end
+end
4 test/models.rb
View
@@ -56,7 +56,7 @@ class PostComment
class Address
include MongoMapper::EmbeddedDocument
- key :address, String
+ key :address, String, :abbr => :a
key :city, String
key :state, String
key :zip, Integer
@@ -258,4 +258,4 @@ class Robot
key :serial_number, String
embedded_in :post
-end
+end
12 test/unit/test_key.rb
View
@@ -26,6 +26,10 @@ class KeyTest < Test::Unit::TestCase
should "allow setting options" do
Key.new(:foo, Integer, :required => true).options[:required].should be(true)
end
+
+ should "allow setting abbr" do
+ Key.new(:foo, Integer, :abbr => :f).options[:abbr].should be(:f)
+ end
should "default options to {}" do
Key.new(:foo, Integer, nil).options.should == {}
@@ -64,6 +68,10 @@ class KeyTest < Test::Unit::TestCase
should "be equal to another key with same name and type" do
Key.new(:name, String).should == Key.new(:name, String)
end
+
+ should "be equal to another key with same name, type, and abbr" do
+ Key.new(:name, String, :abbr => :n).should == Key.new(:name, String, :abbr => :n)
+ end
should "not be equal to another key with different name" do
Key.new(:name, String).should_not == Key.new(:foo, String)
@@ -73,6 +81,10 @@ class KeyTest < Test::Unit::TestCase
Key.new(:name, String).should_not == Key.new(:name, Integer)
end
+ should "not be equal to another key with different abbr" do
+ Key.new(:name, :abbr => :n).should_not == Key.new(:name, :abbr => :x)
+ end
+
should "know if it is a embedded_document" do
Key.new(:name, EDoc()).embeddable?.should be_true
end
Something went wrong with that request. Please try again.