An Argument for Datamapper—Properties
Properties, Migrations, Rspec expectations
DataMapper only knows about the columns on the table that you tell it about. It’ll never select more than it knows about, nor will it ever update anything it doesn’t know about. This makes DataMapper play nice with other systems accessing the same database. (You are planning for a time when your site grows into a system of sub-systems, right?)
In a sense, DataMapper starts stupid and you, the programmer, make it smarter as your needs grow.
Activerecord, on the other hand, starts clever and gets dumber as things get more complicated.
Take column definitions, for instance. With DataMapper, you’ll find writing property :column, :data_type over and over again a little annoying….but with Activerecord, you also had to do so, but in the form of external migrations or schemas. Just because your db schema is spread out amongst migrations, you STILL had to write the same amount of code.
To avoid having to flip between schema or DB GUI and your model defintion, you likely copied portions of your schema.rb into comments in the rdocs for your Activerecord model, right? Hell, I even bet you spaced it out so that the data types were all lined up and the extra options you may need all started on the same column of your text editor:
1 # create_table "courses", :force => true do |t| 2 # t.column "short_title", :string 3 # t.column "min_credit", :float 4 # t.column "max_credit", :float 5 # t.column "department_id", :integer 6 # t.column "number", :integer 7 # t.column "title", :string 8 # t.column "description", :text 9 # t.column "general_ed", :boolean 10 # end 11 class Course < Activerecord::Base 12 ....
If you ever issue a new migration and change up the DB structure, this documentation will need updating too. Without this, you’ll need to occasionally peer at your DB’s GUI tool or schema.rb so that you can refresh your memory as to what attributes your model has. Peering which takes your eyes off of Ruby code…peering, which interrupts your concentration and makes you switch gears to think in “database” instead of in Ruby.
Your Activerecord models contain everything except for what they look like. They get that by introspecting their own table and tacking on accessors for each column they find. Is your model defined all in one place? nope. You’ve got your class and instance methods tucked away in app/models and your property-to-table-column mappings chillin’ in db/schema.rb.
Having properties defined inside your models will consolidate your model’s table definition into your model’s ::cough:: definition…which makes sense in a “Domain Modeling” kind of a way. The whole point of a DataMapper is to be that translation layer between a real honest-to-god native Object and a relational database.
If you ever end up needing to chunk a column for one reason or another, just comment out (or even delete) the property line. Sure, this won’t actually drop the column from your table, but because DataMapper only knows about the columns you tell it about, values in that column won’t be tacked onto your instantiated models. Out of sight, out of mind. When you next auto_migrate, the column will be gone.
I should make note, however, that auto_migrate is NOT for use on a production database. It will literally drop your tables completely and then redefine them according to your property list. You are backing up your production database, right?
Migrations and when NOT to use them
This is not to discount the value of Migrations as a concept; Its just that they’re not particularly useful outside of shuffling data around between DB structures…and you only really need to do that in a production environment. If your using rspec or test-unit, you’ll shouldn’t even need to have real solid data in your development or testing database (if your testing correctly, that is). Blowing away and then recreating your database with each run of your specs is completely safe and sane, it just doesn’t feel right.
To ween yourself off of data in your development database, read up on rspec’s Mocks and Stubs and use them to intercept calls to your database. For example, here’s a quick spec for letting a user specify his preference for how many photos per page he’d like to see, storing it in the Session, and then pulling out photos accordingly.
1 describe Application, "scoping viewable content by a user-set preference" do 2 it "should find photos according to a setting in Session" do 3 Photo.should_receive(:all).with({:limit => 10}).and_return([Photo.new]) 4 5 @app.session = {:per_page => 10} 6 7 @app.photos.each do |photo| 8 photo.should === Photo 9 end 10 11 end 12 end 13
By using Rspec’s Mocks and their.should_receive method, we’re setting an expectation that Photo.all should get called with certain arguments. We aren’t testing that the ORM layer can issue the query correctly and materialize photos, we’re testing that our .photos() method off of our Application controller generates the correct call to the ORM layer. No need for fixtures in the database at all.
Conclusions
In summary, having your model’s table definition separate from your model’s definition means you’ll be flipping back and forth between your DB GUI tool and your Ruby code over and over again, and it means that your model must introspect the database to find out what it looks like. This is silly; your model should already know what it looks like. When you give up on the idea that you NEED fixtures in your database in order to properly develop or test and use Mocks and Stubs the way they were designed to be used, you stop even needing to have a database setup at all, much less that you have data in it.
To sum things up even further, you should switch to DataMapper ;-)