Smells Like a Google Search - Easily Grab Inbound Search Terms in Rails Using search_sniffer

Sure you can mine your referrers in Google Analytics, but why not tailor your site to your users by peeking at what they were looking for when they found your site? The search_sniffer plugin makes it too easy to do just that.

Make your site sticky, give ‘em more of what they want

Once you get folks to your site, the goal is to keep them there. On MindBites, we wanted to provide additional suggested content based on any search terms that led them to the site. The good news is that getting these terms is as easy as parsing the HTTP_REFERER from the HTTP request. Unfortunately, the format of this url differs between search engines.

With nearly 70 percent market share let’s take look at a Google example. A search for “Ruby on Rails Houston” from the Firefox search box results in the following URL:

http://www.google.com/search?q=ruby+on+rails+houston&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a

We only care about the q parameter, “ruby on rails houston.” Since “on” is such a common word, we really only care about “ruby rails houston.” The SearchSniffer plugin includes a list of common English words that are removed from the search terms by default.

How do I get it?

Installation is easy from Github

script/plugin install git://github.com/squeejee/search_sniffer.git

How do I use it?

class ApplicationController < ActionController::Base
    before_filter :sniff_referring_search

    ...
end

The plugin populates the @referring_search object containing info that can be passed to a keyword highlighter or internal site search engine to pull related content.

@referring_search.search_terms 
  => "ruby rails houston" 
@referring_search.raw
  => "ruby on rails houston
@referring_search.engine
  => "google

The plugin supports all the major search engines including Google, Yahoo!, MSN, and more.

Let us hear from you!

If you find it useful, or have ideas for improvement, let us know!

  •  

Mulling Over Our Ruby on Rails Full Text Search Options

There are quite a few choices when it comes to adding a full text search in a Ruby on Rails application. We thought that had considered all of our options when we ultimately settled on using Sphinx / Ultrasphinx. We learned otherwise, though, after stumbling across Xapian / Acts_As_Xapian while trying to find a fix for our Sphinx implementation after a production build.

Here are the details of our thought process and how we ultimately ended up deploying with Xapian.

While looking for a full-text search engine when Mindbites was released a year ago, we were looking for something easy and quick. We ended up going with Douglas Shearer’s Acts_As_Indexed which worked out great. It was written entirely in Ruby and very easy to implement with automatic indexing. (ie No cron jobs needed to keep the index up to date.) If you have a simple site and want to implement a basic search very quickly, definitely give Acts_As_Indexed a look.

However, over the past month or so we decided it was time to find something a little more robust. We wanted the features that the full-blown full-text search engines give such as spell correction, stemming (ie “connection / connecting / connected” would all search for words containing “connect”), finding “similar” results, and the ability to work across multiple servers. We were also having a few index corruptions with Acts_As_Indexed that we were wanting to get away from with another tool.

We went through the usual suspects that are often seen in the Rails Community:

Solr / Acts_As_Solr

Very robust search server based on the Lucene Java library with a mature acts_as_solr Rails plugin. I was blown away by Erik Hatcher’s “Solr On Rails” talk at RailsConf 2007 and thought this could be a good fit. However, we decided to check out other options because, all things being equal, we would rather not deal with installing Java on our servers.

Ferret / Acts_As_Ferret

We nixed Ferret fairly quickly after hearing a few horror stories about corrupted indexes among other issues with Ferret on production servers.

Sphinx / Ultrasphinx

Sphinx appears to be the new defacto standard for full text search among Rails developers. From what we read, it is very powerful, very fast indexing, and easy to use with Ultrasphinx. This was our choice to replace Acts_As_Indexed. However, after about a week’s worth of development and a deployment to our production server, there still were a lot of mysteries to the Sphinx.

We were not thrilled with all of the config files involved. With Ultrasphinx, you will need a xxx.base file which is accessed via a rake task to generate a config file that Sphinx can use. This works, but I was hoping for something a little bit simpler.

