Checking Out DataMapper

Posted by teem on March 27, 2008

I’ve been itching to learn Merb and DataMapper for so long since this two kinda rocked the Ruby world but I just didn’t have enough motivation to really dive in. But with Merb nearing the v1.0 release (v0.9.2 at the time of this writing), I guess it’s about time.

To start-off my journey in the world of Merb, I decided to try DataMapper. Merb is ORM agnostic so I can use ActiveRecord, but DataMapper is also worth learning and using, primarily because I love learning and because its faster than AR, allows lesser SQL coding, and it’s familiar. It also reminds me of the Django ORM.

Since it’s the first time that I’ll be doing DataMapper stuff, I was not sure where or how to start. I headed to the DataMapper site and after a little bit of reading and coding, I thought of creating a very simple Ruby program that uses DataMapper.

I decided to model a family tree. A family tree is a collection of persons with relationships ordered like a tree. Parents have children, persons have siblings or cousins, etc. I chose this because it tackles two basic topics: attributes/properties and associations.

Adding Properties

Being the BDD fan that I am, I start projects with the creation of RSpec examples. The examples themselves are the code that test some features of DataMapper.

The following set of specs describes some attributes of the Person object. It’s kept simple to focus on the two attributes of the Person class: name and date of birth.

teem_attributes = { :name => "Teem",
                    :date_of_birth => Date.civil(1974, 1, 1)}

describe Person do

  before(:each) do
    @person = Person.new(teem_attributes)
  end

  it "should be valid" do
    @person.should be_valid
  end

  it "should have a name" do
    @person.name = nil
    @person.should_not be_valid
  end

  it "should have been born on some date" do
    @person.date_of_birth = nil
    @person.should_not be_valid
  end

end

Now, to the code.

Connecting to the Database

To setup the database for the whole application

DataMapper::Database.setup({
  :adapter => "sqlite3",
  :database => "family.sqlite3"
})
Creating the Person class

And the Person object is simply this:

class Person < DataMapper::Base
  property :name, :string, :nullable => false
  property :date_of_birth, :date, :nullable => false
end
Accessing Properties Within the Person Class

I wanted to know how to access the attributes within the class so I added a method #age.

describe Person, "#age" do

  before(:each) do
    @person = Person.new(teem_attributes)
  end

  it "returns the number of years since the date of birth" do
    @person.age(Date.civil(2008, 3, 1)).should == 34
  end

end

I opened up the class Person and added #age. Attributes of an DataMapper object are simply instance variables.

class Person

  def age(date=Date.today)
    y = ((date.year - @date_of_birth.year)).to_i
    y -= 1 if (date.month < @date_of_birth.month) || (date.month == @date_of_birth.month && date.day < @date_of_birth.day)
    y
  end

end
Finding Persons
describe Person, ".first tries to find a person" do

  describe "who is in the database" do

    before(:all) do
      Person.create(teem_attributes)
    end

    it "should be found using the name condition" do
      teem = Person.first :name => "Teem"
      teem.name.should == "Teem"
    end

    it "can use LIKE in finding" do
      eem = Person.first :name.like => "%eem"
      eem.name.should == "Teem"
    end

  end

  describe "who is not in the database" do

    it "should return nil" do
      Person.first(:name => "Juan").should be_nil
    end

  end

end

The first method finds the first object that satisfies the conditions. What’s cool about finding stuff in DataMapper is it really is possible to not code SQL. :name.like is sweet, pretty much like Django’s field lookups.

This is fun! So I tried gt and lt on date_of_birth attribute.

# Let's try gt and lt on dates!

describe Person, ".all to find all persons born before, after, or on a specific date" do

  # at this point, teem person is already in the database

  before(:all) do
    @teem = Person.first :name => "Teem"
  end

  it "should return an array that does not include teem when looking for persons born before Jan 1, 1972" do
    d = Date.civil(1972, 1, 1)
    Person.all(:date_of_birth.lt => d).should_not include(@teem)
  end

  it "should return an array that includes Teem when finding persons born after Jan 1, 1972" do
    d = Date.civil(1972, 1, 1)
    Person.all(:date_of_birth.gt => d).should include(@teem)
  end

  it "should return an array that includes Teem when finding persons born on Jan 1, 1974" do
    d = Date.civil(1974, 1, 1)
    Person.all(:date_of_birth => d).should include(@teem)
  end

end

Is not this cool?

Okay. This post is long enough. Too long, in fact. I’ll checkout DataMapper associations next time.