RSS Err the Blog - Home

http://errtheblog.com/

Monday June 25th, 2007

5.5

Sake Bomb!

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

Before moving to California, I’d never had sake. Ever. Any. What a mistake that was. My west coast friends made fun of me then promptly taught me the trick where you drop a shot of sake into a glass of beer then drink really fast. It’s called a sake bomb. Pretty intense.

Also intense are Rake tasks, something you and I have talked about before. They are so handy! The only problem is I find myself installing the same base set of Rake tasks on every new Rails project I start. Over and over. Stuff like db:version, routes, yaml_to_spec, etc.

I wish Rake tasks were more like Rubygems: system-wide instead of project specific, easy to install or share, and fun to use.

Well, now they are.

S-s-s-sake!

Sake is system-wide Rake. It works pretty much like how you’d expect it to work: use it to install then run Rake tasks. Let me show you.

$ sudo gem install sake

Imagine I have an err.rake file with two tasks in it. Imagine it looks like this:

namespace :db do
  desc "Returns the current schema version" 
  task :version => :environment do
    puts "Current version: " + 
      ActiveRecord::Migrator.current_version.to_s
  end
end

desc "Show specs when testing" 
task :spec do
  ENV['TESTOPTS'] = '--runner=s'
  Rake::Task[:test].invoke
end

Okay. First, let’s check and see what’s in the file:

$ sake -T err.rake 
sake db:version   # Returns the current schema version
sake spec         # Show specs when testing

Cool. I wonder what spec looks like?

$ sake -e err.rake spec
desc 'Show specs when testing'
task 'spec' do
  ENV["TESTOPTS"] = "--runner=s" 
  Rake::Task[:test].invoke
end

Ah, neat-o. Let’s go ahead and install it.

$ sake -i err.rake spec
# Installing task `spec'

Now let’s see what Rake tasks we have installed system wide.

$ sake -T
sake spec   # Show specs when testing

Amazing. $ sake spec from anywhere. And, of course, we can remove it at any time:

$ sake -u spec
# Uninstalling `spec'.  Here it is, for reference:
desc 'Show specs when testing'
task 'spec' do
  ENV["TESTOPTS"] = "--runner=s" 
  Rake::Task[:test].invoke
end
Railing Some Sake

Did you notice the :environment task dependency in our db:version task? Yikes, that’s Rails-specific. Let’s install that task and see what happens.

$ sake -i err.rake db:version
# Installing task `db:version'
$ sake db:version
rake aborted!
Don't know how to build task 'environment'

(See full trace by running task with --trace)

Failed. Hard. What if we try this from within a Rails app?

$ sake db:version
Current version: 106

Disco. Sake picks up tasks in directory-local Rakefiles, in this case Rails’ Rakefile.

Tasks Are Islands

Sake only knows about Rake tasks, not any other Ruby code. If you’ve got a require in your Rakefile, Sake has no idea. It just cares about the tasks. So, sometimes we need to tweak a task or two.

Let’s say you have this:

require 'rubygems'
require 'hpricot'
require 'open-uri'

desc "Today's sports scores" 
task :scores do 
  doc = Hpricot open('http://sports.com')
  doc.search('.score').each do |score|
    puts score
  end
end

Sake will only pull in the task, not the require statements. No big deal. Just tweak it:

task :hpricot do
  require 'rubygems'
  require 'hpricot'
  require 'open-uri'
end

desc "Today's sports scores" 
task :scores => :hpricot do 
  doc = Hpricot open('http://sports.com')
  doc.search('.score').each do |score|
    puts score
  end
end

Rinse and repeat for any code which depends on plain jane Ruby executed outside of a task.

A Place For All Your Ruby Slippers

I used to have a bunch of Ruby scripts laying around, for all kinds of simple tasks.

Here’s one I had to apply a patch from a pastie url:

#!/usr/bin/env ruby
# Usage:
#   $ pastie_patch 69664 
#   $ pastie_patch http://pastie.caboo.se/69895

require 'open-uri'

pastie_url = 'http://pastie.caboo.se/%s.txt'
patch_id   = ARGV.first.gsub(/\D/, '')

patch = open(pastie_url % patch_id).read

File.open('patch.diff', 'w+') do |f|
  f.puts patch
end

`patch -p0 < patch.diff && rm patch.diff`

puts "Patched with pastie ##{patch_id}." 

Now, thanks to Sake, I can just make these scripts into Rake files and have a fun interface to manage them with:

# Usage: sake pastie:patch PASTE=12345
desc "Apply a patch directly from Pastie" 
task 'pastie:patch' do
  require 'open-uri'

  pastie_url = 'http://pastie.caboo.se/%s.txt'
  patch_id   = ENV['PASTE'].gsub(/\D/, '')

  patch = open(pastie_url % patch_id).read

  File.open('patch.diff', 'w+') do |f|
    f.puts patch
  end

  `patch -p0 < patch.diff && rm patch.diff`

  puts "Patched with pastie ##{patch_id}." 
end

Your productivity will thank you. There’s really no reason not to use Ruby all over the place now.

Sharing Your Sake

Remember how we peeked around a Rakefile? Well, you can do that with URLs, too. It’s just open-uri, y’know?

$ sake -T http://pastie.caboo.se/73211.txt
sake development   # Runs the following task in the development...
sake production    # Runs the following task in the production...
sake testing       # Runs the following task in the test environment
sake dev        
sake prod   

Make sure you inspect the code and know what you’re installing. Just like you would with any random RubyGem or snippet, right?

$ sake -e http://pastie.caboo.se/73211.txt production
desc 'Runs the following task in the production environment'
task 'production' do
  RAILS_ENV = ENV["RAILS_ENV"] = "production" 
end

Rad. If you want to share your Rake tasks with other Sake users, just throw them up somewhere. Like Pastie.

Sharing Your Sake (Hardcore)

Or, also, use -S. Sake comes built in with a Mongrel handler which will serve all your installed Rake tasks. Maybe you’re at a hip European conference or a hip European cafe with no Internet. And maybe your hip European friend needs a specific Rake task right this second. Waste no time.

$ sake -S
# Serving warm sake tasks on port 4567...

As the -h explains, -d will daemonize and -p lets you set the port. (The code for the handler is here if you wanna take a peak. It’s fun.)

The Morning After

When both sake and bombs are involved, nay, combined, the night seldom goes as planned. That’s okay. Please report bugs over at our Lighthouse project and, if you can, send patches as pasties.

Sake probably only runs on Windows using Cygwin, due to its ruby2ruby dependency. So, yeah. Let us know if you can prove otherwise.

I Owe You a Sake Bomb

Special thanks to Ryan Davis & Eric Hodel for ruby2ruby (which is brilliant) and also to Josh Susser, Brian Donovan, and Zack Chandler for their work on the predecessor to Sake.

One Last Thing!

A parting gift. Here are some Rake tasks you may want to $ sake -i.

