Adam Speaks Out Of Turn

Moving On Up (from DataMapper 0.3.x to 0.9)

Lessons Learned During an Upgrade

Posted on 2008/05/01

This entry reflects an older version of a library or code samples that may have expired since the time of it’s writing. Investigate the library or code samples further before use.

I just finished up a solid 24-hour run on upgrading an application from DataMapper 0.3.0 to DataMapper 0.9. Things were rough for a little while, but overall the application came through in flying colors. I actually ended up fighting more with the web framework upgrade than the ORM upgrade.

Here’s some quick notes on some of the things that I had to change when it comes to syntax alterations, association changes, and the like.

No More Inheriting

You get so used to seeing DataMapper::Base or ActiveRecord::Base in your class definitions that you take it for granted and expect all ORMs to work that way. Not so with DataMapper 0.9. You must have include DataMapper::Resource as the second line in your model, rather than as the inheritance.

Properties now accept real Classes as types

This one’s pretty minor but has a cool ramification. In 0.3.x, you would specify property :name, :type, {opts}, but now that 0.9 supports passing classes around as types, you can do:

1 property :name, String, {opts}

By default, DM 0.9 supports the following classes:

If you include the dm-types gem, you get a few more:

If you ever find that none of these classes fit quite right, you can write your own. If it responds to #load and #dump and inherits from one of the DataMapper::Types primitives, it can be used as a property type. The sky is the limit.

No more automatic keys

With 0.3.x, DataMapper would assume a primary key of :id if you didn’t specify one. With 0.9, that no longer happens. It’s up to you to specify a primary key (or composite key). If you don’t specify a key, you can’t retrieve something from the data-store, which kind of sucks.

Thankfully, 0.9 still supports the :key => true option you pass to property, so specifying which properties are keys is really dang easy and doesn’t require much of a change of syntax.

If you relied on the automatic :id key, you’ll need to go back and add it.

1 property :id, Fixnum, :serial => true

Validations are now opt-in

Validations aren’t “baked right in” with DataMapper 0.9 as they were in 0.3.x and in ActiveRecord. Therefore, to opt in to validations, make sure that you have “dm-validations” from “dm-more” installed and loaded, then you add include DataMapper::Validate as the third line of your model definition after the class call and the include DataMapper::Resource statement.

There are some minor syntactical differences between the validations of DM 0.3.x and 0.9. For example validates_numericality_of becomes validates_numericalnes_of [sic]. I’m hoping someone fixes the spelling error in that method name…or comes up with better terms than “numericality” or “numericalness”.

The application I upgraded doesn’t have alot of validations, so I never ran into a problem other than validates_numericalnes_of.

Associations have gotten sexy

DataMapper 0.9 has all but given up on the conventions set by ActiveRecord when it comes to defining associations between models. Gone are:

They’ve been replaced with some ruby DSL sugar:

The new has operator can even take range objects or :min and :max options as well. You could very easily have a model that has 1 to 3 objects in its’ association and DataMapper would enforce the requirement for at least one and at most 3. (I was tempted to quote a Monty Python sketch here, but couldn’t quite work in the Holy Hand Grenade of Antioch bit.)

Something of note that will likely change as 0.9 matures, has doesn’t take the usual options you might expect. For example, supplying an :order isn’t supported yet, nor is :join_table.

Has And Belongs to Many

At the time of this writing, has n..n isn’t yet implemented, but will be when Sam or one of the other freakishly smart people in #datamapper get some time to devote to it.

To get around it, simply write the has n..n line in a comment, create a temporary model representing your join table, and then manually implement the association:

 1 class Post
 2   include DataMapper::Resource
 3   # properties here
 4   
 5   # has n..n, :categories
 6   def categories
 7     rels = CategoriesPost.all(:post_id => self.id).map {|c| c.category_id}
 8     rels.empty? ? [] : Category.all(:id => rels)
 9   end
10 end
11 
12 class CategoriesPost
13   include DataMapper::Resource
14   property :category_id,  Fixnum, :key => true
15   property :post_id,      Fixnum, :key => true
16 end

Yes, it’s fugly, but it’ll get you post.categories while the work is being done on has n..n.

Callbacks are now aspect-oriented

This is probably the hottest change between 0.3.x and 0.9 that I’ve encountered. Gone are before_save, before_create, and it’s ActiveRecord-inspired buddies. Say hello to before, after, before_class_method and after_class_method.

1 class Post < DataMapper::Base
2   # properties here
3   before_save :some_method
4   
5   def some_method
6     # does something
7   end
8 end

Becomes…

 1 class Post
 2   include DataMapper::Resource
 3   # properties here
 4   
 5   before :save, :some_method
 6   
 7   def some_method
 8     # does something
 9   end
10 end

Sure looks like all they did was change before_save :some_method to before :save, :some_method, but underneath you’ve got full aspect-oriented callbacks ready and waiting. You’re no longer restricted to only wrapping the save, create, destroy, and materialize methods now. You could very easily wrap a callback around a custom method you defined, if you like.

The before_class_method and after_class_method are used to wrap callbacks around any class methods like create, all, or first.

Truly truly powerful new feature of DataMapper 0.9.

Finder syntax changed a little bit

A couple of minor changes to the finder syntax crept into 0.9. To manually query the data-store, you no longer do database.query or database.execute and instead talk directly to the adapter of the repository context you want to work with: repository(:default).query and repository(:default).execute.

Also of note is a change in how you specify :order. Rather than passing in the “order phrase” you want dropped into the Query, you now specify the property with a vector. For example :order => 'created_at desc' becomes :order => [:created_at.desc]. It’s expecting an array of properties with vectors, so you can’t just up and pass the property-vector without wrapping it in an array, but you can pass more than 1 property to sort by.

Specifying the order using :property.vector lets the adapter assigned to your repository properly figure out how to represent the field in your data-store.

That’s pretty much it

Those were the major hurdles I faced moving a DataMapper 0.3.x domain into DataMapper 0.9. As usual, your mileage may vary on any included code-samples. Keep in mind that things have likely changed since the time of writing of this post, so the workarounds I described above may not be needed anymore. Enjoy!

Help me properly attribute the original picture from which the sidebar was created. If you know where the original picture is, contact me and I will properly attribute it.