Problem

Your view code in your Ruby on Rails app is getting messy with duplicate code and ugly conditional statements. For example, maybe you have something in your view that displays the user’s avatar (that is, if the user has uploaded one already) such as:

<% if current_user.avatar %>
    <%= image_tag(current_user.avatar.public_filename(:thumb) %>
<% else %>
    <%= image_tag("avatar_thumb.png") %>
<% end %>

Solution

Create custom helper tags. An easy way to get into this is to look at the source of built in helpers and piggy back on them. Don’t get too scared, the built in helpers are ones you already use like link_to, image_tag, etc. For this example, let’s build on the image_tag to write a custom “avatar_tag” helper.

With this call from the view we want to trigger an “avatar_tag” helper method in our rails_app/helpers/application_helper.rb that we will create. You can also put it in your one of your controller specific helper modules if you don’t want this helper available system wide.

Let’s first look at the “image_tag” helper that is built in to rails:

# File src/rails-1.2.1/actionpack/lib/action_view/helpers/asset_tag_helper.rb, line 185
def image_tag(source, options = {})
  options.symbolize_keys!

  options[:src] = image_path(source)
  options[:alt] ||= File.basename(options[:src], '.*').split('.').first.capitalize

  if options[:size]
      options[:width], options[:height] = options[:size].split("x") if options[:size] =~ %r{^\d+x\d+$}
      options.delete(:size)
  end

  tag("img", options)
 end

Let’s dissect this a bit…

  • First, the method is accepting a source location and a hash of HTML options (options={}) that we can pass in to override the default values. (such as :alt, :width, etc)
  • Inside the image_tag method, options.symbolize_keys! is going to turn our options hash into a new hash with all the keys converted to symbols.
  • Next it is going to assign the HTML src option to the source that we pass in.
  • Same goes to any other options if we passed them in to override any defaults. For example, if we called image_tag(“logo.png”, {:alt=>”This is a temp logo”}), it would override the default alt value that image_tag provides (the src of the image).
  • Finally, it is going to use the tag method to create the actual HTML tag.

Now let’s create our own “avatar_tag” helper - which, as we can see, doesn’t look too different:

def avatar_tag(asset, options = {})
        options.symbolize_keys!

        options[:class] ||= :thumb
        options[:alt] ||= (asset) ? asset.public_filename : "No Avatar Found"

        #buddyicon_tiny.png and buddyicon_thumb.png should be in app directory
        options[:src] = (asset) ? asset.public_filename(options[:class]) : "/images/buddyicon_#{options[:class].to_s}.png"

        tag(:img, options)
    end

What’s new here?

  • First of all we are calling our new method “avatar_tag” - which we will then be able to call from any of our views (since we put it in the application helper).
  • We are using the attribute “class” to define the type of avatar to use. We also set it so that it is :thumb by default.
  • For the alt attribute, we set the default to the filename when the source is not nil and set it to “No Avatar Found” if it is nil.
  • Finally, we set the source to be the avatar source if source is not nil and if it is nil we set it to our default avatar image. Note that we are also passing in options[:class] in the filename so we will need to name our default images - avatar_thumb.png and avatar_tiny.png

Now rather than those conditionals clogging up our view template, we can simply render our :tiny version of our user’s avatar icon by calling:

<%= avatar_tag(current_user.avatar, :class=>:tiny) %>

or the :thumb version by just calling:

<%= avatar_tag(current_user.avatar) %>

Mmm..much cleaner…..

Good luck and as always, please post in the comments if you know/found a better way to do this!

Problem

You want to be able to create a new row via AJAX on one of your database driven tables in your rails app. Additionally, you want to ensure that the entire table (in your view) is not reloaded when adding a single row to the table.

Solution

Luckily, with Rails + RJS, this problem isn’t too difficult. For this example, let’s pretend we own a bunch of bike parts and we want to be able to keep track of them in a simple inventory table. For even more simplicity, let’s keep the database using only one table.

Let’s assume you have already created your rails app and configured your database.yml. With the new app ready, let’s setup our migration. First generate the migration file:

In your app root:

script/generate migration AddComponents

This will create your migration file in app/db/migrate/xxx_add_components.rb, open it up and enter the following migration code to create our components table and fields.

class AddComponents < ActiveRecord::Migration
    def self.up
        create_table "components", :force => true do |t|
          t.column "created_at", :datetime
          t.column "make", :string
          t.column "model", :string
          t.column "category", :string
        end
    end

    def self.down
      drop_table :components
    end
end
Now migrate your data. In your app root:
rake db:migrate

Before we go on, first check your db to make sure that your table and fields were created.

With the Components table in your db, we can now generate our model so we can access the data (although we won’t be putting any code in there for this tutorial):

script/generate model Component

Now generate the controller so we can handle all of our actions:

script/generate controller Inventory

Open up the app/controllers/inventory_controller.rb and let’s add an entry page and an instance variable that will eventually allow us to see all of our bike components on one page.

def list
    @components = Component.find(:all)
end

Now, we can create and edit to our view (app/views/inventory/list.rhtml) which will present our table of components and a form to add a new component:

<h1>Bike Components Inventory</h1>
<h4>Example: Adding rows to a table via AJAX (using RJS)</h4>

<table id="components">
    <tr>
        <th>Make</th>
        <th>Model</th>
        <th>Type</th>
    </tr>
    <% @components.each do |c| %>
        <%= render :partial=> "bike_component", :locals=>{:c=>c} %>
    <% end %>
</table>
<p><a href="javascript:new Effect.toggle('newComponent','blind',{duration:0.1});void(0);">Add a new component</a></p>
<div id="newComponent" style="display:none">
    <% form_remote_for :component, :url=>{:action=>:add_row}, :html=>{:id=>"component-form"} do |f| %>
        <p><label id="make">Make: <%=f.text_field :make %></label></p>
        <p><label id="model">Model: <%=f.text_field :model %></label></p>
        <p><label id="category">Category: <%=f.text_field :category %></label></p>
        <p><%= submit_tag "Save" %></p>
    <% end %>
</div>

Let’s dissect this a bit:

  • The layout consists of a simple HTML table and below it is a hidden div that is toggled via javascript when “Add a new component” is clicked on.
  • For each component row in our database table (the @components instance variable that we created in our controller), we create a row in our HTML table and rendering it using a partial. For the partial, we must pass in the component object and store it as the local variable “c” (more on this later).
  • We have a form_remote_for to handle the AJAX form submit that will trigger the “add_row” AJAX method that we are about to write. (FYI, formremoteform pretty much works the same way as form_for but submits it the data via AJAX)
  • We give the form an ID “component_form” so that we can reference it through the DOM using RJS.

Let’s now create and fill in the _bike_component.rhtml partial which will render each component row in the table:

<tr id="component-<%= c.id %>">
    <td><%= c.make %></td>
    <td><%= c.model %></td>
    <td><%= c.category %></td>
</tr>

Note that we are giving the row and ID of “component-xxx” so that we can also reference this using RJS.

So why did we use this partial rather than rendering it right in the list.rhtml? We did this so that we can be DRY (Dont’ repeat yourself) and reuse the above code when we are adding rows via RJS. This will make more sense after we write the code to render the newly created row.

So let’s now go back to our controller and write the method that will add the row on form submit.

def add_row
    @c = Component.create(params[:component])
end

On form submit, the add_row method is called and a new Component is created based on the items in the component hash.

Since we are calling this add_row, we can then just call our rjs template /views/inventory/add_row.rjs (just make sure you don’t have an rhtml/erb file in your /views/inventory folder). With the RJS file created, we can then fill it with:

page.insert_html :bottom, "components", {:partial=> "bike_component", :locals=>{:c=>@c}}
page.visual_effect :highlight, "component-#{@c.id}", :duration => 2.5
page.form.reset "component-form"

Notice in the add_row method in our controller that we assigned the newly created component the instance variable @c? Well, now we can access that from our RJS template and pass @c into the partial as the local variable :c. This way the partial can access the newly component object without any problems.

Now let’s see what this JavaScript in ruby syntax is doing. RJS takes advantage of prototype and script.aculo.us JavaScript libraries. In the above code, it is using:

*Insertion.Bottom to insert the given HTML in the bottom of the given DOM element “components”. In our case, we are rendering the newly created partial to the bottom of the components HTML table.
*Effect.Highlight scriptaculous effect to highlight the newly created component row.
*Prototypes Form.reset to reset the components form.

One last thing, since we are prototype and scriptaculous we have to include them in our application. To do this, we just go to our application layout and add the following in our < head >.

<%= javascript_include_tag :defaults %>

That’s it, we’re done! Go add all your bike parts to your new inventory app! The final result should look something like this screencast (with a tiny bit of css):

Further Reading

I highly recommend reading the O’Reilly RJS article to get a real grasp on what RJS can do for you.

With one street down, Adam Hindman and I are calling it good enough to go….

We both live and work near “the ave” so we decided to create LunchFilter to help us figure out where to eat. Hopefully, it will serve as a nice guide to help other hungry people that live near UW as well.

If there is enough demand, we’ll extend this to other streets filled with restaurants as well.

If you are wondering, it was built with Ruby on Rails…

Problem:

Hosting multiple ruby on rails applications on your server - and of course we want our applications to run at least as fast as an apple fanboy on the day of the iphone release.

Solution:

Mongrel clusters with NGINX as a front-end using virtual hosts all playing nicely on your server. I recommend going with a Virtual Private Server (VPS) which is essentially one step above shared hosting but it gives you full root access. If you aren’t ballin’ on a budget, you could always splurge for a dedicated server - but you should probably wait until your user base demands such luxury. If you do go for a VPS I recommend Rimuhosting.

A word of advice: You pretty much get what you pay for with web servers, you’ll know this if you have ever ran a rails site on a shared server (eg. Dreamhost or Textdrive). Just dont expect to pay $7.00/month and host a site like Digg….or even survive a “digg” for that matter.

I setup my VPS with Fedora Core 6 and had rimu install the “ruby on rails hosting stack” upon order.

Mongrel and Mongrel Clusters

So now you have a server with sudo or root access. The first step is to install mongrel and friends. Mongrel is a webserver by itself and does very well at handling dynamic data. Sidenote: I actually just use mongrel by itself as my webserver on localhost (in replace of webrick).

sudo gem install mongrel
sudo gem install mongrel_cluster

The next step is to add a “mongrel” user so we don’t run mongrel as “root”.

sudo /usr/sbin/adduser -r mongrel

Next we set our mongrel user as the owner of both of our applications. This is important since our Mongrel servers need to have access to create PID log files in each of our applications (eg. /var/www/testapp1/log/)

sudo chown -R mongrel:mongrel /var/www/testapp1
sudo chown -R mongrel:mongrel /var/www/testapp2

Seeing that we have installed the mongrel cluster gem above, we can now configure the a cluster of mongrels for each of our applications. A mongrel cluster will configure and control several mongrel servers, or groups of mongrel servers. By running the configuration command below for each of our apps, it will create a mongrel_cluster.yml file in our config folder within each app directory.

In /var/www/testapp1/

sudo mongrel_rails cluster::configure -e production \
    -p 7000 -N 2 -c /var/www/testapp1 -a 127.0.0.1 \
    --user mongrel --group mongrel 

In /var/www/testapp2/

sudo mongrel_rails cluster::configure -e production \
    -p 8000 -N 2 -c /var/www/testapp2 -a 127.0.0.1 \
    --user mongrel --group mongrel 

Dissecting what we just did here:

  • “-p 8000” and “-p 7000” - setting the starting port number for each application
  • “-N 2” - specifies the amount of mongrels in each cluster. So this means testapp1 will run on 7000 and 7001 while testapp2 will run on 8000 and 8001
  • “-c /var/www/testapp1” and “-c /var/www/testapp2” specifies the location of each application.
  • “-a 127.0.0.1” - means that it will be running on localhost (your server)
  • “–user mongrel –group mongrel” - specifies the user and group that we created above.

NGINX, our front-end friend

Although mongrel is great at serving up the dynamic stuff, it is suggested that a load balancing reverse proxy server should be used as the front-end to handle the static and cached files. Enter NGINX. NGINX is lightweight front-end server (like Apache or Lighttpd). Additionally, NGINX will handle our virtual hosts which allow us to direct traffic to the correct port with multiple domains. At first I was using Apache (which was a pain to setup), but decided to go with NGINX after Zed Shaw (creator of mongrel) recommended it when I was asking an apache question on the Seattle.rb mailing list.

To setup NGINX we first install it on our server.

sudo yum install nginx

Once NGINX is installed, all we need to do is edit our NGINX configuration file at /etc/nginx/nginx.conf. Since we are configuring NGINX to handle two domains, we need to setup two different “servers” as virtual hosts in the configuration file - these have been given the labels “mongrel1” and “mongrel2” in the configuration file below. Also be sure to specify the correct ports for each application. Remember that earlier we set our mongrel config file for testapp1 to use ports 7000 and 7001, and set testapp2 to use ports 8000 and 8001. To map our labels to our applications, we set mongrel1 to use ports 7000 and 7001, and set mongrel2 to 8000 and 8001. With this mapping we can then put all of testapp1 config info in the server block that pertains to mongrel1 and the same goes for testapp2 with mongrel2.

Below is a configuration file from Ezra at Brainsplat with the variables modified by me for this writeup.

user jordan;

# number of nginx workers
worker_processes  4;

# pid of nginx master process
pid /var/run/nginx.pid;

# Number of worker connections. 1024 is a good default
events {
  worker_connections 1024;
}

# start the http module where we config http access.
http {
  # pull in mime-types. You can break out your config 
  # into as many include's as you want to make it cleaner
  include /etc/nginx/mime.types;

  # set a default type for the rare situation that
  # nothing matches from the mimie-type include
  default_type  application/octet-stream;

  # configure log format
  log_format main '$remote_addr - $remote_user [$time_local] '
                  '"$request" $status  $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for"';

  # main access log
  access_log  /var/log/nginx_access.log  main;

  # main error log
  error_log  /var/log/nginx_error.log debug;

  # no sendfile on OSX
  sendfile on;

  # These are good default values.
  tcp_nopush        on;
  tcp_nodelay       off;
  # output compression saves bandwidth 
  gzip            on;
  gzip_http_version 1.0;
  gzip_comp_level 2;
  gzip_proxied any;
  gzip_types      text/plain text/html text/css application/x-javascript text/xml application/xml 
application/xml+rss text/javascript;


  # this is where you define your mongrel clusters. 
  # you need one of these blocks for each cluster
  # and each one needs its own name to refer to it later.
  upstream mongrel1 {
    server 127.0.0.1:7000;
    server 127.0.0.1:7001;
  }

  upstream mongrel2 {
    server 127.0.0.1:8000;
    server 127.0.0.1:8001;
  }

  # the server directive is nginx's virtual host directive.
  server {
    # port to listen on. Can also be set to an IP:PORT
    listen 80;

    # Set the max size for file uploads to 50Mb
    client_max_body_size 50M;

    # sets the domain[s] that this vhost server requests for
    server_name www.testapp1.com testapp1.com;

    # doc root
    root /var/www/testapp1/public;

    # vhost specific access log
    access_log  /var/log/nginx.vhost.access.log  main;

    # this rewrites all the requests to the maintenance.html
    # page if it exists in the doc root. This is for capistrano's
    # disable web task
    if (-f $document_root/system/maintenance.html) {
      rewrite  ^(.*)$  /system/maintenance.html last;
      break;
    }

    location / {
      # needed to forward user's IP address to rails
      proxy_set_header  X-Real-IP  $remote_addr;

      # needed for HTTPS
      proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect false;
      proxy_max_temp_file_size 0;

      # If the file exists as a static file serve it directly without
      # running all the other rewite tests on it
      if (-f $request_filename) { 
        break; 
      }

      # check for index.html for directory index
      # if its there on the filesystem then rewite 
      # the url to add /index.html to the end of it
      # and then break to send it to the next config rules.
      if (-f $request_filename/index.html) {
        rewrite (.*) $1/index.html break;
      }

      # this is the meat of the rails page caching config
      # it adds .html to the end of the url and then checks
      # the filesystem for that file. If it exists, then we
      # rewite the url to have explicit .html on the end 
      # and then send it on its way to the next config rule.
      # if there is no file on the fs then it sets all the 
      # necessary headers and proxies to our upstream mongrels
      if (-f $request_filename.html) {
        rewrite (.*) $1.html break;
      }

      if (!-f $request_filename) {
        proxy_pass http://mongrel1;
        break;
      }
    }

    error_page   500 502 503 504  /500.html;
    location = /500.html {
      root   /var/www/testapp1/public;
    }
  }

  # This server is setup for ssl. Uncomment if 
  # you are using ssl as well as port 80.
  server {
    # port to listen on. Can also be set to an IP:PORT
    listen 80;

    # Set the max size for file uploads to 50Mb
    client_max_body_size 50M;

    # sets the domain[s] that this vhost server requests for
    server_name www.testapp2.com testapp2.com;

    # doc root
    root /var/www/testapp2/public;

    # vhost specific access log
    access_log  /var/log/nginx.vhost.access.log  main;

    # this rewrites all the requests to the maintenance.html
    # page if it exists in the doc root. This is for capistrano's
    # disable web task
    if (-f $document_root/system/maintenance.html) {
      rewrite  ^(.*)$  /system/maintenance.html last;
      break;
    }

    location / {
      # needed to forward user's IP address to rails
      proxy_set_header  X-Real-IP  $remote_addr;



      proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect false;
      proxy_max_temp_file_size 0;

      # If the file exists as a static file serve it directly without
      # running all the other rewite tests on it
      if (-f $request_filename) { 
        break; 
      }

      # check for index.html for directory index
      # if its there on the filesystem then rewite 
      # the url to add /index.html to the end of it
      # and then break to send it to the next config rules.
      if (-f $request_filename/index.html) {
        rewrite (.*) $1/index.html break;
      }

      # this is the meat of the rails page caching config
      # it adds .html to the end of the url and then checks
      # the filesystem for that file. If it exists, then we
      # rewite the url to have explicit .html on the end 
      # and then send it on its way to the next config rule.
      # if there is no file on the fs then it sets all the 
      # necessary headers and proxies to our upstream mongrels
      if (-f $request_filename.html) {
        rewrite (.*) $1.html break;
      }

      if (!-f $request_filename) {
        proxy_pass http://mongrel2;
        break;
      }
    }

    error_page   500 502 503 504  /500.html;
    location = /500.html {
      root   /var/www/testapp2/public;
    }
  }


}
Time to start up our servers! First let’s start up our NGINX server: In /var/www/init.d/
sudo ./nginx start

Now our mongrel servers:

In /var/www/testapp1/

sudo mongrel_rails cluster::start

In /var/www/testapp2/

sudo mongrel_rails cluster::start

If all went well, you should be able to see all of your processes using:

ps aux | grep mongrel
ps aux | grep nginx

You should have 4 mongrel processes running on ports 7000, 7001, 8000 and 8001. For NGINX, you should have a master process and 4 worker processes (if you put “4” for “worker_processes” in nginx.conf).

Assuming you have all of your DNS stuff configured correctly, if you go to each of your domains now, you should connect to the correct application. For example: http://www.testapp1.com should be forwarded by NGINX to go to the mongrel cluster serving ports 7000 and 7001.

Showcase

So you can see the actual speed of multiple domains on a VPS, these are the sites that are currently hosted on my rimuhosting server ($30/mo MicroVPS2 option):

Note: See Caution section below.

Extra Credit:

Go back to the mongrel home page (toward the bottom of the page) to check out how to initialize the server on boot and to setup a command to launch all of your mongrel clusters for both of your applications at once. (ie. /etc/init.d/mongrel_cluster start)

Further Reading on NGINX + Mongrel:

Caution: this is amateur hour

I am far from a sysAdmin guru - so I am positive that there are better ways to do this. If you know of a better one, please let me know in the comments section. Furthermore, as of now, I don’t have a large user base using each of my applications so I’ll have to update this article if I ever reach that stage to report on the speed.