Improving Heroku Postgres with Support Data

We continuously use support data to identify high impact issues in our platform. Over the past couple of weeks in July, we reduced the volume of support inquiries related to Heroku Postgres by over a third — even as overall usage of the product increased. In this post, we'll tell you that story and a bit about how we do support here at Heroku.

Identifying High-Impact Support Issues

The way we approach customer support at Heroku is two-fold. On the surface, we’re here to answer your questions and help you fix issues with your apps. We also play an integral role advocating our customers’ needs within the company. The best support is the one you don't have to use, and as such, we strive to reduce the overall need to file tickets by bubbling support data up to the teams building the product. They then use this data to help guide their priorities.

One way we do this is by generating regular reports from aggregate tickets over a given period of time. Using an internal app, we pull a random sample of tickets for a given product during the specified time period. We then size the sample to make human review feasible while retaining statistical significance. This app allows us to assign each sampled ticket a “culprit”, producing a high-level grouping of issues. The culprit may be a specific feature, a UI/UX issue or even general guidance and questions. We can also add a freeform note to each ticket. Here is a chart from a report that was created in early July for our Postgres product:

Early

Here we’re able to easily identify pgbackups and questions/guidance as the biggest contributors to customer pain on our Postgres offering. By drilling down into the ticket notes for those groups, we determined the main issue for pgbackups was a stability issue in capturing manual backups. In addition, we received a lot of queries about the use of automated backups versus when to rely on continuous protection.

Taking Action

With this data in hand, we’re able to show our product and engineering teams the very real impact that certain bugs or other issues have on our customers. In some cases, a support engineer can cut off the issue themselves by sending a pull request or updating docs and that's the end of it. With our pgbackups issue, we took this data to our engineering planning meeting and the Postgres team went on to tackle the issues.

First, we introduced better instrumentation around the manual backup process which led to replacing the mechanism used to upload the backups to durable storage. We also wrapped this mechanism in retry logic, resulting in more stable backup captures. Finally, since we had determined more broad confusion around the purpose of continuous protection vs. pgbackups, we published new documentation to address the user questions we saw in support tickets. Of course, as with all software, there are more ways we can improve pgbackups and we will continue to do so. These actions together had a measurable impact on the following week’s report:

Late

In addition to the decrease in the questions/guidance and pgbackups categories, we also saw an overall reduction in Postgres-related tickets. This allows the next issue to surface. The notes from this new report indicate we have a lot of follow-up questions around our automated recovery notifications. The team is now exploring improving communication for these events.

As we rinse and repeat, existing features become more and more solid and newly introduced features are quickly polished.

Lessons Learned

Any ticketing system holds a wealth of knowledge on customer pain points with a product. It's important to look at this data from an appropriate lens. If we look at it too narrowly, it’s difficult to spot trends. If we look at it too broadly, the data loses a lot of its meaning. Striking the right balance allows us to bring actionable data to our product teams that they can use to improve a product and enhance the user experience. Supplying our product and engineering teams with the data they need to make good decisions is one of the primary goals of our support team. We’ll explore more about how our support team works behind the scenes in future blog posts.

Editor’s note: Support is available for all Heroku users through our Help app. For critical production apps and enterprises, our premium support offers guaranteed response times and 1:1 help in running your application.

Building Apps Efficiently on Heroku

Whether you’re building your initial prototype or running a large scale profitable business, there will be times where you will have questions about building, maintaining, or troubleshooting your application. One of our goals is to help make your business successful by empowering you to build quality software with best practices, clear documentation on a stable erosion-resistant platform. When in doubt, there are several channels here at Heroku available to help you get the support you need.

Getting Started

Brand new to development or just new to Heroku there’s a place for you in the Dev Center. As you’re getting started you likely want to setup your application so you can get better visibility. Several tools are recommended, including add-ons for monitoring, add-ons for logging and native tools such as log2viz that provide visibility into application performance and issues.

Throughout the app development process, you will want to take into consideration best practices for application architecture and scaling, The Twelve Factor App provides the recommended methodology for building software-as-a-service apps on Heroku.

Ready to Launch

Moving from development to production is a critical stage. When you’re developing you often don’t worry about uptime or bugs, but in production you want to take extra steps. The best place to start is through Heroku’s built in production check which runs your app through a checklist for maximizing uptime and ensuring optimal performance. Items in the production check include using Cedar, dyno redundancy, DNS & SSL, production databases and appropriate visibility and monitoring.