We were also not big fans of the daemons that run in the background. In our ITG environment, the daemons decided to stop running on a couple of occasions which caused a 500 error to be thrown when searching. (We later fixed this issue by reindexing and restarting the daemon with each Capistrano deployment.) Lastly, I read this quote from the Ultrasphinx deployment notes about recommended cron jobs for Sphinx on your production server:

“The first line reindexes the delta index every 10 minutes. The second line reindexes the main index once a day at 4am. The third line will try to restart the search daemon every three minutes. If it is already running, nothing happens.”

Kicking off a job every 3 minutes just to make sure another job is running did not seem right to me.

Xapian / Acts_As_Xapian

I had never heard of Xapian before this past Thursday. I happened to stumble across this article on on Evan Weaver’s blog while I was researching a Sphinx issue with our production build. Always looking for new and better ways to do things, I took a closer look at Xapian. Within 15 minutes, I had Xapian installed on my local Ubuntu machine and was successfully searching Mindbites lessons using the acts_as_xapian plugin. I spent the rest of the weekend replacing our Sphinx code with Xapian.

Installation, configuration, and deployment all went so well over the weekend that we deployed our completely revamped search code to our Mindbites production server this past Monday with spelling corrections, stemming, and “You may also like” functionality intact.

As a side note, that same Evan Weaver blog post has a comment about a plugin called act_as_searchable using the Hyper Estraier full-text search system. I have not looked into this solution, but I would be curious to hear from other readers who have tried this.

In part 2 of this blog post, I will go into detail about our Rails Xapian implementation.

  •  

It’s Double Coupon Days! Generate and Redeem Coupons With the acts_as_redeemable Rails Plugin

Plugins are an excellent way to share functionality across your Rails applications. When we needed the same coupon and invite functionality across multiple apps, we decided to create acts_as_redeemable. Hopefully you’ll find it useful, too!

What is it?

acts_as_redeemable adds redemption capability to a model for items like coupons, invitation codes, etc. Each redeemable gets a unique code upon creation that can be sent in an email or printed as a coupon code.

Installing

You can install the plugin from Github:

script/plugin install git://github.com/squeejee/acts_as_redeemable.git

or if you’re on an older version of Rails, you can download the tarball.

How do I use it?

If you don’t have a model created already, go ahead and generate one:

script/generate redeemable Coupon
rake db:migrate

Make your ActiveRecord model act as redeemable

class Coupon > ActiveRecord::Base acts_as_redeemable :valid_for => 30.days, :code_length => 8 # optional expiration, code length end

Create a new instance

Now that you’ve created and configured your model, it’s time to create your coupon.

c = Coupon.new
c.user_id = 1 # The user who created the coupon 
c.save
c.code 
# "4D9110A3" 
c.created_at  
# Fri Feb 15 14:56:37 -0600 2008
c.expires_on 
# Fri Mar 16 14:56:37 -0600 2008 

Of course, you’ll have other business logic for your coupon, discount, invite or other redeemable item. We just spot you the generation, redeeming, and expiration functionality. If you’re sending coupons via email, be sure and check out Slantwise Design’s excellent acts_as_invitation plugin.

Copyright© 2008 Squeejee, released under the MIT license

  •  

Easily Switch Between Rails Development Sites With Phusion Passenger

mod_rails Phusion Passenger is gaining some steam as it makes deploying Rails apps easier. Passenger also makes it easier to switch between your Rails projects during development.

Multi-tasking, Rails style

If you’re like me, you often find yourself flipping between two or three different Rails projects. Each time you flip, you do the same little dance in Terminal:

wynn$ cd projects/blog 
wynn$ mate . 
wynn$ script/server

You code along happily on the first project, knock out a couple of bugs, and then it’s time to switch. Either you stop your first mongrel server and dance the terminal jig all over again, or you just cmd+T a new terminal tab and crank up project two on a different port, like :3001 (and remember to specify the new port when you crank up your browser).

Phusion Passenger, not just for your production boxes

Passenger eliminates the need to start up your own mongrel server for each of your development Rails apps. Just like your PHP days, you simply set up an Apache virtual host for each app, and Passenger does the rest.

Step 1: Set up your host names

To set up your host aliases for each domain you want to use, edit your /etc/hosts file. Here’s mine:

