Archive

My Favorite Rails Tips

The Rails tips contest held by Ryan Bates at Railscasts.com recently produced a great deal of good tips to use when developing a Rails application. I love this sort of thing: Sharp little bits of Ruby or Rails goodness that are fairly easy to find from one page. It's a nice little collection of drill bits for me, the drill. (So, I'm a tool.) You, dear reader, should check out at least those who finished in the top ten, if not all of the submitted material. Nearly all of them are well worth a skim.

The following are some of my favorite tips from the contest:

Easy SEO

The abbreviation SEO typically makes me wary. I perceive a lot of deceptive, dirty practices in association with 'SEO', but it doesn't have to be that way. There are some fully white-hat practices of which you can take advantage. There were a couple tips related to these in the contest.

Alastair Brunton recommends a simple way to include an article's headline in the URL:

A very cheap way to generate more descriptive urls is to take advantage of the way that ruby parses numbers. It will strip everything which is not a number if the string starts with a number.

class Article < ActiveRecord::Base
  def to_param
    "#{id}-#{headline.gsub(/[^a-z0-9]+/i, '-')}".downcase
  end
end

This will produce URLs that look like 'http://soyunperdedor.com/articles/4-sensational-headline-a-la-michael-arrington'. The headline portion of the URL will get stripped and you will indeed still be able to use params[:id] #=> 4. Sweet.

Visit Mr. Brunton's 5 Rails Tips for more information, and more tips.

Doug (maybe this Doug?) has another recommendation related to URLs and SEO. Assuming you're running Rails 2.1, you can now alias your resources in your routes file. For instance, as Doug suggested:

map.resources :operating_systems, :as => 'operating-systems'

The purpose? Hyphens are more search engine friendly than underscores. See Doug's 5 Rails Tips for more information.

Annotating Models

Doug also recommends Dave Thomas' 'annotate_models' plugin. I'm not sure how I missed this, but I like it. Like Doug, one of my favorite DataMapper features is that the model's schema is displayed within the model definition. Thanks to that, I don't have to look back at a migration, or at the table itself in some DB tool, when I forget the schema (which happens all of the time).

ruby script/plugin install http://repo.pragprog.com/svn/Public/plugins/annotate_models

Try, try() Again

Andreas Neuhaus suggests using Chris Wanstrath's try() to avoid sending messages to nil. This method is used in the GitHub code, as Chris says. A while back, there were several suggestions on what the best way to do this would be. For instance, Reginald Braithwaite's andand() is another popular solution, and installing the andand gem gets you Ruby 1.9's Object#tap (only better!), as well.

For me, try() goes down smoother. I recommend checking out Andreas' Five Tips for Developing Rails Applications for some other great tips, in addition to the try() tip.

TODO, FIXME, OPTIMIZE

Andreas also mentions that Rails includes a few rake tasks that make finding things needing done in your code pretty easy, as long as you left yourself some clues.

  • rake notes:todo - list all TODO comments
  • rake notes:fixme - list all FIXME comments
  • rake notes:optimize - list all OPTIMIZE comments
  • rake notes - list all of the above comments

I find these very handy, especially when I'm implementing or fixing something, and I notice that something else could be done different or better. I just make a note and continue in my work, so I don't lose my concentration. In fact, I made these into sake tasks so that they could be used elsewhere with ease. You're welcome to install them:

sake -i http://pastie.org/210890.txt

Gray's Chunky Finds

James Edward Gray II suggests a library he wrote and uses in many of his Rails apps which allows you to get a large data set in chunks, potentially increasing performance. I found it quite interesting. I haven't tried it, or benchmarked it yet, but I think it may find its way into our app at work soon. The function looks like this:

module FindAllInChunks
  def find<em>all</em>in<em>chunks(query = Hash.new, &amp;iterator)
    1.upto(1.0/0.0) do |i|
      records = paginate({:page => i, :per</em>page => 50}.merge(query))
      records.each(&amp;iterator)
      break unless records.next_page
    end
  end
end</p>

<p>ActiveRecord::Base.extend(FindAllInChunks)

It utilizes the will_paginate plugin, by the way.

I recommend you visit [Gray's tips][Gray's Five ActiveRecord Tips] for some other good tips, and to learn more about 'findallin_chunks'. Let me know if you throw it an app immediately.

The Command Line