I’m stuck, now what?

When additional support is required, Heroku’s Help App provides a guided experience for accessing documentation and resources to get answers quickly. Help App aggregates content from both Heroku documentation and community discussions forums like Stack Overflow where Heroku engineers are very active. If questions are left unanswered, users have the ability to get in touch with Heroku support engineers from Help App. While free standard support hours are 9am–9pm ET without an SLA, Premium Support offers 24×7 coverage with 2-hour response time (this is the maximum; most premium support tickets are answered within a few minutes). When you aren’t able to find the answer yourself, Heroku support engineers are here to help.

Common issues that support can assist with include setting up DNS with root domains, securing a site with SSL, and migrating data between database plans. We are also able to help guide customers with more advanced needs such as difficult scaling techniques, app and database performance. When sending a support ticket, it’s a good idea to first perform the steps outlined in the above sections and explain what you’ve already tried.

I need more

In addition to Premium Support, Heroku offers individualized attention and support to your app through a dedicated Heroku engineer, called a technical account manager. Technical account managers offer the advantage of someone having immediate context and background on your apps. They also review your apps end to end for architecture, configurations and optimizations you may not have considered and will make recommendations on a regular basis.

Early access

Premium support services give you first access to new features that might be of help in making your app perform faster and more reliably, making teams of developers more productive. Some prior examples include early access to to 2X dynos, PostGIS and Fork and Follow before any were publicly announced.

We’re here to help

To learn more about best practices, guaranteed response times and support for critical apps, our customer advocacy team provides free introductory 1:1 assistance. Get in touch today.

The Best of Cheat

There’s some real gold in Cheat. Like, nuggets. Ever since we got tagged by mustache it’s been just wild.

Ever wonder how to make hot chocolate? We’ve got a tasty recipe.

Need some night time reading to go along with your cocoa? May I suggest Into the Code?

The DataMapper sheet is superb in its brevity: has_many :class => “ClassName”

(By the way, this post has been highly optimized with the SEO sheet.)

The Foo sheet is the foo sheet is the foo sheet.

Hot chocolate not hitting the spot? How about some Gazpacho soup?

If you’re a designer, you’ll love this: lipsum. All of it.

Ever wanted to talk like you’re in the military, or own a walkie talkie? Now you can with this handy military alphabet.

Oh, an entire Cheat Emacs mode. Remember: Never quit Emacs.

I’m sure you’ve been wondering, so here it is: every TLD ever.

In the spirit of three letter acronyms, there’s also the GTD sheet.

One of my favorites, the Runescape sheet:

Hot keys

1 selling
2 buing
3 buy
4 noob

For the serious kids in the room, there’s the Unix permissions and Unix redirection sheets. Oh, and the notify trick then the complete Firebug sheet.

Finally, the nonsense sheet imparts on us some non-nonsense wisdom: “and that’s why cheat also is an anagram for teach!”

Huba Huba

Surely, by now, you’ve heard of GitHub. (Don’t call me surely.) It’s totally the Indiana Jones of repository hosts. Feel free to stalk Pj and I to see what we’re up to. Blogging be damned!

If you haven’t heard of GitHub, there are tons of posts explaining the hows and whys of its awesomeness. This is not one of the posts. Instead, I want to quickly share some oft overlooked but tasty GitHub tidbits.

The GitHub Gem

GitHub supports gems, which is cool, and also means we can install the official GitHub gem with ease:

$ gem install defunkt-github --source=http://gems.github.com/

Great. At this point, possibilities become reality. The gem has a few cool features, all of which are displayed via $ github -h, but the best feature by far is pull.

Here’s how it works: I have my fork of technoweenie’s exception_logger. I’ve cloned it and am sitting in the working directory. Suddenly I discover ryanb (of RailsCasts fame) has sent me a pull request. Open source’s finest moment.

So, I type $ github pull ryanb. A remote is added, a new branch is created, and Ryan’s changes are pulled into that branch. (It’s probably named ryanb/master.) I then review the changes and, if they rock, either rebase or merge them back into master. Like this:

$ git checkout master
$ git merge ryanb/master
$ git push

Already reviewed the changes on the web and know they’re legit? Just $ github pull –merge ryanb. This’ll grab the changes and merge them into master for you. Oh, right, you can also specify a branch. The assumption is master, but you know what they say about assumptions: you’re a jerk.

Thus: $ github pull—merge ryanb weird_branch