database.rake svn.rake pastie:patch rake routes

 

Hey, while we’re on the topic, why notshare with us your favorite rake tasks? Put them up somewhere (as plaintext) so we can -T and -e and -i them, then let us know where that somewhere is in the comments. Killer.

Tuesday June 19th, 2007

1.8

Cappin' that Stat

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

vJot’s a public code base, so I saw it unbecoming of an open-source guy to add my stats html to the repo. No worries, I’ll just add it with a little help via the wonder library, Capistrano:

task :after_symlink, :roles => :app do
  stats = <<-JS
    <script src="http://www.google-analytics.com/urchin.js">
    </script>
    <script type="text/javascript">
    _uacct = "UA-104904-8";
    urchinTracker();
    </script>
  JS
  layout = "#{current_path}/app/views/layouts/application.html.erb" 
  run "cat #{layout} | sed 's?</body>?#{stats}</body>?' > ~/app.tmp" 
  run "mv ~/app.tmp #{layout}" 
end

The code appends my Analytics’ javascript to the layout file and voilà, instant stats!

The secret sauce is that if you’ve defined an after_symlink task, Capistrano will run it during your regularly scheduled cap deploy. As a matter of fact, it has before_ and after_ hooks for all its tasks, including any tasks that you’ve written. Schweet.

I’m all ears as to why the following doesn’t work if someone would enlighten me:
run "cat #{layout} | sed 's?</body>?#{stats}</body>?' > #{layout}" 

Friday June 15th, 2007

1.0

Jottin' to the Fullest

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

A few months back I scooped up a sweet domain name, vJot.com, but had no particular reason for doing so. I couldn’t have that, my parents raised me better. So, I built a tasty, little app during RailsConf to fill the void and boy was I happy. I mentioned its arrival on Twitter and managed to get eight people using it concurrently. Hot damn, I’m retiring to the Bahamas!

Then, some folks claimed it was buggy, and others mentioned it wasn’t much to look at (you guys know who you are, my feelings were hurt), but I think I’ve appeased the masses after a month of tweaking. The latter was fixed because I stole the style from a project Chris is working on (more on that soon).

It’s a simple note-taking app inspired by a sweet OS X jotter called Notational Velocity. There’s nothing in particular in the code worth showing off, but anything I’m writing for personal satisfaction is going to be MIT’d. Too long I worried if my previous employer owned my code (and soul, but that’s a different post).

Demo: http://vjot.com
Browse: http://require.errtheblog.com/projects/browser/vjot
Checkout: svn co svn://errtheblog.com/svn/projects/vjot

There’s a handful of stuff I still want to do like adding a concurrent edit warning, converting to javascript-lookups instead of db-lookups, and hooking up global shortcut keys, but sometimes you just gotta launch a bitch.

If you don’t “get” the app, that’s okay, I don’t expect everyone to find a need for it. Chris and I have had success using it to keep track of our ideas and plans for upcoming posts/events since its inception. And if we’re using it, well…

I’ve even been using it to keep track of feature requests for itself, it doesn’t get much more meta than that folks. Happy jottin’.

Monday May 21st, 2007

3.4

Kickin Ass w/ Cache-Fu

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

Between all the post-punk marching bands, free booze, and falls off fire escapes, I somehow managed to give a presentation on memcached at RailsConf 2007.

I’ve got two versions of my slides available: with notes and without notes.

Nick Sieger has an excellent writeup of the talk, as do Rob Sanheim and John Nunemaker. Their collective blog entries really contain more information than my slides.

Grab cache_fu and follow along:

$ svn co svn://errtheblog.com/svn/plugins/cache_fu

Patches are welcome and should be directed towards Lighthouse.

See you guys next year.

Saturday May 19th, 2007

6.5

I'm Paginating Again

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

We’ve all been paginating for a few months now with will_paginate because it’s such a sweet plugin, but Koz put a call out on Rails Core wondering what the leading pagination plugins are. Somebody tell him to subscribe to Err already.

Pagination is getting tossed out of Rails 2.0, so what better time to define the leader. The first step is to pluginize the classic pagination currently in Rails. That’s kinda boring, so we moved straight away to step two, had Mislav Marohnic merge his will_paginate rewrite into our code base, and slapped a 2.0 label on it. (We’ll eventually do step one, but bear with us)

It’s a major release, because we broke the existing API. Mislav’s rewrite converted the paginate method to return not an array of objects and the current page, but a PaginatedCollection proxy. All that really means is you have less code to write now, but you should update any existing code if you’d like to upgrade.

Here’s the updated way to use the plugin via the README:

app/controllers/posts_controller.rb
@posts = Post.paginate :page => params[:page]
app/views/posts/index.html.erb
<p>Handful of posts coming up</p>
<%= render :partial => 'post', :collection => @posts %>

<p>Now let's render us some pagination!</p>
<%= will_paginate @posts %>

The really slick thing going on in the background is that unless you explicitly pass in total_entries, the paginate method with automatically grab the total count based upon the conditions you’ve specified. Nice.

As always, grab the plugin here: ./script/plugin install \ svn://errtheblog.com/svn/plugins/will_paginate

Tuesday May 8th, 2007

5.6

Ya Talkin' Gibberish

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

Oh man! Just tonight Beast Edge was graced with localization, courtesy of Gibberish. Relevant commits are here and here. There’s already some heated debate on the Mephisto mailing list regarding this change and possible future changes, if you’re into that sort of thing. Dig in.

Gibba-what-now?

Yeah, what? Gibberish is a simple Rails localization plugin we developed for FamUpdate and have been slipping into other projects. We’ve got Globalize and we’ve got Localize, but come on. They’re great, and useful, but sometimes you just want something light weight. I know I do.

I also know keying my translations by a string is not fun. What if I want to change a comma? Or add an exclamation point! Oh, use a symbol. But symbols make it hard to read my views. There must be a middle ground.

Of course: Gibberish.

Keyin’
<%= 'Description'[:description_title] %>

The above will return Description by default. If you’ve another language selected (besides English), it will find the description_title key in that language’s translation file and return the corresponding string instead. Simple.

You may leave the brackets blank, which will force Gibberish to assume the key is a lowercase, underscore’d version of the string.

So this:
"Header description"[]
Is the same as this:
"Header description"[:header_description]

Real smooth (thanks to technoweenie). Just, be careful. If you change the string too much you could land yourself in the same place we’re trying to escape from. (Trouble.)

klingon.yml

Translation files are simple YAML. For instance, es.yml:

welcome_friend: ¡Recepción, amigo!
welcome_user: ¡Recepción, {user}!
love_rails: Amo los carriles.

Something like “Welcome friend”[] will, when the language is set to :es, return ¡Recepción, amigo!. You can change the language quite easily:

>> Gibberish.current_language = :es
=> :es

It’s even got one of those trendy around_filters:

class ApplicationController < ActionController::Base
  around_filter :set_language