127.0.0.1 beta.mindbites.local
127.0.0.1 mindbites.local

Step 2: Install Phusion Passenger

Assuming you have Apache2 installed (bundled with Leopard, an apt-get install away on Ubuntu):

sudo gem install passenger 
sudo passenger-install-apache2-module

The install script will chug away for a few minutes. Once on the other side, you’re only a couple steps away from multi-tasking bliss.

Step 3: Set up your virtual host

On OSX, you’ll need to use Headdress or modify /etc/apache2/httpd.conf with your virtual hosts:

NameVirtualHost *  
<VirtualHost *:80> 
        DocumentRoot /Users/wynn/projects/mindbites/public 
    ServerName mindbites.local 
    ServerAlias beta.mindbites.local 
    RailsEnv development 
    <directory "/Users/wynn/projects/mindbites/public">    
        Options FollowSymLinks    
        AllowOverride None    
        Order allow,deny    
        Allow from all 
    </directory> 
</virtualhost>  
<VirtualHost *:80>
    DocumentRoot /Users/wynn/projects/blog/public 
    ServerName locomotivation.local 
    RailsEnv development 
    <directory "/Users/wynn/projects/blog/public">
        Options FollowSymLinks    
        AllowOverride None
        Order allow,deny
        Allow from all
     </directory> 
</virtualhost>  
<VirtualHost *:80>
    DocumentRoot /Users/wynn/projects/goalistics/public 
    ServerName goalistics.local 
    RailsEnv development 
    <directory "/Users/wynn/projects/goalistics/public">
        Options FollowSymLinks
        AllowOverride None
        Order allow,deny 
        Allow from all
  </directory> 
</virtualhost>

After initially setting up my virtuals, I discovered that only the first one worked. A quick look on Google pointed me to the NameVirtualHost * directive which solved the problem. Note that the DocumentRoot /Users/wynn/projects/goalistics/public directive points to the public folder within your Rails app, not the root. Second, you’ll need to include the directory overrides in the Directory block to allow Apache to serve up your static content. (I was shocked when my app was served up naked the first time).

Also note that the RailsEnv directive determines the RAILS_ENV for the application and is production by default.

Wrap-up

I’ve been really pleased with Phusion Passenger so far. I discovered one hiccup, though. Passenger does not seem to care for inline ERB code in your database.yml so I had to side step some dynamic configuration we were doing. Error pages from Phusion are much nicer on the eyes though than the standard Rails versions. Also, restarting Rails applications are as easy as bouncing Apache or:

sudo touch tmp/restart.txt

from the project root. Passenger is definitely worth a look on your development machine.

  •  

Redesign Your Site in Place Using Rails Custom Mime Types

After reading Ben Smith’s classic iPhone on Rails 2 article and implementing our own iPhone site after the jump to Rails 2, I was struck at how easy it is to create you own custom mime types to serve up different content to different devices.

When the time came to redesign the main MindBites site, I didn’t look forward to a lot of branching and merging, no matter how easy Git makes it. Then it hit me. Why not create a custom mime type and serve up a new layout and views for beta users? Let’s take a look at how we’re doing just that.

Step 1: Register your custom mime type

The first thing we need to do is tell Rails we’ve got a new format type to serve up:

# config/environment.rb

Mime::Type.register_alias "text/html", :iphone
Mime::Type.register_alias "text/html", :beta

We used beta, but you could use any value here not already in use.

Step 2: Detect a beta request

In order to let only the cool folks into our new beta site, we’ll need a big burly bouncer at the door keeping out the riff-raff:

# application.rb
before_filter :adjust_format_for_beta

...

def adjust_format_for_beta
  request.format = :beta if beta_request? 
end

def beta_request?
  return (request.subdomains.first == "beta" || params[:format] == "beta")
end

The before_filter executes before each request and if ‘beta’ is the first subdomain or passed in as a format param, Rails will set the format to :beta.

Step 3: We’re in da club, now what?

Once we’ve identified our beta users, we now need to give them the new eye candy. We do this simply by dropping in a new view named xxxxxx.beta.erb. The bulk of the changes for our redesign are in a new application layout which serves up a new basic layout and CSS.