And just like that, GitHub pull requests are no longer a pain in the ass.

But really, this is just start. Please please please fork the gem and add awesome features. github clone, anyone?

Keyboard Shortcuts

Let’s say you want to peep some Rails changes. In classic vi style, j and k navigate between changes. c, t, and p lead you to the selected change’s commit, tree, or parent. h and l navigate between pages.

In fact, h and l will always go back and forward on any paginated page. We’ve written an evil twin which adds those hotkeys to any will_paginate call.

Also cool: s. If you’re logged in, hitting s will display and focus the search bar. I use this one the most.

Ranged Code Highlighting

Clicking on any line number then shift clicking a higher value line number selects a range. Super useful for code discussion. Discussion such as, “Dude, nonzero? is so awesome. Check it out!” (People definitely talk like that.)

Sweet.

Keep Your Dotfiles in Git

Okay, this isn’t strictly related to GitHub, but it’s good. You should be keeping your dotfiles in Git. Here are the steps to do so:

1. Create a ‘dotfiles’ directory.

2. Move your dotfiles to this new directory, sans leading dot. For example, to keep your ˜/.vimrc under version control, do this:

$ mv ˜/.vimrc ˜/Projects/dotfiles/vimrc. Rinse and repeat as necessary.

3. Add the following file to your dotfiles project, then run it: http://pastie.org/195036

4. Finally: $ git init && git add . Then: $ git commit -m ‘new dotfiles project’

You’re all set. Now your dotfiles that live in ˜ are symlinked to their counterparts in ˜/Projects/dotfiles. As a bonus, any time you git commit it will automatically git push. One of the entire points of keeping your files under version control is to back them up regularly.

I push to a private ‘dotfiles’ repo on GitHub. Others have created public repos. Your call.

For posterity’s sake, here’s my version controlled dotfiles:

bashrc
gitconfig
irbrc
railsrc
sake
screenrc
ssh
vim
vimrc

The best part?

The best part about GitHub, f’sure, is all the outrageously cool open source projects hosted on it. _why’s stuff, the jQuery plugins and mirrors, all the LISP projects, newer languages like Io, and of course the assorted GitHub-related projects.

Got something cool hosted there? Let us know.

Til next time, keep on hubbin’.

Update: GitHub Open Sores

I just created the GitHub account and did two things: created and pushed up some extractions from GitHub itself (like the jQuery hotkeys plugin) and also forked all the projects that are used on GitHub which we’re using on GitHub. Dude, meta. Anyway, have fun with that.

Sugary Adapters

Very recently, Simon Harris had an idea: nil? for Ambition. Tasty sugar.

Let’s figure out what it takes to make

User.select { |x| x.nil? }

behave just like

User.select { |x| x == nil }

in Ambition.

Short and Sweet

Simon’s approach was to modify Ambition directly to add support for nil?. While this is for sure ambitious, nil? is just another method. Not special. The adapter should decide what to do with it.

Easy. Here’s what we added to the ActiveRecord adapter’s Select translator:

def nil?(column)
  left = "#{owner.table_name}.#{quote_column_name column}" 
  negated? ? not_equal(left, nil) : self.==(left, nil)
end

See it in action on lines 84 to 87.

The tests, of course, can be found in types_test.

Chaining Stuffs

So, how does this work?

Every adapter’s Select translator has a special chained_call method. Ambition invokes chained_call and passes it an array of symbols when a chained.method.call is executed on itself.

In this case, the chain is m.name.nil?. Ambition knows that m is itself and ignores it, passing [ :name, :nil? ] to chained_call.

The ActiveRecord adapter’s chained_call method takes the passed array and, if it can find the second element, sends it the first element.

Basically:

# methods = [ :name, :nil? ]
if respond_to? methods[1]
  send(methods[1], methods.first)
end

Which translates to:

self.nil? :name

Cool. Adapters don’t need to set themselves up this way, but it works for ActiveRecord.

Notice: the ActiveRecord adapter doesn’t support anything more than chains two methods deep. It calls the second element and passes the first, ignoring the rest. Almost discouraging, but chin up – this is ActiveRecord specific. Ambition itself supports chains of arbitrary length, and your adapter can, too.

So array.include?, right?

The thing is, chained_call is only invoked when a chained method call is executed on an object Ambition owns.

User.select { |x| x.nil? }

In the above, Ambition owns the x. It’s self as far as the translator is concerned.