private
  def set_language
    Gibberish.use_language(session[:language]) { yield }
  end
end

No big deal.

Check the languages you’ve loaded with languages:

>> Gibberish.languages
=> [:es, :fr, :de, :kl]

In dev mode languages are automatically reloaded. From where? RAILS_ROOT + ’/langs’. Gibberish’ll look for .yml files in that directory.

The above example, we can safely assume, is run within a Rails app with lang/es.yml, lang/fr.yml, lang/de.yml, and lang/kl.yml. Add another language file and it’ll get picked up on the fly.

Intersomethinglation

The other cool thing Gibberish provides is interpolation. You may have noticed the {user} in welcome_user way above. Yeah, that’s gonna change.

Like this:
>> "Welcome, {user}"[:welcome_user, current_user.name]
=> "Welcome, Chris" 

The {user} bit will be replaced with current_user.name when being rendered. Works for translations, too. Naturally.

Curly brace’d strings are interpolated in the order arguments are passed. The fact that it says user is irrelevant. Really, it’s only to make things easy to remember. The important part is that it’s the first interpolation bullseye in the string.

Another example (ostensibly to drill home the point but realistically because me and PJ are on a Sandman kick):

>> "{name} is from {place}"[:hey_place, 'Chris', 'the Dreamworld']
=> "Chris is from the Dreamworld" 

Okay, you got the idea.

Grab it!

From the svn:

$ cd vendor/plugins
$ piston import svn://errtheblog.com/svn/plugins/gibberish

Follow trac: http://require.errtheblog.com/plugins/browser/gibberish

View the README: http://require.errtheblog.com/plugins/browser/gibberish

And, as always, report bugs to Lighthouse: http://err.lighthouseapp.com/projects/466-plugins

Enjoy ya jive talkin’ Gibberish. Patches gladly accepted.

Friday May 4th, 2007

6.8

Be Dee Dee and Me

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

Since before January (I don’t know how far before), I’ve been writing all my tests using the Cool New Way. Not because it’s cool, or new, but because it really helps me write better code. Let me tell you about it.

A Brief and Subtle Introduction

Be dee dee. Behavior driven development. BDD is a way of writing code by testing what the code should do rather than testing the code itself. Does that make sense?

Here’s an example:

require 'test/spec'

context "This blog post" do
  setup do
    @post = BlogPost.find_by_title('Be Dee Dee')
  end

  specify "should mention bdd" do
    @post.body.should.include 'behavior driven development'
  end

  specify "should be concise" do
    @post.body.word_count.should.be < 1000
  end

  specify "should contain at least four hyperlinks" do
    @post.body.scan('a href').size.should.be >= 4
  end

  specify "should be written by me" do
    @post.author.should.equal Author.find_by_name('Chris Wanstrath')
  end
end

So, BDD is just writing tests with a special syntax and describing them in plain jane English. “What’s the big deal, then?” Nothing. There is no big deal.

However, if you’ve a bit of time and an inclination for writing awesomely readable tests (which may ultimately help you write better code), continue.

Say, for instance, the above was real code. I could run it with a special test runner and get this output:

This blog post:
- should mention bdd
- should be concise
- should contain at least four hyperlinks
- should be written by me

Finished in 0.0083 seconds.

4 specifications (4 requirements), 0 failures

Nice. Let’s see where this goes.

Choose Your Weapon

A super popular BBD framework is RSpec, which I’m sure you’ve heard of. Those guys are leading the Ruby BDD revolution, more or less, with their code and blog posts.

RSpec is catching on with some big time projects adopting it, among them Rubinius and caboose(read: court3nay)’s sample app. Originally mofo used RSpec, but I’ve since moved on.

To test/spec, by Christian Neukirchen (of Anarchaia fame). His announcement of test/spec 0.3 is a very thorough explanation of the library’s features. This is the library I’ll be focusing on hereafter.

The test/spec library is great because it simply wraps test/unit, meaning most of your test/unit tools work right out of the box with it. No need to do anything special to test Rails (rake test, et al). ZenTest’s autotest “just works.” Heckle and it get along amazingly well. You can use plain ol’ assert_equal in specs. Etc.

As a result, you can start slipping test/spec into your existing project, alongside your existing tests, without doing anything special. A spec here, a spec there, and before you know it the whole damn crew is BDDin’.

But, don’t let me boss you around. A compulsory search on Rubyforge reveals two more BDD libraries: SpecUnit and RBehave, the latter being developed by the dude who coined the ‘BDD’ term: Dan North. Worth a look or, at least, a gander.

Digging a Rails Model BDD-Style

For now we’re going with test/spec: $ sudo gem install test-spec

(Yes, a dash. But you require it with a slash. You’ll get used to it.)

Luke Redpath posted an article on BDDing a Rails model using RSpec back in August. It’s good and thorough, so I’m not going to re-say what he said. Read it.

Anyway, let’s write a model. Digg got all crazy the other day with this HD-DVD bidness, so we’re gonna start our own Digg to make sure we can consistently get news about the stuff we’re interested in: Obama, the Wii, and Apple rumors.

(If you’d like to follow along at home, the completed source can be found at require. Check it out with svn and set everything up with $ rake db:bootstrap.)

$ svn co svn://errtheblog.com/svn/projects/dugg

So! The first thing we’d do here is describe our model’s ideal behave. In English. With YAML.

A User (in general):
  - should be able to digg a story
  - should not be able to digg a story twice
  - should be able to tell if he has dugg a story

Simple enough. Save this as user_spec.yml and then run the yaml_to_spec rake task (this one) on it to generate a scaffold:

$ rake -s yaml_to_spec FILE=user_spec.yml 
context "A User (in general)" do
  xspecify "should be able to digg a story" do
  end

  xspecify "should not be able to digg a story twice" do
  end

  xspecify "should be able to tell if he has dugg a story" do
  end 
end

Put that into test/unit/user_test.rb and you’re mostly ready to roll. What’s xspecify? It means a spec is not yet ready to be tested—when you run this file, all the xspecify blocks will be ignored. Helpful when writing spec/tests before writing any code (which is exactly what we’re doing).

Anyway, let’s fill in those specs:

require File.dirname(__FILE__) + '/../test_helper'

context "A User (in general)" do  
  setup do
    @user  = users(:defunkt)
    @story = stories(:undugg)
    @dugg_story = @user.dugg_stories.first
  end

  specify "should be able to digg a story" do
    @user.dugg_stories.should.not.include @story
    @user.digg(@story).should.equal true
    @user.dugg_stories(true).should.include @story
  end

  specify "should not be able to digg a story twice" do
    @user.digg(@dugg_story).should.equal false
  end

  specify "should be able to tell if he has dugg a story" do
    @user.should.have.dugg @dugg_story
    @user.should.not.have.dugg @story
  end   
end

This describes, to an extent, the behavior we want: digg should return true or false depending on whether or not it succeeds.

