News Flash

Just released 10 more student tickets to the CSS3 Online Conference. They'll sell out quick so grab yours now! http://cot.ag/9mms1c

Tagged: Ruby on Rails

2 July 2007

Any sufficiently advanced technology is indistinguishable from magic.
- Arthur C. Clarke

When Google first unveiled GMail, then GMaps, a firestorm of interest and activity was generated — not just in those Google applications, but the technologies that powered them.

These web programming techniques (now, of course, known collectively as AJAX) have become incredibly popular, especially among Web 2.0 startups and their early adopters.

The developers of Ruby on Rails recognized early on that allowing RoR developers to easily AJAX-ify their webapps would be a great addition to the framework.

With the addition of RJS Templates to Rails core, the AJAX bar was lowered even further.

Simple AJAX requests like incrementing the number of diggs a story has received, or splicing a comment into a blog, are remarkably fast, not to mention user friendly.

What we’ll be addressing today, though, is optimizing ever more complex AJAX requests that might involve a multitude of SQL calls and JavaScript rendering techniques.

Not all of the techniques are strictly JavaScript/browser-oriented. The list was actually derived from the steps that we took to optimize Mailroom, the first app in the Sproutit suite for small businesses.

Mailroom is built for use with small teams; it can be used to manage shared email accounts such as support@ and sales@. It combines an ajax-rich front end with features like tagging, saved replies (for later reuse) and shared internal conversation notes.

When we first launched Mailroom, the service was reasonably fast. As we grew and our server got busier, however, some customers started to complain about how long it took for some of our AJAX functions to complete.

The following optimization techniques, though not complete by any means, were just a few of the methods we used over the course of several weeks to bring Mailroom, literally, up to speed.