Jerod Santo posted several tips in the Ruby on Rails forum. His first tip was to use GNU Screen, and I couldn't agree more. It's difficult to explain to people who haven't actually seen it in action. Basically, Screen allows me to split my terminal window so that the top portion is vim, the bottom portion is autotest, or possibly the 'tail -f' of the development log file, or the server output. Not only that, but I can have as many 'tabs' as I want, and everything can be accessed, moved, or modified through keyboard shortcuts. It's a wonderful thing.

To install on Ubuntu:

sudo apt-get install screen

You're welcome to use my screenrc (which is really Aaron Schaefer's screenrc, but his Mercurial repo interface confused me).

Check out Santo's 5 Tips for RoR Beginners. In particular, he includes a screencast showing GNU Screen in action, which is important.

Mikel Lindsaar 7th tip was all about command line shortcuts. Really, you should just go read Lindsaar's Shell Shortcuts You Should Know and Love. I am genuinely embarrassed to admit that I didn't know 'Ctrl-L' would clear the terminal window, as well as the IRB console. How did I not know that?

A Page of Their Own

I really think someone should go through all of the submitted tips, mining as many as possible, eliminating the duplicates, and make them their own section on someone's site. At this point, it would likely be most logical for them to continue to live as a subsection of Railscasts, if Ryan wished to keep them. I suppose that creating a place for these to all live separate from their original site would also require permission from each author, which would make the whole process an even bigger pain in the ass.

Wouldn't it be nice to be able to access all of these tips within one unified interface? I think so.

Rake Rails Log Stats

There are several log analyzers for all sorts of logs. You can find analyzers for Apache logs, Nginx logs, auth logs, probably fail2ban logs, and on and on. Of course, you can also find several analyzers for Rails logs: Rawk, Rails Analyzer Tools (optionally with the Hodel3000CompliantLogger), Watson, and LogJuicer.

Today, all I wanted to know was the average requests per second for our production app. "I'm sure someone has written a rake or sake task to do this already," I thought, as I typed my search in to YubNub. I was wrong. As far as I can tell from a bit of googling, no one has freakin' done it. So, I did.

Give Me the Goods

A simple rake task for getting the mean, median, and mode requests per second in your production log:

namespace :log do

  desc 'Show average reqs/sec'
  task :reqs_stats do
   
    puts 'Parsing log/production.log...'

    # Log Analyzing Task
    log = File.open("#{RAILS_ROOT}/log/production.log")
     
    reqs_per_sec = []
   
    log.each_line do |line|
      parts = line.scan(/(\d+)(\sreqs\/sec)/)
      # There should only be one match per line... right?
      reqs_per_sec << parts[0][0].to_i unless parts[0].nil?
    end   

    # Find the mode
    nums = {}
    reqs_per_sec.each do |r|
      unless nums.include?(r)
        nums[r] ||= 0
        nums[r] += 1
      end   
    end   
    mode = nums.sort {|a, b| a[1] <=> b[1] }.last[0]
   
    sum = reqs_per_sec.inject {|sum, x| sum + x }

    puts sprintf("%-7s %d reqs/sec", 'Mean:', sum/reqs_per_sec.size)
    puts sprintf("%-7s %d reqs/sec", 'Median:', reqs_per_sec[(reqs_per_sec.size/2).to_i])
    puts sprintf("%-7s %d reqs/sec", 'Mode:', mode)
  end
end

As you can tell, it's pretty simple. Read each line of the production log, parse out the number next to 'reqs/sec', throw them in an array. Calculate.

Dude, You Can Do Better

It could be better. For instance, I could scope it by environment. That wouldn't be too difficult, as several rake tasks do this already. However, I would argue that parsing the development log is rarely necessary, nor is it all that useful. I could probably move each calculation (mean, median, and mode) to separate tasks, so that you can choose to call just one. When would you want to call just one, though?

The first improvement I would make would be to mold it into a useable sake task. Mostly, this would mean simply allowing it to take a file as an argument, which is simple enough. I will probably do this sooner rather than later. I promise I'll post it.

What would you do to improve it? I'm not an expert of anything. I can always learn more, and would greatly appreciate some schooling. (Or, at least suggestions.) So bring it on.

Thor, Sake, and Rails Log Stats

Of course, converting the Rake task from my previous post to a Sake task was simple.