Running this test at this point gives a whole grip o’ errors. Time to start writing code.

Here’s our User, with the digg and dugg? methods simply wrapping our association:

class User < ActiveRecord::Base
  has_many :dugg
  has_many :dugg_stories, :through => :dugg, :source => :story

  def digg(story)
    dugg_stories << story
  rescue ActiveRecord::RecordInvalid
    false
  else
    true
  end

  def dugg?(story)
    dugg_stories.include? story
  end
end

Then, of course, our Dugg model:

class Dugg < ActiveRecord::Base
  set_table_name 'dugg'

  belongs_to :user
  belongs_to :story

  validates_uniqueness_of :story_id, 
    :scope   => :user_id, 
    :message => "can't be dugg again!" 
end  

The Story class has nothing in it (yet).

Running the tests now gives us the green. Fantastics.

Finding Dugg’d Stories

Let’s add another feature, something to the Story class. How about finding the most dugg stories?

Here, a spec for that:

A Story (in general):
  - should return its title when sent #to_s
Trying to find the most dugg stories:
  - should return a list of popular stories
  - should respect an arbitrary limit

Now, a test/spec for that:

require File.dirname(__FILE__) + '/../test_helper'

context "A Story (in general)" do
  setup do
    @story = Story.find(:first)
  end

  specify "should return its title when sent #to_s" do
    @story.to_s.should.equal @story.title
  end
end

context "Trying to find the most dugg stories" do
  setup do
    @second_most_popular, @most_popular = Story.find(:all).last(3)
  end

  specify "should return a list of popular stories" do
    popular = Story.most_dugg
    popular.size.should.equal 5
    popular.first.should.equal @most_popular
  end

  specify "should respect an arbitrary limit" do
    popular = Story.most_dugg(2)
    popular.size.should.equal 2
    popular.shift.should.equal @most_popular
    popular.shift.should.equal @second_most_popular
  end
end

I setup my fixtures to make the last story insert’d not dugg at all, second last story insert’d the most popular, the third last story insert’d the second most popular, etc. Keep that in mind; it’s clutch.

This spec fails, of course. We don’t have a most_dugg method and our to_s method is the default. Let’s fix that.

class Story < ActiveRecord::Base
  alias_attribute :to_s, :title

  def self.most_dugg(limit = 5)
    connection.select_all(<<-SQL).map { |row| find(row['story_id']) }
      SELECT story_id, count(1) as size 
      FROM dugg 
      GROUP BY story_id 
      ORDER BY size DESC 
      LIMIT #{limit}
    SQL
  end
end  

Lil’ bit of SQL never hurt no one. Not the most efficient query, but we just want to get our specs passing—we can focus on optimization later (with a counter_cache or something).

And pass they do. Glory be, be dee dee.

Gettin’ Railsy: BDDin’ Controllers

What’s cool is we haven’t even started Mongrel yet. Web browsers are so last year. Let’s keep it up by adding a controller or two.

Hold on. There’s a catch: functional tests in Rails are more than test cases with stock test/unit assertions. There’s assert_redirected_to and assert_select and all that webby goodness. Does test/spec give us this stuff? No, it doesn’t.

However, test/spec/rails does. A plugin by Per Wigren, the README file explains it all quite well. So does the cheat sheet.

Install it:

$ cd vendor/plugins
$ piston import http://svn.techno-weenie.net/projects/plugins/test_spec_on_rails

Now add require ‘test/spec/rails’ to your test_helper.rb and you’re good to go.

Stories Stories Stories

Time to hussle. We want to list all the stories on a page, in order. Nothing fancy. We also want to show the user whether or not she’s already dugg the story.

Some people like to do their controller specs a bit differently, so take this all with the same grain of salt you take everything on the internet.

A page listing all stories:
  - should display every story
  - should show a 'digg' link if the viewing user has not dugg the story
  - should not show a 'digg' link if the viewing user has dugg the story  

We’re going to sprinkle a bit of Mocha into this spec to pretend we’re always logged in as defunkt. Add require ‘mocha’ to your test_helper.rb after doing a $ sudo gem install mocha. (If you don’t know what the hell I’m talking about, check this chocolately post from the past.)

Speccin’ it out:

require File.dirname(__FILE__) + '/../test_helper'

context "A page listing all stories" do
  use_controller StoriesController

  setup do
    @user     = users(:defunkt)
    @dugg     = stories(:digg)
    @not_dugg = stories(:undugg)
    @controller.stubs(:current_user).returns(@user)

    get :index
  end

  specify "should display every story" do
    status.should.be :success
    template.should.be 'stories/index'
    assigns(:stories).size.should.equal Story.count
    body.scan(/story_/).size.should.equal Story.count
  end

  specify "should show a 'digg' link if the viewing user has not dugg the story" do
    dom_id = "#story_#{@not_dugg.id}" 
    should.select "#{dom_id}>.digg" 
  end

  specify "should not show a 'digg' link if the viewing user has dugg the story" do
    dom_id = "#story_#{@dugg.id}" 
    should.not.select "#{dom_id}>.digg" 
  end
end  

Reads nicely, huh? And not too distant from a test/unit functional test.

The use_controller method will setup all the instance variables we normally set ourselves: the request, response, and controller objects.

In our first spec we make sure the response is a-okay, then make sure we have the same number of ‘story_x’ ids as we do stories. Pagination is for sissies.

In the next two tests we ensure the ‘digg’ link works as planned.

The cheat sheet, again, has all the info you could want about the stuff test/spec/rails gives you. It’s pretty much ripped straight from the README.

With all that done, it’s simply a matter of making the tests pass. And we do, of course.

The first thing to setup are our RESTful routes:

ActionController::Routing::Routes.draw do |map|
  map.resources :stories do |story|
    story.resources :diggs
  end

  map.home '', :controller => 'stories'
end  

Next we create our controller:

class StoriesController < ApplicationController
  def index
    @stories = Story.find(:all, :order => 'created_at DESC')
  end
end

Then, if you will, the view:

<h1>All Stories!</h1>

<% @stories.each do |story| %>
  <div id="<%= dom_id story %>"><%= digg_link story %><%= story %></div>
<% end %>

Finally, the helpers:

module ApplicationHelper
  def digg_link(story)
    return if current_user.dugg? story
    link_to 'Digg!', story_diggs_url(story), :class => 'digg', :method => :post
  end

  def dom_id(record)
    "#{record.class.name.underscore}_#{record.id}" 
  end
end

See that digg_link method? The link itself doesn’t actually work yet, but we can do it. I know we can.

Diggin’ It

Okay, the DiggsController. What we want to assert first is our ability to digg a story. So, to create a digg:

Successfully digging a story:
  - should redirect to the stories listing page

Which brings us to:

require File.dirname(__FILE__) + '/../test_helper'