Following the performance boost, Mailroom’s response times for a Load Conversation call (via AJAX), went from 2-8 seconds to averaging less than half a second. Once we added pre-caching (technique #5) on the client side via JavaScript, clicking on a message resulted in a near-instantaneous load; sometimes faster than Outlook or Apple Mail.

Possible Sources of Latency

When building out your AJAX application, it’s a good idea to keep in mind the different sources of latency that can slow down the user experience. These include:

  1. The Database (both in memory and filesystem swap)
  2. App Server Execution & Rendering
  3. The Webserver
  4. The Network
  5. The Browser (only in really complex AJAX apps)

There are others, but we hope this broadly covers them all. We’ll only be taking a look at a few of these sources, but it’s useful to keep them all in mind as you build out your app.

5 Ways You Can Optimize Your Ruby on Rails/AJAX Application

  1. Optimal Database Indexing
  2. Eliminate Redundant SQL Queries
  3. Fragment Caching
  4. Response Text Compression/Minimization
  5. Pre-rendering and Client-side JavaScript Caching

1. Optimal Database Indexing

For many applications, database indexing will be the biggest performance booster of them all.

Sample output from a Rails log file:


  Completed in 8.29523 (0 reqs/sec) | Rendering: 2.68176 (32%) | DB: 5.38202 (64%) | 200 OK [http://railsapp.org/action]

This action would be an ideal candidate for database optimization. If, however, you see something like DB: 0.05 (5%), the action is already spending most of it’s time rendering and there’s probably not a lot you can do in the way of database indexing.

It used to be somewhat difficult to pinpoint the exact function call in your Rails app that was generating an SQL query. But with last year’s release of Nathaniel Talbot’s QueryTrace plugin for Rails, this query back tracing process got a whole lot simpler.

After you install the QueryTrace plugin your Rails app (in the ‘development’ environment) will give you output like this in your log/development.log:


 Conversation Load (0.001538)   SELECT * FROM conversations WHERE (conversations.id = 23453) LIMIT 1
    app/models/feed_observer.rb:14:in `after_create'
    app/controllers/conversation_controller.rb:162:in `send_to'
    script/server:3

As you can see, it’s now trivial to find the exact call that’s generating the SQL database hit.

Adding Indexes via Migrations

The following is a migration’s self.up method to both create an articles table and add a few indices:


def self.up     # brings db schema to the next version
  create_table :articles do |t|
    t.column :title,      :string
    t.column :author_id,  :integer
    t.column :body,       :text
    t.column :is_live,    :boolean, :default => false
  end
  add_index :articles, :author_id
  add_index :articles, [ :author_id, :is_live ], :name => 'author_live_idx'
end

This would create both a single-column index on the author_id, as well as a multi-column index on author_id and is_live.

When in doubt, use a tool like railsbench or the Ruby Performance Validator to see where the bottlenecks are in your Rails app.

2. Eliminate Redundant SQL Queries

Note: this one is more of a “gotcha” applicable to those of us who are new to Ruby on Rails. (Since I’ve fallen victim to this mistake several times, I thought it was worth mentioning here.)

It’s always a good idea to keep a ‘tail -f log/development.log’ open when you are developing your Rails apps. (Win32 users can get tail, etc. here or use Cygwin )

You might be surprised at what you find if you are not in the habit of checking your query logs.

Ever written a quick hack like this?


  class ApplicationController < ActionController::Base
    def current_member
      return Member.find(@session[:member])
    end
    helper_method :current_member
  end

Only to discover that you’re actually calling current_member more than once in a particular controller/view combination? (thus generating multiple SQL calls for the same unchanged ActiveRecord object)

Here’s how to cache the variable in a per-request instance variable so that the Member.find(session[:member_id]) database query only gets called once per request:


  class ApplicationController < ActionController::Base
    protected
      def current_member

        @current_member ||= Member.find(session[:member_id])
      end

      helper_method :current_member
    end
  end

3. Response Text Compression/Minimization

Before attempting the below technique, the simplest way to reduce the number of bits sent across the wire is to enable gzip compression in your web server.

Some links on gzip compress: mod_deflate (apache 2.2), thread on config’ing lighty with gzip, apache 2.2 mod_deflate tutorial.

In the initial version of Mailroom, we were loading the tag conversation HTML module via AJAX for every load conversation request.

There was quite a bit of superfluous HTML that was being returned, when all we really needed was the specific tags that this particular conversation had been tagged with.

By the way, this kind of optimization is helpful when you have one master page load (where it’s OK to take 1-2 seconds longer), followed by many successive AJAX requests that you want to be rendered as quickly as possible.

We’re also targeting the size of the AJAX response text sent back by the server. A 4k response text is of course more preferable to a 16k one.

Initially, this was the HTML snippet being returned for every load conversation AJAX call:


Choose from the tags below:

… lots of tags …
or enter some new tags:

Instead of returning this whole chunk of HTML each call, we are going to load a static, hidden HTML div once on the initial page load, and return (via AJAX, each call) just the bare essential tag data needed to render this static HTML into a per-conversation tag editor.

First we create a simple Tags object in javascript (with a little help from Prototype):


  Tags = Class.create();
  Tags.prototype =  {
    initialize: function(conversation_id, tag_str, tag_ids) {
      this.conversation_id  = conversation_id;
      this.tag_str          = tag_str;
      this.tag_ids          = tag_ids;
    }
  }

In your main page load, we render a static version of the tag editor template that stays in a hidden div.

The static HTML in a hidden div:




When a conversation is loaded, the contents of the hidden div will be read in via JavaScript. Regular expressions are performed against it on the fly to replace elements like #TAGS# with actual per-conversation data returned via the AJAX request.

Here we create a simple JavaScript function that pulls in the contents of the hidden div, performs regex replacements on it, and completes with a Prototype Insertion call of the newly rendered HTML.


  loadTagEditor: function(tags) {
    var tags_static = $('tag-editor-static').innerHTML;
    var regex = /-static/g;
    tags_static = tags_static.replace(regex, '');  # Strip out the "-static" portion of the ids

    var tags_regex = /#TAGS#/;
    tags_static = tags_static.replace(tags_regex, tags.tag_str);
    ...
    new Insertion.After('the-widget-before-tag-editor', tags_static);
  }

Now whenever a conversation is selected, we just need to make sure the “loadTagEditor(tags)” method is called.

There are a few gotchas you’ll want to watch out for with this technique, such as not ending up with duplicate ids in your HTML.

In the hidden, static divs, one way to avoid duplicate ids is to append all your id names with ”-static” and then strip out the ”-static” when you load in your hidden div HTML. (that’s what we do above with the Regular Expression)

Now, our original chunk of HTML (which can really add up if you have 20 – 50 tags) that was previously being returned back with each Load Conversation AJAX request—is now replaced with a few simple lines of JavaScript:


  tags = new Tags(460857, 'home Marketing Development', new Array(1251, 1252, 1460));
  loadTagEditor(tags);

This is the Rails RJS (inside show_conversation.rjs, perhaps) that would be used to generate the above JavaScript:


  page << "tags = new Tags(#{@conversation.id}, '#{@conversation_tags * ' '}', new
Array(#{@conversation_tag_ids.inspect_raw}));"
  page << "ch.loadTagEditor(tags);"

4. Fragment Caching

Here we’ll use the ever so Web 2.0 example here of the tag cloud. These little beauties can be expensive to calculate in SQL. Once you’re site has been TechCrunched, you’ll want to make sure that your tag cloud (especially miniature versions loaded on heavily-trafficked pages, or via AJAX) gets cached as a Rails HTML fragment.


  class TagController < ApplicationController

    def tag_cloud
      fragment = read_fragment("myrailsapp.com/tagcloud")
      if not fragment
        setup_tag_cloud
        fragment = render :template => “tag/tag_cloud”
        write_fragment(”myrailsapp.com/tagcloud”, fragment)
      else
        #logger.info “Fragment cache read: #{fragment}”
        render :text => fragment and return
      end
    end

    private
      def setup_tag_cloud
        # Expensive SQL queries here …
      end
  end

Uncomment the logger.info call to verify that your fragment really is being read from the cache!

Admittedly, fragment caching in Rails can make your code less maintainable. It’s a technique that should really only be used for expensive-to-compute, fairly static portions of your site.

There are several helpful rails caching plugins on the Plugins page on the RoR wiki.

One simple but effective plugin is the Timed File Store which allows you to set an expiration (e.g. 15 minutes) on your cached fragments.

5. Client-side JavaScript Caching and Pre-rendering

If you really want to wow your users, pre-cache commonly called AJAX components into hidden divs so that the only time necessary to load them is the time it takes their browser to execute (eval) the pre-rendered JavaScript.

In the following example, we’ll cache conversations into hidden divs so that whenever a user clicks on a conversation, it’ll load almost instantaneously.

The pre-caching functions will all access a single global JavaScript variable that holds an array. That array will be populated on the first page load with the conversation IDs that should be cached.

Here’s the inline JavaScript (placed at the bottom of the main rendered HTML page):


  

Here are the JavaScript functions that do the preloading/caching:


  // Uses JavaScript's "setTimeout" function to call the "loadFirst" method (once per id).
  //    This method handles the logic of delaying the first load (to give the parent HTML page
  //    time to load) and putting a delay between each AJAX load so as to not overwhelm
  //    the browser & server.
  function preLoader() {
    var cnt = 0;
    var offset = 250;              # Milliseconds to delay the initial load
    var delay_per_load = 500;      # Milliseconds between each load
    ids.each(
      function (id) {
        setTimeout("loadFirst()", (offset   (cnt * delay_per_load)));
        if (id > 0) {
          cnt  = 1;
        }
      }
    );
  }
  // Pops off the first element in the 'load_ids' array and loads it
  function loadFirst() {
    var id = load_ids.pop();
    if (id > 0) {
      preloadConversation(id);
    }
  }
  // Preloads the conversation specified by creating a new AJAX request -- Note the use of Prototype's
  //   "onComplete" method to specify that "cacheConversation" should be called when the AJAX request
  //   has been completed and its data returned.
  preloadConversation = function(conversation_id) {
    new Ajax.Request('/mailroom/conversation/'   conversation_id   '?preload=true', {asynchronous:true, evalScripts:false, onComplete: cacheConversation});
  }
  cacheConversation = function(originalRequest) {
    // ... omitting some "clever" hacks used to get the conversation id ('conv_id' variable) ...
    var hidden_div_id = 'pre-'   conv_id;
    // Set the Inner HTML of a hidden div to the JavaScript passed back that was generated by Rails RJS templates:
    $(hidden_div_id).innerHTML = encode(originalRequest.responseText);
  }

The following is the JavaScript function used to either A) load the cached conversation, or B) if the cache is not present, load the conversation directly off the server:


  loadConversation: function(id) {
    if ($('pre-'   id).innerHTML != '') {
      var decoded = decode($('pre-'   id).innerHTML);
      eval(decoded);
    } else {
      // Load the conversation via a new Ajax call (eval'ing the result by setting "evalScripts:true",
      //    then caching it via the onComplete parameter).
      new Ajax.Request('/mailroom/conversation/'   id, {asynchronous:true, evalScripts:true, onComplete: cacheConversation});
    }
  }

Phew. Well, there you have it. JavaScript, long the domain of cut n’ paste HTML jockeys, has proven in recent years to be a formidable challenger to Flash (and Applets, remember those), once hailed as the future of interactive web applications. Of course, each has their place and own unique advantages and disadvantages.

While most applications wouldn’t need the kind of firepower described in this technique, hopefully you’ve learned a little bit about JavaScript, Rails and RJS along the way.

Footnote

The inspect_raw method used in technique #5 is a custom method we added to the Array class. Go here to learn more about this method and how we implemented it.

Continue reading 1
Future of Web Design London May 17-19 2010

30 May 2007

Professional on the Web is a portfolio directory for web agencies and freelancers. Its target market is by definition a narrow one. We wanted to attract as many web professionals as we could by giving them the opportunity to promote their work in the fastest and simplest way possible. We achieved this by developing a no-frills, easy-to-use web application.

Today everyone is registered to some sort of a community or web-based network and the majority of them require a lengthy and boring registration process. We decided to differentiate ourselves by building a simple and fast profile creation process. It takes less than a minute to go through the motions. Users enter a few basic personal details, their projects’ names and relative web addresses. Once the data has been inputed, thumbnail screenshots for each project are automatically generated and displayed on the their user home page.

Professional's public page

When logged into the system, the professional’s public home page doubles as their editable settings page. They can edit their profile and projects, associate tags, and instantly preview all the changes on the same page. In-place editing and drag&drop functions make it a breeze and instantly gratifying to modify your home page.

Professional's user area

Professionals can determine how they’re listed in the system’s directory by associating tags to themselves. Users are allowed to choose up to ten tags, which are automatically suggested from a pre-determined library, to indicate the kind of services and expertise they offer. Having only a limited number of tags to choose from insures that users select only the most relevant ones. And it reduces the risk that a couple of bad apples end up spamming the system and appearing on every list.

Funneling Down User Data

A month after the launch of Professional on the Web we analyzed users’ behaviors and produced a funnel diagram. It revealed how many visitors had viewed the sign up page, how many actually signed up and completed their profile, and how many had subsequently updated their projects portfolio.

Professional on the Web's Funnel diagram

After analyzing the results we revisited different areas of the interface to improve usability and increase the ratio between active users and total visitors. In particular:

  • We added a new footer area and populated it with all the secondary links that were cluttering up the sidebar. We relegated the RSS links, social bookmarking links, advertising links and banners, and the request for donations to the new footer. Freeing up room in the sidebar provided more focus on the system’s internal services and navigation.
  • With more room in the sidebar now available we added a new system feature: a third party Job Notice Board with the aim to build our own in the future.
  • We created a new area on the home page, below the top rated professionals, and gave room to three random professionals. The idea was that they’d also receive visits, take stock of the referrals to their websites and inspire them to return to Professional on the Web to update their portfolio.

In the future, we will also be redesigning the homepage. At the moment its main focus is on getting professionals to sign up. Once we have a healthy number of active users registered on the system we will downgrade the prominence of the Signup teaser and highlight other features and services.

Developing with Ruby on Rails

We chose Ruby on Rails as the system’s framework as we knew it would bring several benefits throughout the development phase.

When we started planning Professional on the Web we were very busy with client work. So we decided to avoid the bells and whistles and start developing only the essential functions to get the system online as soon as possible. In this respect Rails was phenomenal. Once we settled on the overall design we jumped in. Framework development, user testing and subsequent modifications were all carried out hand-in-hand. It took two of us just 20 days to complete the application.

The Pros of Rails

The MVC (Model View Controller) architecture lets you easily design, modify and improve the application. It is an architectural pattern that mainly decouples the database interface (Model) from the user interface (View). An intermediary component, the Controller, is placed between how the data is represented and how the end user interacts with it. Thanks to this split, each part of the application has one authoritative and unambiguous place and you can respect the DRY (Don’t Repeat Yourself) principle. For example, the database extracting function would reside inside the template linked to the table that the template refers to, while the user’s request would be handled by the relative controller.

This approach can make learning Rails a lengthier process. Indeed, when you first start coding with it, development is not as swift as it could be with PHP or ASP. But at a certain point (it took us six months) it all starts falling into place and you marvel at how easy it really is. You soon notice how things that seemed so hard in the beginning actually turned out to be very simple.

We used database versioning and Capistrano to help streamline and simplify deployments. They greatly reduce all the usual errors encountered during deployment and it especially makes life easier once the application starts growing and you need to scale all procedures. For example, if you need to make changes simultaneously to the database and the application’s code, Capistrano will create a new folder to copy the whole application to, make the requested database changes and then switch the current symbolic link to the new directory. This allows you to easily rollback if something goes wrong by switching the symbolic link back to the old directory. And the whole procedure takes only a few moments. There’s no need to waste time FTPing, manually creating new folders or symbolic links, or restarting dispatches.

Another advantage with Rails is that tests are automatically generated while developing the application. The test suites created for both the application model and the user interaction allow you to add new functions without adversely affecting existing ones. There are many useful test plugin libraries that we have found indispensable for our projects, such as:

  • Selenium on Rails lets you test client-side behaviors. Tests are easily created by recording a macro with Firefox. Then you can carry out the same tests on all other browsers (amongst which Internet Explorer, Opera and Safari) and on different operating systems (Windows, Mac and Linux). This allows you to pinpoint any browser specific issues, especially when implementing AJAX.
  • SpiderTest is an integration-testing script that crawls all pages of your application. It automatically checks if your HTML is valid and if links point to exisiting pages. It also follows all AJAX calls and fills out required fields to submit and test forms.

Ruby on Rails is also useful for its fragmented caching function which allows you to save HTML code fragments to disk. As a result Ruby doesn’t need to calculate all the respective views and database requests every time they’re required. This is particularly time-saving when dealing with objects that belong to many lists. “Partials” — fragments of Ruby HTML (RHTML) that can be included in a view — can then be easily cached and reused in other lists. Below you can see how we used fragmented caching for the different professionals’ list views:

<% cache("professionals/p_#{professional.id}") do %>
    ...
    # partial code
    ...
<% end %>

The beauty of this function is that you can introduce it retrospectively. All you need is to add the first and last lines of code inside the fragment. If you make changes to the fragment’s content, you will need to annul its disk cache. But even this is a simple operation with Rails. For Professional on the Web, we added an action in the User model for all modification requests made by a professional when editing their profile page:

after_save :destroy_cache

after_save is a Ruby callback which allows you to callback automatically the method destroy_cache following any change on a professional’s profile page. The code for destroy_cache is:

def destroy_cache
  fragment = "#{RAILS_ROOT}/tmp/cache/professionals/p_#{self.id}.cache"
  File.delete(fragment) if File.exists?(fragment)
end

An added bonus with this approach was that it helped increase the application’s speed. Because the fragment was performing a complex calculation, it helped save similarly complex operations that other helpers and database requests would have had to execute. It also allowed us to cache the professional independently whenever they appeared on a list, such as under the CSS or HTML tag lists.

Plugins can also speed up development time. For instance, the registration process is added by using the acts_as_authenticated plugin, which allows you to compile the database, HTML pages and sent emails in under a minute. The same results could be obtained with a CMS, but Ruby’s main strength lies in its flexibility. You can create new plugins whenever needed and more efficiently than with any other framework.

The Cons of Rails

All said, we have to admit that Ruby on Rails has its drawbacks. Since Ruby’s functions are so readily adaptable, it is equally easy to abuse them. This is especially true of helpers as well as database connections. If, for example, you wanted to create a link you could write and call the function item_url(item), such as:

link_to "Show Item", item_url(item)

where item represents a professional and item_url would be something like:

url_for(:controller => 'site', :action => 'show', :id => item.id)

This would generate HTML code as:

<a href="/site/show/1">Show Link</a>

However, it would be overkill when all you’d need to code is:

<a href="/site/show/<%= item.id %>">Show Link</a>

This is a much more efficient way of generating the desired link rather than first calling the item_link function, which would then call the link_to function, which in turn would call the item_url function.

“Helpers” — small functions that can be used anywhere in a Rails app — like the above link helper are useful if the function is required in multiple views. Whenever you have to change the way that a link is associated to an object, you only have to alter it once in the helper’s code, instead of changing it everywhere it’s been used. But if the link generating function were placed in a list inside a view and were invoked tens or, worse, hundreds of times the app would noticeably slow. In this instance, using the direct version of the view would drastically improve the application’s performance.

A similar problem can arise with the ActiveRecord, an object-relational mapping (ORM) pattern. Since it is so easy to implement it can equally be abused. This could then develop into a much larger problem as soon as the application increases in complexity and the relationships between models proliferate. For instance, let’s assume that there’s a user (model User) with many associated tags (model Tag). If you wanted to list the tags associated to the user you could execute the following code:

User.find(:first).tags

The application would then execute two separate queries. So to remedy the problem you would then add:

User.find(:first, :include => :tags)

This allows you to execute a single query using the SQL JOIN query. Unfortunately, we noticed that you can slow the system down if you overuse this function, due to what is commonly known as “eager loading”. Indeed if you concatenate more objects to the equation it can result in very large data transfers, reducing the application’s performance. This is because you can’t specify individual fields in the request, so you’re obliged to receive all the table contents in the JOIN query.

Another drawback with Rails is that for even just a small application you need at least 40MB of dedicated memory. Amongst all the advantages with Rails, you do need to consider whether it is appropriate to develop with it if you’re only going to be creating a simple website. Otherwise you will risk using up 1GB of RAM with little more than 10 projects.

Conclusion

As a final remark, we must commend Ruby on Rails on the clean and self-explanatory code that you can write with it. This is especially important for the way we work. It means that we can each write separate code snippets and then assemble them without having to explain to one another what goes where and why one thing was written in a certain way. Overall, Rails has boosted our productivity and work flow and made Professional on the Web a better app.

Continue reading 4

7 May 2006

An array is a list of items in order (like vitamins, minerals, and chocolates).

Those keeping score at home might be interested to know that the Rails website framework makes 771 calls to Array.each, 558 calls to Array.map, and 1,521 calls to Array.empty?, not to mention the 2,103 times it accesses a single element inside an array.

But these are just numbers. How do you use arrays in Ruby?

Creating Arrays

Most of the time, you will be working with arrays of database objects. Other times, you will need to make a simple array of your own.

If you have used Perl, PHP, Python, or other scripting languages, the syntax of Ruby might seem at least remotely familiar to you. You can follow along by typing these code samples into the Try Ruby website. If you have installed Ruby on your computer, you can also use the “irb” command-line program, which runs lines of code interactively.

You can make an array by using square brackets like this:

# Make a list of clues for a game of charades
clues = ['vitamins', 'minerals', 'chocolates']

# Create the same array by splitting on whitespace
clues = %w(vitamins minerals chocolates)

# Create the same array in steps
clues = Array.new
clues << 'vitamins'
clues << 'minerals'
clues << 'chocolates'

You can retrieve values from an array by number, starting at zero. In these examples I’ll use => to show the result of each line.

a_clue = clues[0]
=> 'vitamins'

another_clue = clues[1]
=> 'minerals'

Ruby also has some shortcuts for commonly accessed attributes of an array:

a_clue = clues.first
=> 'vitamins'

another_clue = clues.last
=> 'chocolates'

clues.length
=> 3

Ok … enough of the basics. What can you do with an array?

each and each_with_index

Many languages force you to handle arrays with arcane sequences of semicolons, parentheses, and placeholders. Not Ruby!

my_vitamins = ['b-12', 'c', 'riboflavin']

my_vitamins.each do |vitamin|
  puts "#{vitamin} is tasty!"
end
=> b-12 is tasty!
=> c is tasty!
=> riboflavin is tasty!

As you might guess, this prints a message for each of the vitamins. Here are a few things to remember:

  • The variable “vitamin” inside the “goalposts” refers to each item in the array as it goes through the loop. You can give this any name you want, but make it memorable.
  • The “do” and “end” identify a block of code that will be executed for each item. Blocks are used extensively in Ruby.
  • The “#{vitamin}” inside the double-quotes is how you insert a variable inside a string. There are other ways to do it, but “#{}” is the simplest.

What if we wanted to print the name of the vitamin and also do something with its place in the array?

my_vitamins.each_with_index do |vitamin, index|
  puts "#{index} cheers for #{vitamin}!"
end
=> 0 cheers for b-12!
=> 1 cheers for c!
=> 2 cheers for riboflavin!

Again, the names “vitamin” and “index” inside the vertical bars are names that you choose. However, the “each_with_index” method calls the block with two values, so you need to provide two variable names. Several useful references can be found at Ruby-Doc.org.

If this were production code, we might write an additional method to apply proper English grammar based on the number of cheers (Rails has such a method that we could use). We might also want to use a more authoritative source for determining how many cheers each vitamin should get!

map

Ruby’s ability to work with arrays doesn’t stop there. Array.map calls a block for each item and returns a new array based on the result of the block.

nouns = ['truffle', 'kiss', 'rabbit']
array_of_chocolates = nouns.map do |noun|
  "chocolate #{noun}"
end
# array_of_chocolates now has these values
=> ['chocolate truffle', 'chocolate kiss', 'chocolate rabbit']

You might notice that we don’t have to use the “return” keyword … Ruby automatically returns the last value in a block. In this case, the return value from each iteration of the block is the new “chocolate something” string. However, we could have returned a number or any other kind of object.

On my blog I’m writing a Rails plugin to take data from my Mint stats installation and show a list of popular pages. I use “map” to put the numbers together.

# Get an array of rows from a database query
stats = Mint.popular_pages

# Assemble a summary for each
stats_report = stats.map do |stat|
  "#{stat.resource_title} was visited #{stat.visit_count} times"
end
# stats_report is now an array with these values:
=> ['Home was visited 1200 times', 'Search was visited 900 times', ...]

The resource_title and visit_count methods are actually fields in the database. ActiveRecord looks at the database schema and creates these methods automatically. I use these to build a string for each row which is returned as a new array.

inject

The inject method is one of my favorites even though it is a bit complicated (Ruby borrowed it from the Smalltalk language). Let’s look at an example and then I’ll explain how it works:

# Get an array of rows from a database query
stats = Mint.popular_pages

# Add statistics to get total number of visits
total_visits = stats.inject(0) do |sum, stat|
  sum  = stat.visit_count
end
=> 8800

Several things are happening here:

  • We start with an array of database row objects, as seen earlier.
  • Inject takes an argument, which in this case is a number. This is the starting value that will be manipulated during each iteration of the block and returned at the end. We could have started with a blank array (“[]“), a blank hash (“{}”), or even an existing object.
  • The arguments to the block are the variable we just initialized, and a single element from the array.

Still confused? That’s ok … let’s step through it.

To begin with, “sum” will be zero because we sent “0″ as the initializing value to inject.

“stat” will be the first item from the “stats” array. We add that page’s number of visits to the current “sum”.

That cycle repeats for all the elements in the array, gradually increasing the overall “sum”.

At the end, the final value of “sum” is returned (8,800 in this case).

So that’s a lot of functionality in a very small amount of code. In fact, you could fit it all on a single line with the optional curly-bracket syntax:

total_visits = stats.inject(0) {|sum, stat| sum  = stat.visit_count}

Operators – and

Arrays are capable of much more, but here is a final feature: addition and subtraction. Adding arrays simply combines them, even if there are duplicates. Array subtraction is smart enough to take out the duplicates.

a = [1, 2, 3]
b = [1, 4, 5]

a - b
=> [2, 3]

a   b
=> [1, 2, 3, 1, 4, 5]

(a   b) - a
=> [4, 5]

Many of these methods are available to any object that has its own “each” method! See Ruby-Doc or the older online version of Programming Ruby for the details.

And tune in next time for the basics of Ruby hashes!

Continue reading 10

Sign Up to our Newsletter

Enter your e-mail address below to receive regular updates on web design, web development and web business. Subscribe today and receive a free 44 page PDF "Designing Web User Interfaces" by Ryan Singer of 37signals.

Subscribe to the Think Vitamin articles RSS feed

HTML5 Online Conference April 12 2010

News

Twitter

Follow us on Twitter

Subscribe

Article Subscribers

Feedburner blog subscriber indicator

News Subscribers

Feedburner blog subscriber indicator

Subscribe by Email

You can receive Think Vitamin updates via email. Just pop your email address in the box below and click the arrows.

Subscribe by RSS

You can also receive new Think Vitamin posts via your RSS feed reader

Subscribe RSS Think Vitamin is a proud member of the Smashing Network

Ads Via The Deck