Moving On Up (from DataMapper 0.3.x to 0.9)
Lessons Learned During an Upgrade
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:
- TrueClass (boolean)
- String
- Text
- Float
- Fixnum
- BigDecimal
- DateTime
- Date
- Object (gets marshaled)
- Class (for Single Table Inheritance, stored same as String)
If you include the dm-types gem, you get a few more:
- Csv
- Enum
- Flag (first, second, third, that sort of thing)
- Yaml
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:
- has_many
- has_one
- has_and_belongs_to_many
They’ve been replaced with some ruby DSL sugar:
has norhas 0..nhas 1has n..n(still tentative)
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!