When your web application accesses data in the model, it normally does it in couple of different contexts. You are either listing all available resources in a “summary” context, or you are listing the details of a single resource in a “detailed” context. I’m sure you’ll find cases where this is not true, but for the most part it holds true.
Lets take a Contact resource as an example:
-
-
class Contact
-
include DataMapper::Resource
-
property :id, Fixnum, :serial => true
-
property :display_name, String
-
property :first_name, String
-
property :last_name, String
-
property :work_phone, String
-
property :home_phone, String
-
property :email, String
-
# ..
-
# more properites
-
# ..
-
property :notes_updated, DateTime
-
property :notes_updated_by_id, Fixnum
-
property :notes, String
-
end
-
You build a RESTful interface to show your new contact resource. Lets say our interface includes:
- /contacts
- /contacts/1
- /contacts/1/notes
This gives you 3 contexts for data access (”summary”, “detailed” and “notes”). Every time someone goes to the /contacts URL your ORM retrieves all the data in the contacts table, maps it into Contact instances and returns an array of these instances. Then you do something like this:
-
-
# In your controller
-
@contacts = Contact.all()
-
-
# And in your view something like
-
<% @contacts.each do |contact| %>
-
<tr>
-
<td><%= contact.display_name %></td>
-
</tr>
-
<% end %>
-
What a waste! Your ORM loaded 10+ fields from the database. Mapped them to variables in memory and then you only used contact.display_name (maybe contact.id). No problem, your ORM is smart, it can lazy load. So you do something like:
-
-
class Contact
-
include DataMapper::Resource
-
property :id, Fixnum, :serial => true
-
property :display_name, String
-
property :first_name, String, :lazy => true
-
property :last_name, String, :lazy => true
-
property :work_phone, String, :lazy => true
-
property :home_phone, String, :lazy => true
-
property :email, String, :lazy => true
-
# ..
-
# more properites
-
# ..
-
property :notes_updated, DateTime, :lazy => true
-
property :notes_updated_by_id, Fixnum, :lazy => true
-
property :notes, String, :lazy => true
-
end
-
That works great. When a user list all your contacts, the ORM only pulls out the id and display_name field. Now a user hits /contacts/1 URL.
The problem is your detailed view is a bit more verbose and outputs many more properties. All the lazy properties are loaded on first access. This means one query per lazy property read. Not great. Whats worse is if another part of your code does a Contact.all() and then iterate over the array accessing lazy properties.
The solution. In the soon to be release 0.9.0 of DataMapper lazy loaded properties are going to have contexts. When you read the first lazy property in a context, all the properties in that context for all the instances are loaded in one query. You can do something like this:
-
-
class Contact
-
include DataMapper::Resource
-
property :id, Fixnum, :serial => true
-
property :display_name, String
-
property :first_name, String, :lazy => [:detailed,:notes]
-
property :last_name, String, :lazy => [:detailed,:notes]
-
property :work_phone, String, :lazy => [:detailed]
-
property :home_phone, String, :lazy => [:detailed]
-
property :email, String, :lazy => [:detailed]
-
# ..
-
# more properites
-
# ..
-
property :notes_updated, DateTime, :lazy => [:notes]
-
property :notes_updated_by_id, Fixnum, :lazy => [:notes]
-
property :notes, String, :lazy => [:notes]
-
end
-
– until next time –