0

Suppose I'm working on a MongoMapper class that looks like this:

class Animal
    include MongoMapper::Document

    key :type, String, :required => true
    key :color, String
    key :feet, Integer
end

Now I want to store a bird's wingspan. Would it be better to add this, even though it's irrelevant for many documents and feels a bit untidy:

    key :wingspan, Float

Or this, even though it's an indescriptive catch-all that feels like a hack:

    key :metadata, Hash

It seems like the :metadata approach (for which there's precedent in the code I'm inheriting) is almost redundant to the Mongo document as a whole: they're both intended to be schemaless buckets of key-value pairs.

However, it also seems like adding animal-specific keys is a slippery slope to a pretty ugly model.

Any alternatives (create a Bird subclass)?

Ross
  • 1,415
  • 10
  • 10

2 Answers2

1

MongoMapper doesn't store keys that are nil, so if you did define key :wingspan only the documents that actually set that key would store it.

If you opt not to define the key, you can still set/get it with my_bird[:wingspan] = 23. (The [] call will actually automatically define a key for you; similarly if a doc comes back from MongoDB with a key that's not explicitly defined a key will be defined for it and all docs of that class--it's kind of a bug to define it for the whole class but since nil keys aren't stored it's not so much of a problem.)

If bird has its own behavior as well (it probably does), then a subclass makes sense. For birds and animals I would take this route, since every bird is an animal. MongoDB is much nicer than ActiveRecord for Single Table/Single Collection Inheritance, because you don't need a billion migrations and your code makes it clear which attributes go with which classes.

Brian Hempel
  • 8,844
  • 2
  • 24
  • 19
  • Interesting - I just played around and confirmed that undefined keys are indeed stored and retrieved. So then, what's the point of defining keys in your model at all (aside from setting defaults and :required => true)? – Ross Mar 30 '12 at 18:29
  • Well, the `key` call does `def my_attr` and `def my_attr=`, so you can do `bird.wingspan`. For undefined keys, `key` is not called until an object is _loaded from the database_, so if your app is cold (e.g. tests) then `Bird.new.wingspan` will raise a `NoMethodError` since `key :wingspan` hasn't happened yet. – Brian Hempel Mar 30 '12 at 20:36
1

It's hard to give a good answer without knowing how you intend to extend the database in the future and how you expect to use the information you store. If you were storing large numbers of birds and wanted to summarize on wingspan, then wingspan would be helpful even if it would be unused for other animals. If you plan to store random arbitrary information for every known animal, there are too many possibilities to try to track in a schema and the metadata approach would be more usable.

Tad Marshall
  • 1,353
  • 8
  • 10