User.select { |x| [1,2,3].include? x.id }

Ambition does not own the array, only the x.id. So what happens?

Well, it’s the same as [1,2,3] == x.id to Ambition. The dude really doesn’t care. Any time there is something like left op right, Ambition calls op(left, right) on your translator.

Here’s an idea of the call:

include?([1,2,3], x.id)

Luckily x.id is translated for you prior to this. The call really looks more like:

include?([1,2,3], 'users.id')

The include? definition, then, on ActiveRecord’s translator is very straightforward:

def include?(left, right)
  left = left.map { |element| sanitize element }.join(', ')
  "#{right} IN (#{left})" 
end

Beautiful.

Join the Fun

While the Err twitter is great for general stuff, you should really hop on the Ambition mailing list if you want in on this action. Or just watch the project on GitHub.

Til next time.

Adapting Ambitiously

It’s funny, really. All these people walking around, talking about Ambition. “Oh, Ambition? Yeah, pretty cool.” “Ambition? Impedance mismatch.” “I’m happy with SQL-92 the way it is, thank you very much.” Outrageous!

I know, I know. We’ve said some crazy things ourselves. Like how we wanted Ambition to be a Rack for databases. Or, far fetched as it sounds, how we hoped Ambition could evolve into something akin to LINQ. But we’re done talking.

Today we want to show you some plain jane Ruby and how Ambition empowers it to leverage its inherent synergy. Er, I mean, we want to show you something kickass.

New School

This is what we’re used to:

>> SQL::User.select { |m| m.name == 'jon' && m.age == 21 }.to_s
=> "SELECT * FROM users WHERE users.name = 'jon' AND users.age = 21" 

This is what’s new:

>> LDAP::User.select { |m| m.name == 'jon' && m.age == 21 }.to_s
=> "(&(name=jon)(age=21))" 

Adapter School

As of 0.5, Ambition is no longer a SQL wrapper. Rather, it is an API for writing your own adapters. If you’d like to continue using the ActiveRecord version of Ambition, please install the ambitious-activerecord gem:

$ gem install ambitious-activerecord

Then, of course, use it:

require 'rubygems'
require 'ambition/adapters/active_record'

You can, too, install and use the older 0.3 series:

$ gem install ambition -v 0.3.2

Anyway, you heard right: Ambition now supports arbitrary data stores. Anything. Ambition adapters are just gems which depend on ambition and use its amazing API powers for the greater good.

What other adapters are underway? Oh, I dunno. How about ActiveLDAP, CouchDB, Facebook FQL, XPath, and DataMapper, to name a few. Why, just the other night the Boston.rb guys started working on a Sphinx adapter. Check it out with git:

$ git clone git://technicalpickles.com/ambitious_sphinx.git

We’ve also got two example gems: ambitious-activeldap and ambitious-activerecord.

There’s basic documentation for Ambition’s API over at ambition.rubyforge.org, which you are free to peruse as well.

We’re just starting out, but it’s not a bad start. Got an idea? Something crazy? We’re all about it. Jump on the mailing list or join #ambition on irc.freenode.net then chime in.

Dream School

Let’s take the youtube-g gem, as an example. There’s no finished adapter for it yet so we’re going to pretend.

Using the new Ambition, we could (behind the scenes) turn a query like this:

Videos.select { |video| video.user == 'liz' }

Into this:

YouTubeG::Client.new.videos_by(:user => 'liz')

We could turn a query like this:

Videos.select { |video| video.tags.include? 'apple' }

Into this:

YouTubeG::Client.new.videos_by(:tags => 'apple')

And we could even turn a query like this:

Videos.select do |video| 
  video.tags.include?('football') && !video.tags.include?('soccer')
end

Into this:

YouTubeG::Client.new.videos_by :tags => { 
    :include => ['football'], 
    :exclude => ['soccer'] 
  }

Not bad. It even comes with a generator, courtesy of Dr Nic, for spitting out an adapter scaffold:

$ ambition_adapter ambitious_youtube

Future School

Got an idea for an adapter, or some code to show? Throw it in the comments. You better believe we’ll keep the rest of you abreast of cool adapters, fancy tricks, and new features.

Want to get involved? Like I said, there’s always the list and the GitHub repo. Bugs can go to Lighthouse and you can clone my repo thisaways:

$ git clone git://github.com/defunkt/ambition.git

Ah, how far we’ve come. And how far we’ll go! Here’s to it.

Feeds for Free