context "Successfully digging a story" do
  use_controller DiggsController

  setup do
    @user  = users(:defunkt)
    @story = stories(:undugg)
    @controller.expects(:current_user).returns(@user)
    @user.expects(:digg).with(@story).returns(true)
    post :create, { :story_id => @story.id }
  end

  specify "should redirect to the stories listing page" do
    flash[:notice].should.equal 'Dugg!'
    should.redirect_to stories_url
  end 
end

Simple and beautiful, one hopes.

The first error is test/spec bemoaning the lack of a DiggsController constant, so go ahead and add that. Make an empty create method while you’re in there, too. Remember, we’re just trying to make stuff pass at this point. We’ll cross every bridge as we come to it.

Now it’s complaining about the create.html.erb template not existing—time to write our create method:

class DiggsController < ApplicationController
  def create
    story = Story.find(params[:story_id])
    if current_user.digg(story)
      flash[:notice] = 'Dugg!'
      redirect_to stories_path
    end
  end
end  

Disco. But, remember, digg can fail. Let’s handle that case with another context:

context "A failed story digging attempt" do
  use_controller DiggsController

  setup do
    @user  = users(:defunkt)
    @story = stories(:dugg)
    @controller.expects(:current_user).returns(@user)
    @user.expects(:digg).with(@story).returns(false)
    post :create, { :story_id => @story.id }
  end

  specify "should render with an error message" do
    flash[:notice].should.match /error/i
    should.redirect_to stories_url    
  end
end  

Here, the new controller code:

class DiggsController < ApplicationController
  def create
    story = Story.find(params[:story_id])  
    if current_user.digg(story)
      flash[:notice] = 'Dugg!'
    else
      flash[:notice] = 'Error digging :('
    end
    redirect_to stories_path
  end
end

And just like that, we’re passing. Apple rumors, here we come.

The Full Spec

Oh, for bonus, remember that “special test runner” I told you about? You are now fully qualified to use it:

$ ruby test/functional/diggs_controller_test.rb -r s

Successfully digging a story
- should redirect to the stories listing page

A failed story digging attempt
- should render with an error message

Finished in 0.101304 seconds.

2 specifications (8 requirements), 0 failures  

Rad. But there’s more, given you download these double bonus test/spec/rails tasks:

$ rake spec
A Story (in general)
- should give its title when asked for a string representation of itself

Trying to find the most dugg stories
- should return a list of popular stories
- should respect an arbitrary limit

A User (in general)
- should be able to digg a story
- should not be able to digg a story twice
- should be able to tell if he has dugg a story

Finished in 0.136295 seconds.

6 specifications (10 requirements), 0 failures

Successfully digging a story
- should redirect to the stories listing page

A failed story digging attempt
- should render with an error message

A page listing all stories
- should display every story
- should show a 'digg' link if the viewing user has not dugg the story
- should not show a 'digg' link if the viewing user has dugg the story

Finished in 0.186009 seconds.

5 specifications (9 requirements), 0 failures  

That’s it, that’s everything our app does. Right there.

Testing is Fun Again!

If you want to feel around some more examples check out the cache_fu specs or the Gibberish specs or even the mofo specs. Mephisto, I hear, has some test/spec action these days.

Wanting to test/spec integration tests in Rails? Brian has the solution, which he busted out at a SF Ruby Meetup hackfest.

Let me know if I missed anything, if I broke anything, or if you know of any public projects using test/spec. The best way to get into this stuff, as always, is to just do it. Go go go!

Monday April 30th, 2007

2.6

Microformathingies

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

Look, I know it’s been a while since we chatted about microformats, but they’re still cool. Here: some random microformathingies I’ve been thinking and loving since our tryst last November.

First: Operator

The new(est) Operator is really rad. It’s taken Tails’ place as the de facto Firefox uformat extension, for good reason.

Just take a look at this upcoming event:

Imagine. Cool.

Second: Working With hCards

Install that extension then browse over to my (or your) Working With Rails profile. See it? Yes! The Working with Rails fellas recently added hCards to all profiles. You can now toy with Railers from the comfort of irb.

$ irb -rubygems
>> require 'mofo'
=> true
>> robby = HCard.find('http://workingwithrails.com/person/5410-rob-sanheim')
=> #<HCard:0x208189c ...>
>> robby.url.last
=> "http://www.robsanheim.com" 
>> robby.fn 
=> "Rob Sanheim" 

Wicked. And so easy, as Martin Sadler says: “I had a quick look and suffice to say it took me about 5 mins to implement. I tested using the Firefox tails plugin and all seems in order.” C’mon, add some uformats to your site. Maybe this hCalendar tutorial will help?

Third: Making and Breaking Stuff

Last weekend I was lucky enough to talk about Web Services (and Ruby) at the Silicon Valley Ruby Conference. Some real smart people gave presentations and I pretended to be one of them. Anyway, I posted my incomprehensible slides over at SlideShare. There is some fun code sprinkled throughout, as well as some Hpricot goodness, so have at.

Fourth: mofo two point oh

Okay, it’s really just 0.2.3, but Steve Ivy, Olle Jonsson, Christian Carter, Andrew Turner, Grant Rodgers, and Denis Defreyne have fixed so many bugs and added so many new features that it might as well be Enterprise Ready™.

The newly supported formats include XFN, Geo, Adr, and hResume.

Try it out:

>> olle3 = HResume.find('http://www.linkedin.com/in/olleolleolle')
=> #<HResume:0x20ae428 ...>
>> olle3.skills
=> "Ruby on Rails passion. PHP/MySQL veteran..." 
>> olle3.contact.fn
=> "Olle Jonsson" 

We’ve also fixed autoloading, added support for the include pattern, added proper ISO8601 date handling, created an after_find callback (example here), made base url figurin’ actually figure, and moved to Echoe. If I missed something it’s just because I’m so excited.

Oh yeah, mofo finally has its own webpage. About damn time.

Anyway, grab it: $ sudo gem install mofo

Fifth: Javascripting

While using Ruby to parse microformats is fun and educational, Javascript is the real win.

Brian Donovan has some hcard.js wizardry while Dan Webb released the gloriously named sumo. The guy who wrote Operator, Mike Kaply, also has a bunch of Firefox and Javascript specific info over at his blog.

Sixth: Around the Web

Dr Nic’s MyConfPlan will soon let you add conferences simply by providing the url of an hCalendar-ready webpage, using mofo. Be on the lookout.

The big hAccessibility debate is really shaking things up, which is sometimes exciting and sometimes crazy.

While it’s got a lot of noise, you can kinda follow the microformat world over at the planet. Therein you may stumble upon blog posts about intercepting microformats in Rails input or versatile RESTful APIs beyond XML.

Finally, the Code in Motion guys wrote about their experience adding microformats to their stolen bike list. Good write-up and great cause.

If you’ve recently added microformats to a Railsy site, let us know in the comments. It’s cool to see them getting more and more adoption and, more importantly, more and more practical.

Sunday April 29th, 2007

5.2

Nginx Config Like Whoa

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