application.html.erbapplication.beta.rb

Other views (even partials) are swapped out in a similar manner:

show.html.erbshow.beta.erb

_lesson.html.erb_lesson.beta.erb

And that’s it! You don’t have to do every view in the site because Rails will fall back to .html.erb for any views without a .beta.erb version. Most of our redesign is accomplished in CSS. Where we have massive markup changes, we simply swap out the view templates.

See update 2 below

A gotcha to watch out for

In a few cases, we had respond_to blocks in our controllers to handle differences between the iPhone and regular versions of the site. Anywhere that you have these, you’ll need to add the beta mime type as well:

respond_to do |format|
  format.html
  format.beta # &lt;= You'll get a 406 Unprocessable entity error without this line
  format.iphone do 
    render :layout =&gt; false if request.xhr?
  end
end

If an action does not already have a respond_to block, you’re OK. No changes are required in your controller.

UPDATE

Thanks, Ryan Heneise, for reminding me about the last step:

Step 4: What is new is old again

When you’re ready to launch, you’ll want to fold all those beta views back into your project as the default .html.erb views. Simply rename them from .beta.erb to .html.erb. For us, it’s a bit easier since we don’t have to overwrite any .html.erb files. We’re still using .rhtml!

Thanks, Ryan!

UPDATE 2

Paul Canavese turns in some excellent QA work and informs me that Rails will not fall back to .html.erb. Our templates are still .rhtml. So it would appear you will have to touch each template or use .rhtml. Sorry!

  •  

jQuery Parallax Scrolling - Build Your Own 1980’s Video Game!

What is old is new again. Inspired by the recent crop of css parallax techniques. I thought it would be cool to take it one step further and introduce parallax scrolling. Thus the jQuery Parallax plugin was born.

What the heck is a parallax anyway?

Paul Annett of Clearleft does a far better job of explaining the css parallax technique than I could. If you’re unfamiliar with this layer-stacking technique, jog over to Vitamin and get the scoop.

That’s pretty cool, but why do I have to resize my browser to enjoy it?

For no practical reason whatsoever, I thought it would be cool to animate the layers in a CSS parallax and give the scene a depth of field that you can’t convey with static layers. As with all my jQuery endeavors so far, I found this surprisingly easier than I thought.

Step 1: Build the layered scene

Just in case you’re reading this and were served one of the other parallax scenes on the site, here’s a look at the finished product.


Locomotivation.com header


For our simple western landscape, we’ll construct the scene from four layers:

  1. poles.png Through the magic of repeat-x, a single telephone pole with power lines becomes a never-ending array of energy delivery
  2. logo.png Adding the logo to the scene creates an even greater illusion of depth. The slight gradient also conveys depth as items further in the distance have more light
  3. windmill.png A lone windmill is set off in the distance
  4. mountains.png Just like our telephone pole, this mountain range goes to the end of the earth
  5. sky.png Finally, that pretty blue sky is a tiny image only one pixel wide, repeating on the x-axis


CSS Parallax diagram


Step 2: Add the required Javascript references

To animate our parallax we’ll use my jQuery Parallax plugin which requires jQuery 1.2.x and the jQuery Easing plugin (for acceleration and deceleration).


<script src="/javascripts/theme/jquery-1.2.6.min.js" type="text/javascript"></script>
<script src="/javascripts/theme/jquery.easing.1.3.js" type="text/javascript"></script>
<script src="/javascripts/theme/jquery.parallax.js" type="text/javascript"></script>


Step 3: Wire up the parallaxes (what a fun word to say!)

Now it’s time to attach our parallax behavior to the container and each slice in our scene.

jQuery Parallax diagram


jQuery(document).ready(function() {

    jQuery("#header").parallax("create");
    jQuery(".scene-1 .slice-1").parallaxSlice("create", {speed: 5});
    jQuery(".scene-1 .slice-2").parallaxSlice("create", {speed: 3});
    jQuery(".scene-1 .slice-3").parallaxSlice("create", {speed: 1});

});


Step 4: Sit back and enjoy the show!

