Every so often at Songkick we have what we call innovation days – a day or two where everyone can work on whatever they like to make our product, technology or workplace better. We did one this last Friday, and I made a start on something I’ve been wanting to do for a long time: it’s called Primer.
This year a couple of development platforms have sprung up that make doing real-time applications considerably easier: LunaScript and Fun. They both aim to let you write a web app by writing your templates in the usual way, but then they deal with updating the client and synching state for you. From the Fun intro:
When you say
"Hello " user.name
, you don’t mean just “render the value ofuser.name
right now”. Rather, you mean “display the value ofuser.name
here, and any timeuser.name
changes update the UI.
Fun will update your UI in real-time, keystroke by keystroke. I can’t do that yet, but I can do something similar: automatically update caches when your ActiveRecord model changes. Consider:
<% primer '/users/1/name' do %>
<p><%= @user.full_name %></p>
<% end %>
This is just wrapping part of a template in a caching block, much like Rails
fragment caching. Now, surely your development tools ought to be able to figure
out that the cache key /users/1/name
depends on the full_name
attribute of
@user
, right? It says so right there. So this is what Primer does: it
caches your HTML fragments and figures out which bits of your database they
depend on. When your database changes, it updates the cache. No sweeper code, no
combing through call stacks, no dealing with message buses.
Now it turns out that using paths as cache keys has two really nice side effects. Firstly, you can use a declarative routing scheme to tell Primer how to calculate values:
Primer.cache.routes do
get '/users/:id/name' do
user = User.find_by_id(params[:id])
user.full_name
end
end
This means that, by moving some rendering logic out of your templates, you can tell Primer how to regenerate cache keys, so it can update the cache with new data instead of just invalidating it. This can be useful for long-tail pages that don’t get much user traffic but need to be fast for the Googlebots.
Secondly, it means that all your cache keys are valid Faye channel names, and can be used to update the client in real time. If you put this in your view:
<%= primer '/users/1/name' %>
Primer will use the '/users/:id/name'
route to generate the value, cache it,
and also will update any clients viewing the page when that cache slot is
updated. You don’t need to write any of the network code for this, it’s all done
for you, so that template fragment gets you real-time updates for free.
Right now it’s very much a proof-of-concept, just to show that this sort of
thing is possible. It needs to store a lot of data about your model to do its
job, and needs to perform a lot of ugly reflection on ActiveRecord to
propagate changes correctly. It still probably screws up a lot of edge cases,
most glaringly it can’t figure out when a has_and_belongs_to_many
collection
changes. It can figure out a lot of common has_many
/belongs_to
/has_many
:through
relationships. I have taken some steps to ease the load by making it
really easy to create background workers to update your caches, but it’s
definitely not for production.
I’ve rambled on enough, so if you want to poke at a half-baked week-old project
I’d really like feedback on whether this can be turned into a viable bit of
production software. Just gem install primer
or get it on GitHub.