Fragment caching with lambda's

Here’s some commonly seen Rails controller code:

Your controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class PostsController < ApplicationController

  def index
    @posts = get_posts

    respond_to do |format|
      format.html
      format.js  { render :json => @posts}
      format.xml { render :xml  => @posts}
    end
  end

  private

  def get_posts
    # some complex query that
    # returns some posts, say...
  end

end

Technically the #get_posts method should be in the Post model,
but bear with me, I’m trying to prove a point.

index.html.erb

1
2
3
4
5
<h2>Posts!</h2>

<% @posts.each do |post| -%>
  <h3><%=link_to(post) %></h3>
<% end -%>

So that’s pretty standard. The site’s been doing great and everyone who is
anyone is checking out the index page!

Unfortunately your server is getting steadily dragged down and after
some profiling you realize that the main bottleneck is the #get_posts
method.

Not all is lost, you’ve heard that caching can help! Sadly the rest of your
page (that is, various bits of your layout) is quite dynamic and cannot be
cached globally so page and action caching isn’t going to help.

Luckily Rails supports fragment caching which allows you to cache a specific
bit of ERB. So your view becomes:

index.html.erb

1
2
3
4
5
6
7
<h2>Posts!</h2>

<% cache do -%>
  <% @posts.each do |post| -%>
    <h3><%=link_to(post) %></h3>
  <% end -%>
<% end -%>

So that looks great, you profile again and notice that from the second
request onwards, the server is indeed breathing a little easier…but
not nearly as much as you expected.

The reason is that the database queries are still being run in ‘#getposts’,
and the posts assigned to @posts. The fragment caching just skips looping
through the @posts array, replacing the entire ‘cache do … end’ block
with the HTML that got generated during the previous request.

This leads people to unhappy action like this:

The associated helper

1
2
3
4
5
6
7
8
module PostsHelper

  def get_posts
    # some complex query that
    # returns some posts, say...
  end

end

Your controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class PostsController < ApplicationController

  def index
    respond_to do |format|
      format.html
      format.js  { render :json => get_posts}
      format.xml { render :xml  => get_posts}
    end
  end

  private

  def get_posts
    # some complex query that
    # returns some posts, say...
  end

end

index.html.erb

1
2
3
4
5
6
7
<h2>Posts!</h2>

<% cache do -%>
  <% get_posts.each do |post| -%>
    <h3><%=link_to(post) %></h3>
  <% end -%>
<% end -%>

Don’t get me wrong, this works. Also, in this rather simple case you can
use Rails’ #helper_method function to remove the duplication.

However, something worth looking at when you run up against more involved
problems of this kind is to use lambda’s for lazy evaluation in the view:

Your controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class PostsController < ApplicationController

  def index
    @posts_finder = find_posts

    respond_to do |format|
      format.html
      format.js  { render :json => @posts_finder.call}
      format.xml { render :xml  => @posts_finder.call}
    end
  end

  private

  def find_posts
    Proc.new do
      # some complex query that
      # returns some posts, say...
    end
  end

end

index.html.erb

1
2
3
4
5
6
7
<h2>Posts!</h2>

<% cache do -%>
  <% @posts.call.each do |post| -%>
    <h3><%=link_to(post) %></h3>
  <% end -%>
<% end -%>

That way @posts is lazily evaluated and you’ve cleanly removed your
bottleneck.

Let me know what you think!

Gustav

Use Babushka to set up an Ubuntu 10.04 Server to run a Rails app

UPDATE: These instructions no longer work. Babushka went through a growth spurt and lots of breaking changes were made. I decided to wait for the project to settle down a bit before updating this post. I believe it is stabilizing so I’m going to update this soon. Be sure to check back here within the next week!

Basic setup

Most of this is copied from the Zero to Hero with Babushka screencast. Watching it is highly recommended
before you start fiddling with Babushka!

For this tutorial I assume that you’ve intalled a bare bones Ubuntu 10.04 Server and you’re logged into your user account.

First we’re going to set up babushka itself:

1
bash -c "`wget -O - babushka.me/up`"

This will bootstrap Babushka and proceed to install ruby, irb and various other useful packages.
When prompted for whose deps you’d like to install, enter ‘gpaul’.

Next we’re going to have Babushka do some base system config by installing ssh, curl, htop and some other apps that come in handy at times:

1
babushka system

Application setup

From here onwards it’s all up to you. You can install mysql, postgres, nginx, etc. You can also add more babushka-deps from github if you see
something you like or fork Ben Hoskings’ Babushka-Deps repo and add your own deps.

For this tutorial I’m going to use Ben’s Nginx and Postgres deps and get a sample rails app (Enki) going.

Ben uses an interesting approach: he sets up a user account for the app named after the domain. Sounds pretty cool so let’s go that route.
Remember: when asked for your username, enter ‘your-domain-name.tld’ (eg. enkiblog.com) Also, when asked for home dir base enter ‘/var/www’ or ‘/src/http’, whichever one you prefer to host your apps out of.

1
babushka 'user exists'

Set up a password for your new user:

1
sudo passwd your-domain.tld

Now become the new user:

1
su - your-domain.tld

Sweet! Now for some real action. We’re going to set up a git repo our app is going to reside in at. When asked, I put it in ~/current to keep with convention.

1
babushka 'passenger deploy repo'

The post-receive hook it adds is pretty cool: every time you push to this repo, the post-receive hook will refresh the code and restarts the passenger instance…have a look:

1
2
3
4
5
6
cat ~/current/.git/post-receive

cd ..
env -i git reset --hard
mkdir -p tmp
touch tmp/restart.txt

On your local machine, cd to your rails app’s root and add the repo on the server as a remote repository:

1
2
3
git remote add server your-domain.tld@your-ip-address:~/current
git push server master
... wait for it to finish pushing ...

Once its finished, your code will be on the server in the ~/current repo you created.

Now that we’ve set up the code on the server, we need to set up the rails app itself. Easy enough with Babushka, answer the prompts as you go:

1
babushka 'rails app'

Congrats, you can hit http://your-domain.tld and see your app hosted and running off postgres and nginx.

Sharing a session between subdomains

http://guestbook.co.za/ is also available at http://www.guestbook.co.za/. We wanted our users to remain logged in if they’re using GuestBook without the ‘www.’ subdomain and then enter it or visit the site from a link.

To do so we needed to share the session between www.guestbook.co.za and guestbook.co.za. This sounds really difficult but thankfully all it takes is prepending the domain name of your cookie with a period (.)

Here’s how to do it in Rails:

1
2
3
4
5
6
# In your production.rb file
config.action_controller.session = {
  :session_domain => ".your-domain.tld",
  :session_key    => "_yourapp",
  :secret         => "somehugesecretkeythathastobemorethanthirtycharacterslong"
}

Thanks for the original post I got this from!