And money for nothing. Or something like that? Sorry, Mark Knopfler. I’ll pay more attention next time.

Anyways, let us be painfully aware that we can get Atom feeds for free. Not as in beer or speech, but as in ‘zero lines of code.’ How? Microformats.

You and meFormats

Almost a year has past since we last spoke of microformats, and way more than a year since our first encounter. Seems like only yesterday.

Remember hAtom? It’s like Atom, only embedded into your existing content’s HTML pages. The mofo site references the following example:

A normal, typical blog post:

<div class="post">
  <h3>Megadeth Show Last Night</h3>
  <span class="subtitle">Posted by Chris on June 4th</span>
  <div class="content">
    Went to a show last night. Megadeth. 
    It was alright.
  </div>
</div>

The same post with hAtom superpowers:

<div class="post hentry">
  <h3 class="entry-title">Megadeth Show Last Night</h3>
  <span class="subtitle">Posted by 
    <span class="author vcard fn">Chris</span> 
    on 
    <abbr class="updated" title="2006-06-04T10:32:10Z">June 4th</abbr>
  </span>
  <div class="content entry-content">
    Went to a show last night. Megadeth. It was alright.
  </div>
</div>

To you and I, eagerly searching for a review of last summer’s Megadeth show, there is no difference between the two. Our browsers render them the same. To a machine, however, the second post is chock full of semantic goodness.

This semantic goodness represents, in our HTML, the same information an Atom feed would provide. This leaves us with two paths of action for gettin’ our feed on: we can wait for feed readers to start speaking hAtom fluently, or we can have someone translate hAtom to Atom for us.

Subtlely Free Feeds

One year ago today Subtlety was released. Today it is re-released with a new feature: it can convert a page containing hAtom entries into an Atom feed. This means your feeds are now officially free.

We’ve actually been doing this for a while right here on Err. Our Feedburner feed points to this url: http://subtlety.errtheblog.com/O_o/29f.xml. It’s an Atom feed generated by Subtlety after parsing the hAtom elements on this site. On Err the Blog.

My ozmm blog is a static blog with no special RSS code. Instead, I point the Feedburner URL at a Subtlety Atom feed which is generated from the hAtom in the posts. Our Dynamite blog uses the same trick. See the pattern?

There’s no reason to ever write your own Atom feeds anymore. Sorry.

But what if I don’t want you hosting my feeds?

That’s fine, and acceptable. How about I just hand you the technology to do this on your own?

It goes like this:

$ gem install mofo
$ cd rails_app/vendor/plugins
$ gem unpack mofo

Then, here’s your controller:

class PostsController < ApplicationController
  def index
    @posts = Post.find(:all)
  end

  def atom
    target = url_for(:action => :index)
    render :xml => hEntry.find(target).to_atom(:title => 'whatever')
  end
end

You can use this trick for dynamically generated feeds (changelogs or activity feeds, perhaps) or whatever else. Thanks, mofo.

Last Step: Cut the Code

Now go through your app and remove all the Atom code. Drop those extra plugins, remove those xml templates, cut out all the special logic, and enjoy simple Subtlety or profound mofo.

Have fun.

The jSkinny on jQuery

j-j-j-jQuery. It’s on everyone’s lips, right? You love it or you hate it, or you’ve never tried it but you love it, or you’ve never tried it but you super-hate it. Yeah, we know.

Well, PJ and I launched FamSpam a bit ago and made the bold move of powering all the jabbascript with jQuery. We even wrote our own Facbeook-style lightbox library in jQuery (Facebox). So while this is a Ruby blog, indulge me for a moment as we dance with Ruby’s ugly-cool half-sister: Javascript.

whatQuery?

There are a bajillion posts about jQuery, all of which introduce you gently, so I will be brief: jQuery is all about a single namespace and kickass querying. (Get it?)

Our buddy Hpricot, you may remember, was heavily influenced by jQuery’s selector syntax. Which was, in turn, heavily influenced by CSS selectors. As such, some of this may look familiar:

$('#id').hide()
$('.class').css('height', 20)
$('#posts li > a').addClass('dark')

And so forth. One of the fun things is that any of those $() queries may return 0, 1, or more elements—yet the code stays the same. That’s right: our css() call would affect the height of all matched elements. Same with the addClass. But, if nothing is found, it’ll all silently fail. jAwesome!

niceQuery()

