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.