Mongomatic New and Old
Note: This was originally posted on my previous blog. It is archived here for sentimental, educational (to me) and, in the unlikely case, actual value
Mongomatic 0.7.0 was released this week. Since the release of 0.6.0 several new features have been added. In this post I’ll provide some examples of some Mongomatic features, ones existing from the start and ones recently added, that I have not yet discussed. At the end I will, also, discuss the a bit about the future of Mongomatic.
Modifiers
One of the features I use most in MongoDB and Mongomatic are the modifiers with the interface provided through the Mongomatic::Modifiers module included in every subclass of Mongomatic::Base. Mongomatic::Modifiers provides a clean API to perform operations like $push, $inc, and $unset. Calling these methods performs an atomic operation on the database. In older versions of Mongomatic this used to force a reload. Thanks to some awesome work by Ben the modifiers now simulate the reload by updating the document in memory saving the extra call to the database.
Here are some examples of using modifiers:
class Post < Mongomatic::Base
def like
self.inc('likes', 1)
end
def add_comment(comment_data)
push('comments', comment_data)
end
end
class Item < Mongomatic::Base
def set_name(name)
self.set('name', name)
end
def clear_name(name)
self.unset('name', name) # self['name'] is now nil
end
def add_stats(*stats)
self.push_all('stats', stats)
end
end
Check out the Mongomatic::Modifiers module for more.
Typed Fields
At first I was going to write a set of expectations, like is_number, to validate field types. Ben wrote Typed Fields before I even got around to it and its a better implementation. Typed Fields are as close as you will see Mongomatic get to other Mongo ODMs but they are leaner and serve one clear purpose: communicating the underlying storage type in MongoDB. You have two options when the specifying the type. The first is to raise an error if the data is of the wrong type. The second, to cast the value to the underlying type. You can specify types for keys using Mongo’s dot syntax, as well.
class Rig < Mongomatic::Base
# :cast => true, :raise => false is the default
typed_field "age", :type => :fixnum, :cast => true
typed_field "manufacturer.name", :type => :string, :raise => false
typed_field "manufacturer.phone", :type => :string, :cast => false
typed_field "waist_measurement", :type => :float, :cast => true
typed_field "friends_rig_id", :type => :object_id, :cast => true
end
>> r = Rig.new(:age => "12")
>> r.valid? # notice that typed fields to not require them
>> r['age']
12
>> r = Rig.new
>> r['manufacturer'] = {'phone' => 12345}
>> r.valid?
# Raises Mongomatic::TypedFields::InvalidType
Casting to an :object_id will attempt to convert the string representation of a BSON::ObjectId, say from a URL parameter, and cast it to an instance of BSON::ObjectId.
Observers
Closer to ActiveRecord this time Mongomatic now provides observers, with its usual twist. After about an hour of back and forth Ben and I agreed that Mongomatic will do no observer auto-adding magic (I was originally for the magic). To get observable functionality on your collection class you must include the Mongomatic::Observable module. You can then add observers to the collection class using the observer macro, which takes either a Symbol or Class. Observers inherit from Mongomatic::Observable, although in reality this is not really necessary as Mongomatic::Observable is an empty class. However, you should inherit from this class as we may implement behavior here in the future. Observers can implement any of the callbacks that exist in your collection. You can also define custom notification handlers and use Mongomatic::Base#notify to trigger them.
Some trivial examples:
class Person < Mongomatic::Base
include Mongomatic::Observable
observer :MyCoolCallbacks
observer :MyOtherCoolCallbacks
def add_friend(id)
# do stuff
notify(:custom, friend_id: id)
end
end
class MyCoolCallbacks < Mongomatic::Observer
def after_insert(instance, opts)
puts instance.class.to_s # Person
p opts # {}
end
end
class MyOtherCoolCallbacks < Mongomatic::Observer
def after_insert(instance, opts)
puts "I'm called to. opts are always empty hash in common callbacks"
end
def custom(instance, opts)
puts instance.class.to_s # Person
p opts # { friend_id: 1234 }
end
end
Value for Key/Set Value for Key
If you would like to use MongoDB’s dot syntax to access and update your document’s in-memory data there are two methods you can use, Mongomatic::Base#value_for_key and Mongomatic::Base#set_value_for_key respectively.
>> r = Rig.new
>> r.set_value_for_key('address.street', 'Brennan')
>> r.insert
>> r = Rig.find_one(r['_id'])
>> r.value_for_key('address.street')
"Brennan"
Some Never Set in Stone Notes on the Future of Mongomatic
While adding observers and merging in some other changes Ben and I got into a pretty deep discussion about the philosophy of Mongomatic (minimalist, as no-fluff as possible) and somewhat discussed how things will continue. I’ll talk about some of that here.
Dot Syntax & the Hash API
Up until now the use of Mongo’s dot syntax has been implemented randomly. We have decided that any part of the API that mimics Ruby’s Hash API will work exactly the same. We will continue to develop a series of “sister” methods that you can use with the dot syntax. I just talked about the first two above. Although I am making these names up as we go, they are not part of the real API, and we didn’t really discuss them in detail yet, some other examples would be delete_value_for_key and values_for_keys.
The Meta-Gem?
This is not something we are not decided on but we are discussing building Mongomatic as a meta-gem, like rspec-2. This would take a considerable amount of work for us to manage but it would be very beneficial, in my opinion. The meta-gem would install a set of base gems. This would allow developers to stack features as needed keeping true to Mongomatic’s lean nature. Some of these base gems would include mongomatic-core, mongomatic-expectations, mongomatic-typed-fields and mongomatic-observable.
Minimal “Magic”
Ruby’s magic is awesome but sometime to the minimalist it is abused. I ended up agreeing to remove the observer auto-adding magic when Ben made the point that it can be much harder to track down a bug when you have fresh or stale eyes on the project. In Mongomatic you will see this philosophy constantly applied as we continue to improve the code. This does not mean you will see no magic (see Mongomatic::Expectations), just less.
Some Notes on mongomatic-rails3 and mongomatic-sinatra
Both these gems are lagging behind and I am trying to make time to update them. I don’t have many plans for these except to keep them in sync with Mongomatic core. One thing I would like to add to mongomatic-rails3 is support for typed fields in the model generator. If anyone has other ideas or would just like to let me help out send me a message on Github (link in the sidebar) and I will give you commit access.