Again, this little demo has very little practical value. Maybe I should do something more constructive with it like, recreate the first level of Contra

  •  

ImageMagick OS X Installer

Installing ImageMagick / RMagick on OS X does not have to be a hastle. There is a very handy install script on RubyForge that does all of the work for you.

http://rubyforge.org/projects/rmagick/

Just be sure you have the following prereqs already installed on your Mac:

  • XCode Tools
  • X11 SDK
  • X11.app

Also, even though the script is supposed to include RMagick Ruby library, that piece did not work for me. Luckily, a simple “sudo gem install rmagick” worked like a champ for me.

  •  

Syntax Highlighting Blog Posts

After setting up this blog on the amazing new Expression Engine 1.6, we needed a way to share code snippets on blog posts. I found a number of utilities that will preformat your code, injecting markup around keywords that you can then style to taste. However, most of these solutions were language specific and cumbersome. One of the best tools I found was Pastie. With its built-in API, it would have been a nice fit had I been running a Rails-based CMS.

Most of the tools I found were Wordpress plugins which doesn’t help out an EE fan like me. I did find a great EE plugin for PHP code, but we tend to sling much more Ruby (and Rails), SQL, HTML, Javascript, and CSS on this blog.

Perhaps we were asking too much but we simply wanted to format our code within an entry with:

  1. No special markup required
  2. Multi programming language support (including the ones I haven’t learned yet)
  3. CSS based styling

The solution

I remember liking how Javascript Fu Master Dan Webb formatted code within posts on his blog and was pleased to find a link to his CodeHighlighter project on his site. CodeHighlighter is a 100% client-side solution, using Javascript to perform configurable Regular Expression matches to inject CSS classes around keywords, constants, and other language constructs. Supported languages include Ruby, HTML, CSS, and Javascript, however you can add support for your favorite language in literally a few minutes. CodeHighlighter doesn’t offer SQL highlighting out of the box, so that’s exactly what we did.

Step one: set up the scripts Simply copy the CodeHighlighter.js and other language specific scripts to your server and add the script tags to your page:

Step two: insert your code block Next, add your code block to your post:

<pre><code class="html">
  your code here
</code></pre>

Note the CSS class on the code element. This determines the language formatting that CodeHighlighter should apply to your code.

Step three: set up your styles The last step to syntax highlighting bliss is to add language specific CSS rules to your stylesheet:

.javascript .comment, .ruby .comment, .sql .comment { color : #9c6; font-style: italic; }

.javascript .string, .ruby .string, .sql .string { color : #9c6; }

.javascript .keywords, .ruby .keywords, .sql .keywords, sql-keyword { color : #679ef1; }

.javascript .global { color : blue; }

.javascript .brackets, .ruby .brackets { color : red; }

.css .comment { color : gray; }

.css .properties { color : navy; }

.css .selectors { color : maroon; font-weight : bold; }

.css .units { color :red; }

.css .urls { color :green; }

Adding support for SQL (well actually MySql)

To support SQL, I simply cloned the Javascript rules (javascript.js) as sql.js and edited the Regular Expression rules as necessary.

CodeHighlighter.addStyle(“sql”,{ comment : { exp  : /(—[^n](n|$))|(/[^]*+([^/][^]*+)/)/ }, string : { exp  : /’[^’]’|”[^”]*”/ }, keywords : { exp  : /b(ADD|ALL|ALTER|ANALYZE|AND|AS|ASC|ASENSITIVE|BEFORE|)b/ }, global : { exp  : /b(NULL)b/ } });

All I had to do was copy this file to the server and Jim’s first MySql post just got a bit more purty.

Drawbacks

While CodeHighlighter is a super easy, unobtrusive way to provide syntax highlighting in a server agnostic way, it’s client-side nature does mean you may see a slight delay before your code becomes formatted as the page loads before the script initiates. Also, as you can see with my SQL example, the rules files may get quite large, 8k in our case. If any RegEx gurus are reading, let me know if you can match a set of words in upper, lower, and mixed case for case insensitive languages like SQL. That would cut the sql.js rules file in half!

  •