In the rankers app that I’ve recently finished for a client, I cached the users data offline, but needed a way to quickly and easy to see if the user had the freshed version of the data.
Downloading database updates
To speed up loading time, and enable offline access, I download the list of places (about 350kb) by an ajax call, and then cache the json and the etag of the latest revision in localStorage
. Then when the app launches next time, I send a request for the newest version of the places list, and attach the etag to the request, so that rails can send a not-modified response, and save the users some data.
Here’s the rails pseudocode code I used:
@places = Proc.new do
Place.find(:all)
end
response.etag = @etag = [
Place.find(:first, :order => 'updated_at desc').updated_at.to_i,
Place.count
]
if request.fresh?(response)
head :not_modified
else
render :action => 'index', :mime_type => 'application/json'
end
And in the view:
<% cache(@etag) do %><%= @places.call.to_json %><% end %>
This cached the json (since json generation can be pretty slow in ruby), and sends the appropriate header. To handle this on the client side, I used something like this, to cache the places data, and the etag.
$.ajax {
url : "http://example.com/some/endpoint"
dataType : 'json'
headers : { 'If-None-Match' : localStorage.getItem('etag') }
success : (data, textStatus, xhr) =>
if textStatus == "success"
localStorage.setItem('etag', xhr.getResponseHeader('Etag'))
localStorage.setItem('placesData', JSON.stringify(data))
else if textStatus == "notmodified"
data = JSON.parse(localStorage.getItem('placesData'))
else
throw "your toys"
Places.reset(data)
}
I was going to implement my own version of etags and if-none-match
, since I thought I might get heisenbugs with different implementations of xmlhttprequest on different mobile webkits, but in the end, I decided not to reinvent the wheel and use what already exists. So far this has worked well.