19 November 2008

Using View Helpers and Controller Actions from Rails' script/runner

Recently I coded up a controller and view that produces a large data file. It was done this way because it needs to generate the proper URL's and take into account a fair bit of stuff at the view level in our application (like pagination, and the custom URL's we have, so extensive use of our URL helpers and other things at this level). The action takes a long time to run (about 3 minutes), so of course I page cache it. However, 3 minutes breaks the web server and/or Mongrel timeouts in our environment, so won't get served up in production and staging environments.

The solution, and I'd love to hear other ways, as I certainly won't claim this is the best way, was to create a small script/runner script that simply executed this route within our app. However, script/runner doesn't normally give you controller and view layer access, plus it doesn't have the context of a web request, so it won't know say the host it's being run against and so on. However, one can leverage an Integration::Session and manually set the host to get that. Thus, the script becomes as simple as:


require 'action_controller/integration'

session = ActionController::Integration::Session.new
session.host = 'www.yourdomain.com'

session.get_via_redirect '/controller/action'

Note, I'm using get_via_redirect here because the particular action I call does a redirect after it expires the cached page of the action it's redirecting to. If you don't have a redirect going on, then you can just call get instead. This does not output anything of course, but for us, just caches the resulting page, which is exactly what we want.

18 November 2008

Video of DealBase CEO's Demo/Presentation at PhoCusWright Show

Sam Shank, the CEO of DealBase, the hotel deals site I work on, unveiled the site officially at PhoCusWright 2008. The video of his presentation (as well as all the others) is now available. Go here, and then if you hover your mouse over the video you'll see a slide/icon for each company that presented along the top - scroll to the right until you find DealBase, and then click it to watch. Presentation is about 5 minutes, and is nearly all demo and discussion of our advantages, strengths, how the site works, and business model aspects, etc.

I think the demo went great, and I've continued to hear tons of praise and useful feedback. The demo was completely live, no smoke-and-mirrors; Sam was not kidding when he told the audience to go check out the deal he just posted during the demo.