While some of the recent “jQuery vs <insert_framework_here>” blog posts might not be so nice, jQuery itself certainly is: it (mostly) easily works alongside other libraries. That means you can start dipping your toe into the jSauce while your Prototype or MooTools code doesn’t suspect a thing.

It’s easy:

jQuery.noConflict()

My above examples would now be written like this:

jQuery('#id').hide()
jQuery('.class').css('height', 20)
jQuery('#posts li > a').addClass('dark')

The noConflict() call causes jQuery to defer ownership of $() to Moo or Proto, leaving your current js intact. How thoughtful. Check more at the comprehensive doc site.

chainQuery()

jQuery is all about chaining, in a big way. Here’s an example from FamSpam:

var person_email = $(this).parent().find('#person_email').val()

Pretty self explanatory. The find is scoped to the receiver, in this case the parent of the current element.

Another cool chain:

$('#invite_error').show().text('Please enter an email and a name.')  

Hrm, we should probably put text() before show(), yeah? I love these kind of questions!

Finally, a slightly more advanced chain:

$('#facebox .body').children().hide().end().
append('<div class="loading"><img src="'+$s.loading_image+'"/></div>')

Get it? The end() reverts the most recent ‘destructive’ (read: find) operation. So we start with the .body, then find its children, then hide its children, then go back to .body and append some html. Slick, I think. Real slick.

Like I said, the doc site is super great.

ujsQuery()

Okay, here’s the segue: jQuery has unobtrusiveness built in. And it feels smooth. Real smooth.

Here, a snippet straight from FamSpam’s javascript:

$('.reset_invite_form').click(function() {
  $('#new_person').resetForm()
  $('#invite_another').hide()
  $('#invite_another > span').remove()
  $('#new_person').show()
  return false
})

Pretty simple, right? And clear, to boot. What we do is slip this code inside of a function passed to $(document).ready(), which will be run when the, erm, document is, uh, ready.

Like this:

$(document).ready(function {
  $('.reset_invite_form').click(function() {
    ... stuff ...
  })
})

So on and so forth. We attach Facebox to links the same way:

if ($.facebox)
  $('a[rel*=facebox]').facebox()

If the Facebox plugin is loaded, we find any links with a rel of “facebox” and convert them from normal links into jsery’d Facebox links. Easy as pie.

Which brings us, of course, to the segue.

spamQuery()

How are we using jQuery on FamSpam? jRails? Something custom? By hand?!

Yeah, well, by hand. We add all our behavior unobtrusively using the method detailed above. As far as I know, there’s no javascript in our html. If there is it’s on the run. For its life.

If you want to peep around, the js is (predictably) right here: http://famspam.com/javascripts/application.js.

Something to note: as of writing (1.2.2), jQuery doesn’t play nicely with Rails’ respond_to. But, hold the phone, it’s okay: a simple fix. Right here:

jQuery.ajaxSetup({ 
  'beforeSend': function(xhr) {xhr.setRequestHeader("Accept",
    "text/javascript")} 
})  

You’re now ready to rock.

Oh, one more thing before we move on: if you want some ajax-flavored will_paginate, check out this short guide. It was mentioned in another post, and now it’s mentioned here.

plugQuery()

jQuery, you see, has a wonderfully simple plugin system. We take full advantage of it by using a few choice plugins. Here’s a taste to wet your pallet.

The most essential plugin is the jQuery Form Plugin. With it, you can unobtrusively convert normal forms into ajax forms. The (obvious) advantage of this is graceful degradation, which is very kind but also very courageous.

$('#new_person').ajaxForm(function() { alert('Atta boy!') })

Seriously. Simple. And just so perfect for Rails—all the form’s attributes stay the same, including its action and method, just now it’s submitted through ajax. respond_to and jQuery are so in love it’s making me sick.

Another plugin we use is the Tablesorter. While we don’t have much tabular data on the promo or family sides of FamSpam, our admin interface is full of it. Want to sort your families by number of members? Conversations? Photos? It’s one line of code with this plugin. Sure, we’ll have to do some more complicated server-side sorting as our database grows, but this does the trick so quick right now.

$('#sorted_table').tablesorter()

See?

Another plugin we really love is the anti-aliased rounded corner plugin. Unfortunately this is not the most popular rounded corner plugin for jQuery, and that’s a shame. A damn shame. Because it’s definitely the best. We use it on the home page and other places we thought could use some class.

As usual, it’s dead simple once installed:

$('.corner').corner()  