I’ve been using Nginx (engine-x) for some time now and really digg it. So much simpler and nicer than Apache. Ezra apparently discovered the thing, a hidden Russian gem in the endless desert of HTTP servers, sometime last year.

The problem I’ve been having is managing my vhosts. There’s probably a solution out there already, yeah. Adam Keys has one idea. It’s nice, and it even has Capistrano support, but it’s Rails centric. Cheat, Subtlety, and require are not Rails sites. I need a more global solution.

Jamie van Dyke has his cheese solution and topfunky provided all his Nginx scripts, but I wanted something I could use to easily add new sites. Without thinking.

Here, take this:

$ sudo gem install nginx_config_generator

The nginx_config_generator is a gem which takes a YAML file of options for Nginx vhosts and spits out a complete Nginx-friendly config file. I’m using it for Err and other debaucherous apps hosted right here on this very machine.

Okay, get started:

$ generate_nginx_config ‐‐example > config.yml

This’ll create an example config file for you. Yep, YAML. It looks just like this.

Run the generator on that example config file and tell it what you want to call your nginx file. Like:

$ generate_nginx_config config.yml nginx.conf

If your out file (nginx.conf) already exists and you want to overwrite it, use the -y option.

The generator will respect two environment variables: NGINX_CONFIG_YAML and NGINX_CONFIG_FILE. These’ll make spittin’ out your modified config even easier.

Check the README for anything I may have missed. Source is right here. SVN? svn://errtheblog.com/svn/projects/nginx_config_generator.

If you have ideas for alternate options, I’d love to hear (and add) them. I’ll be growing what the config file supports as my nginx-fu grows stronger. For now this is all I’ve needed, but I can see SSL and alternate location declarations very clearly in the near future.

(Throw bugs at Lighthouse, please.) (Thanks.)

Thursday March 29th, 2007

7.2

Sexy Migrations

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

Damn, that new release of Hobo reminds me: those guys have got some nice looking migrations. Let’s take it a step further. Let’s bring sexy back.

Let’s turn this:

class UpdateYourFamily < ActiveRecord::Migration
  create_table :updates do |t|
    t.column :user_id,  :integer
    t.column :group_id, :integer
    t.column :body,     :text
    t.column :type,     :string

    t.column :created_at, :datetime
    t.column :updated_at, :datetime 
  end

  def self.down
    drop_table :updates
  end
end

Into this:

class UpdateYourFamily < ActiveRecord::Migration
  create_table :updates do
    foreign_key :user
    foreign_key :group

    text   :body
    string :type

    timestamps!
  end

  def self.down
    drop_table :updates
  end
end

Using this:

SVN:
$ ./script/plugin install \ svn://errtheblog.com/svn/plugins/sexy_migrations

Piston:
$ piston import svn://errtheblog.com/svn/plugins/sexy_migrations \ vendor/plugins/sexy_migrations

That’s it. It works with them newfangled Independent Migrations and is 100% backwards compatible. Keep your old migrations, sex up your new migrations. But, be careful: it’s real hard to go back to the ‘old way’ of writing migrations once you start getting your sexy on. You’ve been warned.

Bugs!

You heard that Lighthouse launched, right? Right. From now on, any bugs found in any Err plugins should be reported at the Err plugins Lighthouse project. You can jump straight to creating a new ticket if you’d like. (Just make sure to include the name of the plugin you’re reporting a bug for.)

Oh, and speaking of bugs: you may want to follow Err on Twitter. We’ll be using it to post updates about our projects and plugins and blogs, oh my. Oh yeah.

Tuesday March 27th, 2007

5.6

Vendor Everything

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

Seriously. You’ll thank me later.

What do I mean, exactly? Well, let’s say you’re working on a small Rails team. You decide to start using test/spec. Be dee dee. As usual, you gem install test-spec then Pistonize the plugin. You start writing specs and begin converting existing tests to specs. You’re on a tear. Nothing can stop you. Behavior is king. You commit your changes.

You break the build.

What? Why? Well, all your new specs depend on the test-spec gem, a gem your comrades and continuous integration builder do not have.

Quickly: fix it! Tell everyone to install the gem locally. Install the gem on your staging server. Carefully install the gem on your production server. Phew. Everyone’s got the same version, right? Right. Well, maybe. (At least the build works.)

See, there’s something wrong with this scenario: it’s not very DRY. Why should only our code be DRY? Why not our environment, too? It should be.

The solution we’ve come up with is to throw every Ruby dependency in vendor. Everything. Savvy? Everyone is always on the same page: we don’t have to worry about who has what version of which gem. (we know) We don’t have to worry about getting everyone to update a gem. (we just do it once) We don’t have to worry about breaking the build with our libraries. (we leave that up to the internet entrepreneurs)

Adding Depended Sees Alright, alright, let’s see how we’d vendor the test-spec gem. First, the vendor directory:
$ ls -1 vendor/
bin
data
gems
plugins
rails

We obviously added a few directories, namely gems, data, and bin. The rails and plugins directories should be familiar, one hopes.

Let’s focus on the the vendor/gems directory:
$ ls -1 vendor/gems/
RedCloth-3.0.4
RubyInline-3.6.2
crypt-1.1.4
image_science-1.1.1
memcache-client-1.3.0
session-2.4.0
sphinx-0.9.7-rc2
We’ll cd in there and then use the handy gem unpack command to, erm, unpack the contents of our test-spec gem:
$ gem unpack test-spec
Unpacked gem: 'test-spec-0.3.0'

Cool. Now we need to dive into our config/environment.rb file to ensure Rails knows to look in vendor/gems/test-spec-0.3.0/lib when we try to require ‘test/spec’. It’s easier than you think.

Add this to your Rails::Initializer.run block:

config.load_paths += Dir["#{RAILS_ROOT}/vendor/gems/**"].map do |dir| 
  File.directory?(lib = "#{dir}/lib") ? lib : dir
end

Now all the libraries in vendor/gems will automatically be included in your load path, complete with a lib check for libraries like RubyInline and crypt (which don’t come with lib directories).

Want to only include some libraries in a specific environment? Maybe you don’t want RubyInline or image_science in development mode. Put this under the above snippet o’ code:

if %w(development test).include? RAILS_ENV
  config.load_paths.delete_if { |f| f =~ /RubyInline|image_science/ }
end  

Good to go.

It should be noted that this trick will not auto-require the gems for you. You still need to do that in your config file, or in your gems, or wherever. Maybe with a line like %w(crypt/blowfish redcloth).each { |f| require f } in your config/environment.rb if it pleases you.

Other Approaches

For one, Dr Nic has something cool: this patch. It lets you run tasks from within vendor/gems right from your RAILS_ROOT. Nifty. He’s also got his gemsonrails plugin which is a similar (but different) approach than we illustrate here.

Jay Fields has his own method for autoloading gems in vendor. Worth a look, and a listen.

