Archive for July, 2009

Plug-in Load Order in pre-2.0 Rails

Wednesday, July 29th, 2009

In Ruby on Rails, you can specify the order in which plug-ins will load.

In Rails 2.0, if you want to put a few plug-ins at the top of the order, and you didn’t really care about the rest, you can do so with this syntax:

config.plugins = [:my_first_plugin, :my_second_plugin, :all]

Prior to Rails 2.0, in Rails 1.2.3 for example, you have to specify every plug-in in order to take advantage of this feature because the :all option is not supported. This is a pain for those of us who have some Rails 2.0 apps and some pre-2.0 apps. Especially when we do a lot of plug-in work. Below is a workaround. It takes your array of preferred plug-ins and then appends the rest. That may or may not be what Rails 2.0 does with the :all symbol.

order = ['my_first_plugin', 'my_second_plugin']
others = Dir.new("#{RAILS_ROOT}/vendor/plugins").select{|f|File.directory?("#{RAILS_ROOT}/vendor/plugins/#{f}") && f[0].chr!='.'}.sort
config.plugins = (order << (others-order)).flatten

And without the line breaks:

order = ['my_first_plugin', 'my_second_plugin']
others = Dir.new("#{RAILS_ROOT}/vendor/plugins").select{|f|File.directory?("#{RAILS_ROOT}/vendor/plugins/#{f}") && f.[0].chr!='.'}.sort
config.plugins = (order << (others-order)).flatten

How to Configure PuTTY’s Log File

Tuesday, July 28th, 2009

PuTTY doesn’t seem to have a command-line way of turning on logging and specifying a log file in which to output session data. That’s rather annoying, but there is a workaround. This information is stored in the Windows Registry for the Default Session. So if you edit the LogFileName string and LogFileType DWORD values for the Default Session key, you can specify the log file without having to use the GUI. This is helpful in unattended scenarios.

The path to the key is:
HKEY_CURRENT_USER
    \Software
        \SimonTatham
            \PuTTY
                \Sessions
                    \Default%20Settings

The best part is: If you only create the LogFileName string and LogFileType DWORD values, PuTTY will create the rest later. This means you don’t have to create the complete key which contains dozens of different values.

You can use the Windows Registry Editor (the regedit command) to navigate your way to the proper key and create the values. Note that none of the keys will exist if PuTTY has not been run before.

If you want to do it in a completely unattended manner, you can save the values to a .reg file and use the reg import command. Save the text below to a text file named, for example, putty_settings.reg

Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\Sessions\Default%20Settings]
"LogFileName"="..\\up\\and\\over\\to\\mylogfile.log"
"LogType"=dword:00000002

Then import it using the command below.

reg import putty_settings.reg

Tips

  • The reg import command is very picky about formatting. Make sure the first line of the .reg file is exactly as shown above.
  • Backslashes need to be doubled. For example, ..\up\and\over needs to be typed as ..\\up\\and\\over
  • I found that All session output is 2, so I set LogType to 2 in the registry. I did not test any other values.

Below is a screenshot of PuTTY before the registry key

putty_normal

Below is a screenshot of PuTTY after the registry key

putty_after

An Average that is Scalable

Thursday, July 16th, 2009

A normalized database of movie ratings might look like this:

scalable_average_1

A web page might execute the following two queries to display the movie’s information:

select title
from movies
where id = 12345;

select avg(rating) as rating
from ratings
where movie_id = 12345;

To squeeze out a little better performance, the database could be denormalized a bit, and the average rating for the movie could be stored along with the movie. This would result in a performance increase because you wouldn’t have to hit the ratings table to retrieve the movie’s rating.

scalable_average_2

Then you could select the movie’s rating along with the title and whatever else.

select title, rating
from movies
where id = 12345;

You just have to remember to update the movie’s rating every time a user rates it:

update movies
inner join ratings on (movies.id = ratings.movie_id)
set movies.rating = avg(ratings.rating);

However, let’s say you have a very large user base, so the ratings table has a billion or so records. Also, you want the rating process to be as quick as possible, so every millisecond counts. Further, you are processing thousands of simultaneous requests, so you can’t waste any CPU power. You might find this model more appealing. In this scenario, the components of the average are stored in the movies table, and they are incremented each time a movie is rated.

scalable_average_3

The SQL to retrieve the average is below. The difference in performance is negligible.

select title, sum_rating/count_rating as rating
from movies
where id = 12345;

However, the improvement comes when a movie is rated. The SQL below is faster than the previous method because you don’t have to hit the ratings table in order to rate a movie. Keep in mind: you’ll have to rate a billion movies to notice the improvement.

update movies set
  sum_rating = sum_rating + 3
, count_rating = count_rating +1
where movie_id = 12345;

As with all performance testing and tweaking, you need real data and real people. If you compare these three methods to a small database with a few thousand rows using a powerful development computer with one user, you’ll find that there is no difference between them. However, if you have millions of records and dozens of simultaneous users, the second method becomes noticeably faster than the first. When you reach the billion record mark and have thousands of simultaneous users, then you see the third has advantages over the second.

How to Build a Grammatically Correct Sentence Containing a List

Tuesday, July 14th, 2009

Sometimes, you have one or more words that you need to combine into a grammatically correct sentence that may or may not contain a list. For example, you might want your application to write:

  • Apples may be added to your grocery bag.
  • Apples and bananas may be added to your grocery bag.
  • Apples, mangos, and limes may be added to your grocery bag.
  • But you do not want your application to write:

  • Apples, and limes may be added to your grocery bag.
    • The comma is incorrect.
  • Apples mangos and limes may be added to your grocery bag.
    • At least one comma is required.
  • Apples mangos limes may be added to your grocery bag.
    • At least one comma and the word and are required.
  • Apples and mangos and limes may be added to your grocery bag.
    • At least one comma is required and there is an extra and.
  • Apples, may be added to your grocery bag.
    • The comma is incorrect because there is only one item. Thus, it is not a list.
  • Cases of bad grammar like those shown above are plentiful on the Internet. Here’s how you make it right every time in your application, assuming that your list contains at least one item:

        <%
           items = ["apples"]
           items << "bananas" if @bananas_are_available
           items << "mangos" if @mangos_are_available
           items << "limes" if @limes_are_available
           items.push("and #{items.pop}") if items.size > 1
        %>
        <%= items.size<=2 ? items.join(" ") : items.join(", ") %>
        may be added to your grocery bag.
    

    It’s simple. Just:

    • Add your nouns, verbs, or adjectives to an array
    • Prepend and plus a space to the last item, if the array contains more than one item
    • Join the items together separated by a comma plus a space, if the array contains more than two items; otherwise separate by a space only.

    Perhaps this example better illustrates the concept:

        <%
           items = ["lettuce"]
           items << "pork" unless @user_is_muslim || @user_is_jewish || @user_is_vegetarian
           items << "beef" unless @user_is_hindu || @user_is_vegetarian
           items << "fish" unless @user_is_vegetarian
           items.push("and #{items.pop}") if items.size > 1
        %>
        <%= items.size<=2 ? items.join(" ") : items.join(", ") %>
        may be added to your grocery bag.