We somehow (I'm truly surprised, but of course I am a bit biased) didn't get picked as a top 6 for the show, but for example, others disagreed as well. Tim Hughes, author of The Business of Online Travel blog, listed DealBase.com in his Top Six pick of 2008 PhoCusWright Travel Innovation Summit finalists. Regardless, interest has been outstanding, and we're really excited. We're still cranking away with new features, and various other improvements. It's a lot of fun!

14 November 2008

DealBase - Check out the new features

Since doing our initial public access to the DealBase.com site, you know, the web site that has the most hotel deals on the web (50x more than anyone else), we've been working hard on improving the experience for users. Today we rolled out the latest major revision to the site, which includes some pretty cool stuff. First, let me list these things out, and then I'll cover a few interesting technical bits that occurred along the way.

  • New home page: yes, a brand new, much nicer home page. This really helps tell you what's great about the site. It also has a new search box that I call the "omniscient search" - a single search field that auto-completes on our deal locations and hotels, which is much nicer than the separate search boxes we had before. This same search box is also used everywhere else on the site.

  • Deal filtering: this was a big one. Seems fairly simple on first blush, but lots of interesting stuff going on in the background. You can now filter deals on any deal listing page, by various criteria. You can combine filters too. So, for example, you can narrow down the deal listing by dates, prices, and hotel ratings, allowing you to, for example, look for 4 star hotel deals valid from April through August of 2009, in a certain price range, for a given city. Very handy. Here, take a look at the deals for Hawaii page as an example.

  • Deal sorting: in addition to the filtering, you can now sort the deal listing a myriad of ways, from most percent savings or most dollar savings to high or low rates, or most recently posted, etc. Combining this with filters allows you to really narrow down what deals are best for you.

  • Various UI/visual improvements. Meagan, our talented designer, has done a lot of work here.

  • Speed improvements. Various database queries and other operations for the site have been sped up, sometimes in small amounts, sometimes in extremely drastic ways.

  • More deals: we should probably have about 10,000 deals on the site by the time you read this. This is very exciting for us, and shows how serious we are. These are all very real deals, no link bait, no BS. These are true deals, checked by our team of editors. This gives us about 50x more deals than any other hotel deals site.

  • Comments! You can now comment on deals. No login required (just like the rest of the site). Comments do get reviewed, and we'll be watching for spam and so forth, but this is a great way to tell other people about a good deal or a hotel you like, etc. Comment box is at the bottom of a deal's page. For example, check out this wild personal fireworks show deal at the Ritz-Carlton in New York.

  • Chrome browser support. DealBase works and looks great in Chrome.

And now, since this is a geek blog anyway, a few technical bits...

  • We're running Rails 2.1.2, which is the latest release of Rails as of this writing. We try to stay up to date regardless, but this was a key release, as it fixed some tricky ActiveRecord named_scope issues when using SQL JOINs. Our filtering and other work requires various JOINs and the fixes here prevented us from having to explicitly hand craft a bunch of queries. Thanks Rails team.

  • My current favorite gem is Ryan Bates' scope-builder. This is just so nice for building up big, conditionally chainged named_scopes. As you can imagine this is heavily used in building up combined filtering and sorting of deals.

  • More jQuery goodness. I continue to love jQuery, and use it extensively. It is used heavily in the filtering features, pulling in some nice slider UI elements, and also using it for the "updating" status and dimming effects when the AJAX filtering operations are running.

  • One issue we ran into with Chrome was using the :cache feature of Rails' javascript_include_tag. If we used this to combine and create a cache file of a bunch of separate JavaScript files, Chrome failed to properly load/parse the resulting JavaScript file. This broke pretty much everything JavaScript wise in Chrome, but the simple fix was to not use :cache to achieve this.

  • As a helpful, and economical testing tool, we've been using CrossBrowserTesting.com to give us VM's of a slew of different OS and browser combinations. I tend to run VMWare Fusion and do a lot that way, but it's also a pain to keep up a bunch of different VM images, or have to fire that up for a quick test, etc. We're also using BrowserCam.

  • Finally, another shout-out to the Hoptoad service/folks. This continues to be an outstanding service for us. It works really really well, and it's free, so to me it is the winner amongst the competitors.

  • We've done a fair number of modifications to our tagging plugin (acts_as_taggable_on_steroids), although they're all particular to our app, so not sure if any will get contributed back. Things like enforcing all our rules about tag naming and so on. The same goes for the will_paginate plugin. But in this case, I'm hoping to contribute these back as soon as I can properly contribute the patches and ensure they'll work in any app.

I'm sure there's more, but that's what I can think of for the moment. It's been a busy couple weeks, and I'm really excited about the state of the site these days. We've been getting some great feedback, and I've had a few friends book deals they've found on the site (one friend saved $800 on a trip!). If you have feedback, don't hesitate to add a topic or question in our Get Satisfaction feedback system. This tells us what things you'd like to see, or any problems you're finding, etc.

04 November 2008

Speed Up and named_scope acts_as_taggable_on_steroids Finds

I use the acts_as_taggable_on_steroids plugin for tagging. I've been happy with it, but recently have been adding a lot of searching, sorting, filtering, etc. functionality to an app, and needed the find by tag functionality to work as a named_scope, so that I can have it within a chain of many named_scope finders.

This turned out to be trivially easy to do (without having to copy the SQL and put that into my named_scope). To add a "tagged_with" named_scope to your model that is already acts_as_taggable, you can just do this:

named_scope :tagged_with, lambda { |tags| YourModel.find_options_for_find_tagged_with(tags) }

I've been doing a lot of benchmarking and performance improvements to our SQL as well, and decided to see if this was any different in performance compared to just doing YourModel.find_tagged_with that acts_as_taggable_on_steroids adds. As it turns out, the named_scope version, which is really identical at the core, is faster, especially if called more than once (per thread/Rails request)! Here's the benchmarks to prove it:

Single query/1 Iteration:
user system total real
named_scope 0.040000 0.000000 0.040000 ( 0.045085)
find_tagged_with 0.020000 0.010000 0.030000 ( 0.108233)

10 Iterations:
user system total real
named_scope 0.030000 0.000000 0.030000 ( 0.040282)
find_tagged_with 0.420000 0.030000 0.450000 ( 2.040245)

I repeated these multiple times and got the same/similar results each time. So, for a single query, it's only about 2x faster, but if you start issuing this same find multiple times per request then I believe it's the Rails query caching that kicks in with named_scope, but apparently not with the generic find with all the options (I'd love to hear some commentary on this from someone who knows the details).

Regardless, using a named_scope is nice because now you can more easily chain a tag find together with other named_scope items.