You don’t have to be so generic with it, but we like to be.

Finally, the brand new autocomplete plugin by ReinH and wycats is simple, small, and slick. See a pattern here? We’re using this on our admin site and couldn’t be happier. It speaks JSON, baby. Sign me up.

thatsitQuery()

Thanks for letting us stray for a moment from our normally dreary discourse. Got any other cool jQuery tips or treats? Leave ‘em in the comments.

Oh, a parting gift. More code to chew on: our tour.js. We use it to power the FamSpam tour. Enjoy.

Paginatin’ Christmas

In a timely holiday manner, we present to you a short list of will_paginate resources. Please enjoy responsibly.

will paginate

Sightings

Since its inception, millions of people have paginated billions of records using will_paginate. A few notable examples:

Know a site that belongs on this list? Let us know in the comments.

New Features

Just in time for the holidays, Mislav has gifted us all with a bucket o’ new features for will_paginate, mostly dealing with customizing the output. Take a look at his announcement and dive in.

Testing Your Views

For those of us constantly asking the view testing question, stern but fair will_paginate maintainer Mislav comes to the rescue with his aptly titled will_paginate and view testing article. It’s in-depth, so be sure to check it out. Also: subscribe to his blog. Immediately.

Ajax Pagination

This Ajax thing is going to be huge! Get in on the action with Matt Aimonetti’s Ajax Pagination in less than 5 minutes article. He’ll teach you how to unobtrusively add Ajax behavior to will_paginate using Prototype, LowPro, and RJS.

If you’re more of the jQuery type, check out the Ajax will_paginate, jq-style article over at ozmm.

The RailsCast

The prolific Ryan Bates has a screencast explaining the basics of WP. As always, it’s to the point and very well done. And hey, you can even watch it on your iPod! Have a look.

will_paginate without ActiveRecord

There’s only a few things I like more than websites with tildes in the URL. Like, say, twisting Rails plugins into non-Rails uses. Lucky for us, Erin Ptacek provides both in an entry titled Erin’s Adventures with Rails: will_paginate without ActiveRecord. In it, you’ll learn how to paginate a collection of OpenStruct objects. No ActiveRecord required. What a rush.

Ferret Integration

Brandon Keepers, that handsome devil, wrote a popular article detailing the steps necessary to paginate your Ferret search results using good ol’ WP. If you’re using Ferret, this is definitely the way to go.

Solr Integration

While I don’t know what ‘The Pug Automatic’ means, and I certainly know nothing of the RoboPug in said blog’s header, Henrik Nyh’s article on paginating acts_as_solr with will_paginate is an undeniable must read for any Java lovin’, Apache huggin’ Solr user wanting to add a bit of style and flair into his app.

acts_as_taggable Integration

Perhaps as contentious as the comments and this thread suggest, Jim Morris’ Paginating acts_as_taggable article offers a few ways to make both the on_steroids variant of acts_as_taggable plugin and will_paginate play nicely together.

Rails Plugins

A number of Rails plugins have been released with support for will_paginate lovingly baked in. Like a Santa shaped sugar cookie.

SpinBits’ SimplySearchable helps you search in style while providing options to paginate using our friend WP.

UltraSphinx, Evan Weaver’s preeminent Sphinx search engine plugin, longs to be installed alongside will_paginate.

While will_paginate, due to popular demand, plays nicely with scope_out, Nick Kallen’s similar HasFinder works technically and conceptually well with everyone’s favorite paginator.

And finally, how can we neglect to mention Will_Paginate_Search, which hooks into both WP and acts_as_indexed to the benefit of all involved parties.

The Bugtracker

Found a bug? Got an idea? Of course we’d love to hear it. Our Lighthouse tracker, which we love is the place for all of it.

The Google Group

Did you catch it above? Yep, you did. So observant: there’s now a Google Group for will_paginate. Be sure to join up and chime in.

Nightly RDoc

Finally, RDoc is now generated nightly from the latest code in Subversion. Check it out at the Rock. It’s like Christmas every day!

That’s a wrap

Hey, where did 2007 go? I’m getting sucked into 2008 faster than Perl into obscurity.

Keep paginatin’, Railers. See you next year.

Evil Twin Plugin

Seriously, I think I have something against Rails’ lib directory. We jumped from keeping gems in lib to vendor/gems back in March. Then we jumped from keeping generic Rake tasks in lib/tasks to Sake. Now we’re gonna jump again.

Hacking Plugins