Classic Railer topfunky, back in the day, rolled a rake task to scratch the same itch.

Then there’s this thread on Rails-Core about adding some kinda gemy-ness to Core which, unfortunately, hasn’t yet transpired. The gem in question, the one to metaly manage other gems, lives in technoweenie’s repository.

I even do this (for better or worse) with cache_fu, btw. I am starting to really like not being dependent on the environment.

All Set!

The goal here is simple: always get everyone, especially your production environment, on the same page. You don’t want to guess at which gems everyone does and does not have. Right.

There’s another point lurking subtlety in the background: once all your gems are under version control, you can (probably) get your app up and running at any point of its existence without fuss. You can also see, quite easily, which versions of what gems you were using when. A real history.

Tuesday March 20th, 2007

6.0

A Zoned Defense

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

Supporting timezones is a pain in the ass, but it’s a necessary evil for some sites and there are two working solutions as I see it.

Observe: caboose “the official way” why “the slick way”

The first one involves a fair amount of setup including one gem and two plugins, and the second one is too slow.

Err’s gonna give you a third option: zoned

We take the best of both worlds and mash them together into a plugin that consists of about 10 lines of ruby and the same amount of javascript.

We’ve been using it on Chowhound for the past month with positive results, even through the dreaded DST change. Here’s how it works:

The Javascript
var Timezone = {
  set : function() {
    var date = new Date();
    date.setTime(date.getTime() + (1000*24*60*60*1000));
    var expires = "; expires=" + date.toGMTString();
    document.cookie = "timezone=" + (-date.getTimezoneOffset() * 60) + expires + "; path=/";
  }
}

This sets a timezone cookie with the value returned from the essential date.getTimezoneOffset() function. This is important because you need the client’s offset, ruby can’t help you here.

The Ruby
module Zoned
  mattr_reader :server_offset
  @@server_offset = Time.now.gmtoff

  def zoned(date)
    return date unless timezone = controller.send(:cookies)[:timezone]
    date - (server_offset - timezone.to_i)
  end
  alias :z :zoned
end
All this does: Returns the date untouched if the cookie isn’t set Determines the server’s GMT offset Takes the difference of the server’s offset with the client’s offset and the date passed

The second bullet point is unnecessary, but an added bonus for anyone with a server not running UTC which is a requirement for almost all other timezone solutions.

