Lazy Loading Properties in DataMapper

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:

  1.  
  2.   class Contact
  3.     include DataMapper::Resource
  4.     property :id, Fixnum, :serial => true
  5.     property :display_name, String
  6.     property :first_name, String
  7.     property :last_name, String
  8.     property :work_phone, String
  9.     property :home_phone, String
  10.     property :email, String
  11.     # ..
  12.     # more properites
  13.     # ..
  14.     property :notes_updated, DateTime
  15.     property :notes_updated_by_id, Fixnum
  16.     property :notes, String
  17.   end
  18.  

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:

  1.  
  2.   # In your controller
  3.   @contacts = Contact.all()
  4.  
  5.   # And in your view something like
  6.   <% @contacts.each do |contact| %>
  7.     <tr>
  8.       <td><%= contact.display_name %></td>
  9.     </tr>
  10.   <% end %>
  11.  

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:

  1.  
  2.   class Contact
  3.     include DataMapper::Resource
  4.     property :id, Fixnum, :serial => true
  5.     property :display_name, String
  6.     property :first_name, String, :lazy => true
  7.     property :last_name, String, :lazy => true
  8.     property :work_phone, String, :lazy => true
  9.     property :home_phone, String, :lazy => true
  10.     property :email, String, :lazy => true
  11.     # ..
  12.     # more properites
  13.     # ..
  14.     property :notes_updated, DateTime, :lazy => true
  15.     property :notes_updated_by_id, Fixnum, :lazy => true
  16.     property :notes, String, :lazy => true
  17.   end
  18.  

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:

  1.  
  2.   class Contact
  3.     include DataMapper::Resource
  4.     property :id, Fixnum, :serial => true
  5.     property :display_name, String
  6.     property :first_name, String, :lazy => [:detailed,:notes]
  7.     property :last_name, String, :lazy => [:detailed,:notes]
  8.     property :work_phone, String, :lazy => [:detailed]
  9.     property :home_phone, String, :lazy => [:detailed]
  10.     property :email, String, :lazy => [:detailed]
  11.     # ..
  12.     # more properites
  13.     # ..
  14.     property :notes_updated, DateTime, :lazy => [:notes]
  15.     property :notes_updated_by_id, Fixnum, :lazy => [:notes]
  16.     property :notes, String, :lazy => [:notes]
  17.   end
  18.  

– until next time –

2 Responses

  1. Adam (afrench) Says:

    Whenever I request the `email` property, it would load up all of the other properties marked :detailed…..and `first_name` and `last_name` get loaded with that.

    Does DM then trigger the load of properties with the :notes context now too, since `first_name` was loaded with :detailed?

    I guess my question is ‘Does lazy-loading context chain loading of multiple contexts if a property is assigned to more than one lazy-context?’

  2. guyvdb Says:

    Adam, when you access a lazy property, it is loaded along with all other properties in contexts shared with the contexts of the property you are accessing. So yes, they do chain.

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.