-
Migrations with Mongo (and MongoMapper)
Posted on February 26th, 2010 7 commentsI’m working on an application (which you’ll get to see soon) with another guy, and he introduced some timestamps to a MongoMapper model after some of those models had already been coded. As a result, the first time I tried to visit a page with my user, I got a sweet NoMethodError, because apparently nil doesn’t implement strftime.
There are lots of ways to solve this problem, but it’s one that doesn’t arise nearly as frequently on SQL databases (I hypothesize…) because you have migrations where you can set default values for the schema.
When we decided to write our new app in Mongo, one thing that worried me would be data transforms down the road. How do we keep up with those? Do we just create rake tasks and run each one, noting them somewhere in a textfile? That sounds kind of awful:
8.3
* run rake:transform_user_emails to downcase emails
* run rake:default_user_nicknames8.4
* run rake:user:canonicalize_phone_numbersThis gets out of hand in a hurry.
Anyways, in my MO of looking on the first page of Google, not finding my answer, and writing something that does mostly what I want, I give you Mongrations.
Before I say anything else, I’ll tell you that I would love forks and fixes and bug findings and I know I need to write tests – but I’m pretty sure no matter what anyone does, Mongration is going to stick.
If you install the plugin (it needs to be a MongoMapper backed application), and change your vocabulary to use “mongration” and “mongrate” instead of “migration” and “migrate,” respectively, you have pretty much what you had with ActiveRecord’s migrations, only instead of schema changes, you just manipulate data.
I think this is actually a fairly pure solution. While there were problems and gotchas with integrating schema changes and data changes in the same set of migrations, I think MongoMapper skirts that with the schema being solely defined in the models.
It’s entirely possible I’m overlooking something ridiculously easy, so feel free to tell me as much if you think so.
7 responses to “Migrations with Mongo (and MongoMapper)”

-
Your post has been linked at http://www.DrinkRails.com!
-
As a result, the first time I tried to visit a page with my user, I got a sweet NoMethodError, because apparently nil doesn’t implement strftime.
…Or your partner could have added a :default => value on any new keys he created in the model, which in Does the Right Thing in MongoMapper and takes the place of “nil” for any existing records.
This is a neat idea, but I don’t see much use for a whole new layer of process in a typical MongoMapper app deployment. MM can handle the uncomplicated cases very elegantly with defaults and callbacks. For the complicated cases where data does need to be transformed, you haven’t really removed the overhead of having to remember to run a Rake task. That’s not hard for a typical small project, and if your development is incremental and additive, changes that warrant it shouldn’t be that common. If I had a big team or big production app farm where I’d have to worry about more than a couple of installations, I’d probably roll the transformation script into an initializer in the app. That way I could be sure it was done before the code that needed it.
Rollbacks are sort of a silly idea in MongoMapper. Again, a data transformation that breaks old code is just so rare. They were a silly idea in ActiveRecord migrations too — I’ve never seen a production snafu where a migration rollback was part of the recovery. So much coding time goes into them, and yet it’s so much more reliable just to back up the database as part of a deployment and then restore from it if things explode.
-
My point about defaults was just that they’ll help you avoid the dreaded NoMethodError. They’re obviously no substitute for having actual data.
As it happens, I have run into the name-splitting issue in the recent past, thanks to having to integrate with a braindead membership database. I handled it by overriding the read accessors to figure it out from the Name field when the more specific ones weren’t filled in. I even tried to handle middle initials (our audience is academics, so this matters):
# An intelligent guess at first name where none exists
def first_name
attributes[:first_name] || name.split[0]
end# An intelligent guess at middle initial where none exists
def middle_name
attributes[:middle_name] || (((a = name.split($;,3)).length == 3) ? a[1] : nil)
end# An intelligent guess at last name where none exists
def last_name
attributes[:last_name] || name.split($;,3)[-1]
endThis isn’t perfect, but it’s a decent 80% solution, and the user can fix any mistakes themselves in their profile. Querying’s not really a problem; you just do a regex query into the complete Name field, which will have all the components.
It’s just given as part of most deployments today anyway that you’ll need to migrate your database,
But some of us started using MongoDB in part to get away from that. >8->
-
Oh, and hey — for what it’s worth, it really wasn’t my intention to be a hater here. I’m playing Devil’s Advocate to some extent, but I don’t actually think this is a bad idea. We have different approaches, but that’s not a Bad Thing. And the implementation looks decent. (You do need tests, though.) >8-P
I can particularly see how this could be a handy helper for someone just transitioning into MongoMapper from more ActiveRecordish approaches, or even for an existing Rails project that’s starting to move some models into Mongo but not all at once.
So please don’t read my comments above as trying to say “This is stupid and you should stop.” If I meant that, that would be stupid. And I should stop.
-


Sohan February 28th, 2010 at 03:03