How we’re using it Login/Signup:
<% form_for @login, { :html => { :onsubmit => "Timezone.set()" } %>
Logout:
cookies.delete(:timezone)
Views:
<%= z(post.created_at).to_s(:long) %>
Final thoughts

There are some issues you have to be cool with if you use this plugin (or come up with a solution):

None of the dates will convert if the client’s javascript is disabled When the date is converted, it will still think it’s in the original timezone If DST happens or they change their computer’s timezone, they will need to log out and log back in to update their timezone cookie If their computer’s timezone is set incorrectly, so will the updated time

In the case of something major like Daylight Saving Time, making sure their timezone cookie has been reset is as easy as changing the salt value used in our cookies to invalidate all of the existing ones, thus forcing them to log back in.

Per usual, install via:

./script/plugin install svn://errtheblog.com/svn/plugins/zoned
Overtime

You can convert everyone’s timezone (not just those logged in) via:

window.onload = Timezone.set

On the first page load it will show the server’s timezone, but on any subsequent loads the time will convert correctly.

Another idea is just to make an Ajax.Request in Timezone.set call an action that will save the user’s offset in the database so you don’t have to rely on a cookie.

Think of this plugin more as the building block that’ll cater to whatever suits your site best. Let us know how you’re using it.

Friday February 16th, 2007

3.8

alias_method_bling

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

Lately I’ve been thinking a lot about How and When. Two old friends. They’re pretty quiet, but play a huge role (I think) in making us better at doing what we do.

Consider this: you know how to use alias_method_chain (you read Marcel’s post back in April (if you didn’t just keep reading)), but do you know when? Maybe. Maybe… not. Care to join me for a short journey through the land of Refactoring and Bling? Alias method bling?

Training Wheels (or The How in How&When)

(Skip this section if you already know the How. (!))

The basics are this: you have a method. You want to add functionality to that method by wrapping it. Of course, you’re gonna use alias:

alias :real_name :name
def name
  "My name is #{real_name}." 
end

As you know, this points real_name to name as it exists at the time of the alias call, allowing you to safely override name while maintaining a reference to the original version.

There are problems with this approach on a big project. Imagine you want to override render to provide benchmarking powers:

alias :real_render :render
def render(*args)
  benchmark("Render call") do
    real_render(*args)
  end
end

Okay, yeah. Now let’s say you want to layer another bit of functionality onto render. Disaster. alias_method_chain prevents these conflicts of interest:

def render_with_benchmark(*args)
  benchmark("Render call") do
    render_without_benchmark(*args)
  end
end
alias_method_chain :render, :benchmark

Our little friend will, behind the scenes, alias render to render_without_benchmark then promptly alias render_with_benchmark to render. Now we can add thinks like alias_method_chain :render, :layout and not worry about breaking other aliases. They all layer (safely) on top of one another.

Check the Rails documentation for further examination.

That’s the How. And even the When, in Rails. But When should you apply this?

Well, plugins.

Ballin’ Out of Control (or Refactoring acts_as_cached)

A while ago I released a Rails plugin called acts_as_cached. “This is messy, but it has to be messy!? I told myself. “This is complicated code.? Also: “This is as clean as I can get it.?

Sound familiar? Of course. I knew all the mantras: “Make each method do one thing.? “Separation of concerns.? “Be modular.? Those, however, are just words. Fun to say, but just words. I think in code.

Here’s the old get_from_cache method:

def real_get_from_cache(key)
  cache_benchmark "Tried to get #{key} from cache." do
    # Try to grab from cache, lazy load any classes which Rails can't find.
    # Re-raise non-load related exceptions.
    begin
      if cache_config[:local_cache]
        cache_config[:local_cache][key] ||= cache_store.get(key) 
      else
        cache_store.get(key) 
      end
    rescue MemCache::MemCacheError => e
      # Memcache API swallows const errors.
      lazy_load ||= Hash.new { |hash, hash_key| hash[hash_key] = true; false }
      klass_file = e.to_s.split.last.underscore
      if e.to_s =~ /undefined class/ && !lazy_load[klass_file]
        require_dependency klass_file
        retry
      else 
        raise e
      end
    end
  end if cache_store
end

Wow. What. The. Hell.

Take a deep breath. Okay, there are a few things going on here:

Use the local_cache if it exists Lazy load classes (super messy) Benchmark the call Do nothing if no cache_store exists Get from the cache_store


That’s, like, more than one thing. Bad bad bad. How can we clean this up? alias_method_chain, mostly.

Slim Pickin’ (or The Retrofacted get_from_cache)

Without going into any detail yet, here’s the new version. I renamed it fetch_cache and cleaned it up a bit:

def fetch_cache(id)
  return if ActsAsCached.config[:skip_gets]

  autoload_missing_constants do 
    cache_config[:store].get(cache_key(id))
  end
end

What. The. Hell. It still does more than one thing, you could argue, but it’s definitely a lot cleaner and easier to maintain. Where’d all that other code go, though? Did I throw it out? Functionality be damned!

No: I bling’d it.

The Bling (or The Chain)

What I did was identify all the different aspects of get_from_cache and move each bit of functionality into its own module. One for the local_cache stuff, one for disabling the cache, one for benchmarking, etc.

For instance, in the LocalCache module I have fetch_cache_with_local_cache:

def fetch_cache_with_local_cache(*args)
  @@local_cache[cache_key(args.first)] ||=
    fetch_cache_without_local_cache(*args)
end

Activated like so:

alias_method_chain :fetch_cache, :local_cache  

It lovingly wraps the concise, simple fetch_cache I have already shared with you to provide additional functionality.

Likewise, in my Benchmarking module I have fetch_cache_with_benchmarking:

def fetch_cache_with_benchmarking(*args)
  cache_benchmark "Got #{cache_key args.first} from cache." do
    fetch_cache_without_benchmarking(*args)
  end
end  

Activated like so:

alias_method_chain :fetch_cache, :benchmarking

Etc. If you want to use the local_cache stuff, the alias_method_chain will be called. If you want to use the benchmarking stuff, the alias_method_chain will be called. If you don’t use benchmarking, fetch_cache will be left alone.

You can start to see the benefit of layering different methods on top of each other without interfering with the base functionality. The code is just easier to understand this way.

Three Dollar Bill (or Added Advantages)

While it may seem we’re spreading fetch_cache logic all over the source tree, what we’re really doing is grouping related methods together in convenient, simple modules. There isn’t just fetch_cache_with_benchmarking in the benchmarking module; there’s also set_cache_with_benchmarking and expire_cache_with_benchmarking. All the benchmarking methods are together. All the local_cache methods are together. Etc.

This makes it easy to edit all the benchmarking code at one time, and even easier to add entirely new functionality to select methods. We could drop in a new module, wrap what we need to with alias_method_chain, add a conditional check to the acts_as_cached method to include it, and rock out.

Bonus Round (or Bonus Round)

You may have noticed that my refactored fetch_cache method includes a method called autoload_missing_constants, which takes a block. Rather than keep the re-loading code in my method, I wanted it to have its own method. Right? To keep things clear. Here it is:

def autoload_missing_constants
  yield
rescue ArgumentError, MemCache::MemCacheError => error
  lazy_load ||= Hash.new { |hash, key| hash[key] = true; false }
  retry if error.to_s.include?('undefined class') && 
    !lazy_load[error.to_s.split.last.constantize]
  raise error
end  

Blocks are fun. This keeps the fetch_cache method even more focused.

Diamond Grills (or Other Resources)

I mentioned the documentation for further alias_method_chain understanding. There’s also some explanation and a few examples in Ezra’s great PDF on the Rails request lifecycle. The cache_fu source is available for perusal, as are various hacks strewn throughout pastie. Finally: technoweenie’s got some bling scattered throughout his plugins. Naturally.

Monday February 5th, 2007

I Will Paginate

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

We all remember the story: guy says don’t use pagination, guy gets 700+ diggs, people yell at guy.

What they had missed was that he didn’t say there was something inherently wrong with pagination (we use it, they use it), but that the Rails pagination code is really bad.

So, Err’s gonna end your troubles by stealing some PHP code. The horror, but as the wise DHH once said:

“When something is too hard it means that you’re not cheating enough.?

Couldn’t agree more. Chowhound was in desperate need of a better pagination solution to help the search bots crawl its 400k topics with ease. I have cool features to build and this is a solved problem, so I go to Google.

30 seconds later, I find Digg-style Pagination. As I mentioned, it’s written in PHP, but there’s something truly statisfying when you take 120 lines of PHP and slim it down to around 40 in Ruby.

The gent even provided some starter CSS so it doesn’t look half-bad from the get-go.

Nice, here’s how it works:

app/models/post.rb
class Post < ActiveRecord::Base
  cattr_reader :per_page
  @@per_page = 50
end

app/controller/posts_controller.rb

def index
  @board = Board.find(params[:id])
  @page  = params[:page].to_i.zero? ? 1 : params[:page].to_i
  @posts = Post.find_all_by_board_id(@board.id, 
               :offset => (@page - 1) * Post.per_page, 
               :limit => Post.per_page)
end
app/views/posts/index.rhtml
<%= will_paginate(@board.topic_count, Post.per_page) %>

Your views will paginate, the code says so.

Grab the plugin here: ./script/plugin install \ svn://errtheblog.com/svn/plugins/will_paginate

Inspect the code here: http://require.errtheblog.com/plugins/browser/will_paginate/lib/will_paginate.rb

Monday January 22nd, 2007

RSSin' Your SVN

Err the Blog - Home From Err the Blog - Home, 1 year ago, 0 comments Comment

As you well know, we’ve of late been on the Piston kick for our Rails plugins. Lots of great libraries in our vendor/plugins directory, all stored locally and sync’d with the author’s repository. Real nice. But, there’s a problem. One last something standing in the way. Specifically: How do you know when a remote repository is updated? You need to know about changes to sync (read: update). Heaven forbid we miss a killer new feature.

You can follow the blogs, or check the Rubyforge, or grab the Trac feed, but sometimes none of these things are available. Sometimes all you really have is the repository.

Sometimes you need Subtlety.

subtlety: a remote subversion excursion

Subtlety is a little Camping mini-site cooked for this very purpose. Give it a public repository’s url (starting with http:// or svn://) and it gives you an RSS feed of the changes. That’s it. Really.

Get Rollin’

Get started by subscribing to the mofo changelog. Or maybe you prefer Cheat? _why’s sandbox? Some inferred routes action? You get the picture.

Be creative. The guys over at Nuance Labs rolled up the RSSes for all their plugins into a single feed, thanks to Google Reader. How cool is that?

Let me know if you have any problems. Pass it on. Etc. Feeds are cached for 15 minutes, so be patient when trying it out.

More importantly, have fun and be responsible.

Update: Private Repositories

Some folks have asked how they can use Subtlety with private repositories. Well, you can’t. But you can roll your own.

One solution is the svn2rss.py Python script included with Subversion. Because I know how much you love Python.

Another option is to do what technoweenie did and create your own RSS feed using XSLT. This is the foundation upon which Subtlety is built, you see.

Finally, if you must, you can checkout the Subtlety code and hack it as you see fit:

$ svn co svn://errtheblog.com/svn/subtlety

If you go this route you may want to grab (of course) the subtle Subtlety RSS Feed.

Come up with a cool hack? Find a bug? Be sure to let me know. The comments work. So does chris[at]ozmm[dot]org.

« older items