How to Do Things With Datamapper
Basics and not-so-basics for using DataMapper
Tables without primary keys
If you’re working with a table that doesn’t have a :id column, you can declare your properties as you usually do, and declare one of them as a natural key.
1 property :name, :string, :key => true
You should now be able to do Class['name_string'] as well. Remember: this column should be unique, so treat it that way. This is the equivalent to using set_primary_key in ActiveRecord.
Be Paranoid
1 property :deleted_at, :datetime
If you’ve got deleted_at, your model is paranoid auto-magically. All of your calls to #all(), #first(), and #count() will be scoped with where deleted_at is null. Plus, you won’t see deleted objects in your associations.
Single Table Inheritance
1 class Person < Datamapper::Base 2 property :type, :class 3 # other shared properties here 4 end 5 6 class Salesperson < Person; end
You can claim a column to have the type :class and DM will automatically drop the class name of the inherited classes into that column of the database.
Class Table Inheritance is on the drawing board and everyone’s drooling over it. Don’t be surprised if you see it in this quicky how-to soon.
Self-referential HABTM
1 class Task < DataMapper::Base 2 has_and_belongs_to_many :tasks, 3 :join_table => "task_relationships", 4 :left_foreign_key => "parent_id", 5 :right_foreign_key => "child_id" 6 end 7
I highlight this because of its clarity. With ActiveRecord, you have to specify the :foreign_key and the :association_foreign_key, both of which are pretty vague as to what’s going on when the JOIN happens.
Find_By_Sql
1 database.query("select * from users where clue > 0")
This does not return Users, but rather Struct’s that will quack like Users. They’ll be read-only as well.
DIY database execution
1 database.execute("update users set active = false")
Why not use the database.query to do this? Because database.query assumes you’re expecting a result set back and will take the database response and attempt to create an object or set of objects out of it. Doesn’t necessarily sounds like a bad thing, until you realize that some most database servers don’t return anything when your command was successful. Nothing went wrong, so why bother? database.query assumes a result set and gets confused.
Batch-process a truck-ton of records
1 User.each(:performance_rating => "low") do |u| 2 u.employment_status = "fired" 3 u.save 4 end
With ActiveRecord, doing a User.find(:all).each{} would execute the find, instantiate an object for EVERY result, THEN apply your transformations to each object in turn. Doesn’t sound too horrible unless you have a TON of records; you WILL grind your system to a screeching and bloody halt.
Datamapper’s #each works in sets of 500 so the amount of objects instantiated at a time won’t make your computer think it’s a victim in a Saw movie. Once it’s done executing your block on the first set of 500, it moves on to the next.
What’s more is #each is secretly a finder too. You can pass it an options hash and it’ll only iterate on 500-item sets matching your query. Don’t send it :offset though, because that’s how it pages. You can overload the page size by sending it :limit
Viewing Query Logs
1 --- 2 # This is database.yml 3 :development: &defaults 4 :adapter: postgresql 5 :database: project_development 6 :username: postgres 7 :password: 8 :host: localhost 9 :log_stream: log/sql.log 10 :log_level: 0 11
By supplying the log_stream your telling DM what file you want to see your sql logs in. log_level is the Logger level of output you want to see there. 0, in this case, says that you want to see all messages sent to the logger. For more information how to work with Logger, hit up http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/.
Incidentally, if you’d like to send a message into the Datamapper logger, do:
1 database.adapter.logger.info "your message here"