It’s really not that big of a deal, and pretty common—you want to change the behavior of some existing plugin. Maybe you Piston it and commit your changes. Sure. But maybe you just want to leave the original code alone.

A classic approach has been to stick some kind of hack in the lib directory. Issues abound, for sure. First: the load order. Who gets loaded first? Who reloads and who doesn’t? Second: location. You’ve got one bit of code messing with another bit of code in a totally separate location. Third: testing. Are you testing it? Maybe.

None of these things are deal breakers, but we can certainly address them. And we will.

The Evil Twin Plugin

Evil Twin

Here’s the simple solution: create a plugin called whatever_hacks, where whatever is the name of the plugin you’re hacking. That’s it. An evil twin, if you will.

Adding the _hacks suffix ensures it will always be loaded after the target plugin (assuming you haven’t messed with the default plugin load order—alphabetical). Keeping it right next to the target plugin also ensures anyone who peers into vendor/plugins will instantly know tomfoolery is afoot.

You can now build out a tested, hack happy plugin. Or, y’know, just stick it all in init.rb. With caution.

Caution: init.rb

Caution: init.rb does not always do what you expect it to do. It’s loaded in the context of Rails::Plugin in 2.0 and Rails::Initializer in 1.2.5, not Object. Come again? Like this: re-opening existing classes isn’t as straightforward as elsewhere.

=> init.rb

class Hash
end
puts Hash.inspect

Guess what that prints. Ready?

$ ./script/runner 
Rails::Plugin::Hash

That’s right—we didn’t re-open Hash, we created a new Rails::Plugin::Hash class. Any methods we add in there won’t be added to Hash proper.

If we want to grab a real class and stuff some methods in it, we need to use class_eval or module_eval:

=> init.rb

Hash.class_eval do
  def duck_punched?
    true
  end
end  

puts({}.duck_punched?)

As expected:

$ ./script/runner 
true

Doing it this way (class_eval) forces a constant lookup, making Ruby happily run up the chain and find the class or module in question.

attachment_fu_cropper

Okay, time for a real example. I wanted to change attachment_fu’s ImageScienceProcessor to crop thumbnails before resizing them. As this is a hack I use on all my apps, I also want to keep it out of my models. Hence, attachment_fu_hacks.

=> vendor/plugins/attachment_fu_hacks/init.rb

klass = Technoweenie::AttachmentFu::Processors::ImageScienceProcessor
klass.module_eval do
  ##
  # Hacked to use image_science's #cropped_thumbnail method
  def resize_image(img, size)
    # create a dummy temp file to write to
    filename.sub! /gif$/, 'png'
    self.temp_path = write_to_temp_file(filename)
    grab_dimensions = lambda do |img|
      self.width  = img.width  if respond_to?(:width)
      self.height = img.height if respond_to?(:height)
      img.save temp_path
      callback_with_args :after_resize, img
    end

    size = size.first if size.is_a?(Array) && size.length == 1
    if size.is_a?(Fixnum) || 
        (size.is_a?(Array) && size.first.is_a?(Fixnum))
      if size.is_a?(Fixnum)
        img.cropped_thumbnail(size, &grab_dimensions)
      else
        img.cropped_thumbnail(size.first, &grab_dimensions)
      end
    else 
      new_size = [img.width, img.height].dim size.to_s

      img.cropped_thumbnail(new_size.first, &grab_dimensions)
    end
  end
end 

Works like a charm.

When heavysixer wanted to hack acts_as_taggable, he took the same approach: http://pastie.caboo.se/119904. Feel free to follow suit.

View Testing 2.0

I’ve come to realize how anti-web 2.0 this blog really is. Why are we, the authors, doing all the work?! Where is the user generated content? How can we pretend to be Rails developers when we waste our time writing all these blog posts!

No longer. Today all that changes. It’s time you guys start pulling your weight.

Let’s Talk About Testing Views

There seem to be about a bajillion different solutions to the ‘problem’ of testing views. If you’re into testing and also into Rails, my guess is you’ve tried a few different styles before settling on your current method. (hey, me too)

What I would love is for you to post your current, favorite, flavor-of-the-month style of view testing. What library do you use, where can we download it, and how about some sample code?

When commenting, wrap your code in <code>code tags</code> and use "textile":http://textism.com/tools/textile/ for links.

Everyone has different taste, but the hope is we’ll have enough options for people to find something they like. Something tasty.

Hit me!