namespace :log do

  desc 'Show average reqs/sec of provided log file. Usage: sake log:stats FILE=/path/to/file'
  task :stats do |task, args|
    file = ENV["FILE"]
    puts "Parsing #{file}..."

    # Log Analyzing Task
    log = File.open(file)

    reqs_per_sec = []

    log.each_line do |line|
      parts = line.scan(/(\d+)(\sreqs\/sec)/)
      # There should only be one match per line... right?
      reqs_per_sec << parts[0][0].to_i unless parts[0].nil?
    end

    # Find the mode
    nums = {}
    reqs_per_sec.each do |r|
      unless nums.include?(r)
        nums[r] ||= 0
        nums[r] += 1
      end
    end
    mode = nums.sort {|a, b| a[1] <=> b[1] }.last[0]

    sum = reqs_per_sec.inject {|sum, x| sum + x }

    puts sprintf("%-7s %d reqs/sec", 'Mean:', sum/reqs_per_sec.size)
    puts sprintf("%-7s %d reqs/sec", 'Median:', reqs_per_sec[(reqs_per_sec.size/2).to_i])
    puts sprintf("%-7s %d reqs/sec", 'Mode:', mode)
  end
end

So...

> sake -i http://pastie.org/212542.txt
> sake log:reqs_stats FILE=/path/to/file
Parsing production.log...
Mean:   55 reqs/sec
Median: 90 reqs/sec
Mode:   110 reqs/sec

Stand and Face the Might of Thor!

Thor purports to replace Rake and Sake, at least for system scripting. It can do the same things Rake and Sake can do, but it does them while actually looking like (mostly) plain Ruby, and, on top of that, it makes dealing with command line options and argument super freakin' simple. I'll admit, I was on the fence about Thor until actually writing this script:

# module: rlog

class RLog < Thor

  desc 'stats FILE', 'view reqs/sec stats from a Rails log'
  def stats(file)
    puts "Parsing #{file}..."

    reqs_per_sec = parse_log_file(file)

    puts sprintf("%-7s %d reqs/sec", 'Mean:', mean(reqs_per_sec))
    puts sprintf("%-7s %d reqs/sec", 'Median:', median(reqs_per_sec))
    puts sprintf("%-7s %d reqs/sec", 'Mode:', mode(reqs_per_sec))
  end

  private

    def parse_log_file(file)
      # Log Analyzing Task
      log = File.open(file)

      reqs_per_sec = []

      log.each_line do |line|
        parts = line.scan(/(\d+)(\sreqs\/sec)/)
        # There should only be one match per line... right?
        reqs_per_sec << parts[0][0].to_i unless parts[0].nil?
      end

      reqs_per_sec
    end

    def mean(reqs_per_sec)
      sum = reqs_per_sec.inject {|sum, x| sum + x }
      sum/reqs_per_sec.size
    end

    def median(reqs_per_sec)
      sum = reqs_per_sec.inject {|sum, x| sum + x }
      reqs_per_sec[(reqs_per_sec.size/2).to_i]
    end

    def mode(reqs_per_sec)
      # Find the mode
      nums = {}
      reqs_per_sec.each do |r|
        unless nums.include?(r)
          nums[r] ||= 0
          nums[r] += 1
        end
      end
      nums.sort {|a, b| a[1] <=> b[1] }.last[0]
    end

end

I am going to assume you haven't installed Thor. If you have, you know which step to skip:

> sudo gem install thor
> thor install http://pastie.org/212189.txt
> thor list
Tasks
-----
r_log:stats FILE   view reqs/sec stats from a Rails log
> thor r_log:stats /path/to/file
Parsing production.log...
Mean:   55 reqs/sec
Median: 90 reqs/sec
Mode:   110 reqs/sec

And verily, thine hour of judgment is at hand!

Thor is cool. There's no doubt about it. It does indeed make dealing with options in Ruby scripts trivial (although I obviously haven't really used that yet), and it's nice that it is less DSL and more plain Ruby. I don't understand why Thor hasn't gotten the same amount of love that Sake got when it was debuted. It makes me wonder if people love the Rake DSL more than they love Ruby. Sinners! Blasphemers! Hipsters! If I can make Thor do anything I would want to do with Sake, or, even better, anything I would want to do with any old bash script, then I am sure I will be using Thor quite often. You should, too.

Go checkout wycats initial post about Thor. Also, go look at the Thor source on GitHub. Write a couple Thor scripts and tell me what you think. Or, tell wycats what you think.

Art Thou Mad?

Where was I? Oh, right, the Rails log stat scripts. Yup. There they are. What do you think? Seriously, what could be added (that it would make sense to add to a Rake/Sake/Thor script)? What have I done horribly wrong? How poor is my Rubyisms knowledge? Tell me!

I'll leave you with the most common Thor (from Marvel Comics, if you hadn't gotten it by now) phrase:

I Say Thee Nay!

Powered by Drupal - Original Theme by Artinet - Theme Enhancements by Me