Close and Go BackBack to Viget

Creating Gems with Mr Bones

Clinton R. Nixon
Clinton R. Nixon, Senior Developer, March 31, 2008 0

Ruby users are really lucky to have Rubygems. Outside of Perl’s CPAN, I can’t think of an easier distribution system for language-specific libraries. Creating gems for distribution, though, takes a little more knowledge. Creating them by hand is workable, but quickly becomes a chore.

Luckily, there are several tools to help you build your own gems. hoe and newgem are the best-known, and have a lot of good qualities. However, hoe adds itself as a dependency to your gem, and newgem has become a very large tool, one that I find unwieldy when I want to create and deploy a gem quickly.

My favorite tool is Mr Bones by Tim Pease. It’s lightweight, featureful, and does not add dependencies to your project. To create a project with it, you just run bones <my_project_name> on the command line, and a skeleton is built for you, complete with a lib directory for your code, a bin directory for your tools, and a test directory. The configuration is in a Rakefile, and it’s clear and concise. Here’s the configuration for my recent project, a Ruby client for the FriendFeed API:

load 'tasks/setup.rb'

ensure_in_path 'lib'
require 'friend-feed'

task :default => 'test'

PROJ.name = 'friend-feed'
PROJ.authors = 'Clinton R. Nixon'
PROJ.email = 'crnixon@gmail.com'
PROJ.url = 'friend-feed.rubyforge.org'
PROJ.rubyforge_name = 'friend-feed'
PROJ.dependencies = ['json']
PROJ.version = FriendFeed::VERSION
PROJ.exclude = %w(.git pkg)

Mr Bones has the standard set of features you’d expect: you can use it to package up gems and tarfiles of your library, as well as release it on RubyForge and deploy your documentation there. Its killer feature, though, is its ability to freeze its skeleton in your home directory. When you run bones --freeze, a directory named .mrbones is copied into your home directory. You can edit the files in there to make a skeleton for your gems that works the way you work, and from then on, when you run bones to create a new gem, it will use your personal gem skeleton. You can unfreeze Mr Bones by running bones --unfreeze and your skeleton will be backed up, and the default skeleton will be used again.

For a tool to create RubyGems that focuses on usability, I highly recommend Mr Bones. If you know of another tool we should check out, let us know in the comments!

cURL and Your Rails 2 App

David Eisinger
David Eisinger, Web Developer, March 28, 2008 5

If you’re anything like me, you’ve used cURL to download a batch of MP3 files from the web, or to move a TAR file from one remote server to another. It might come as a surprise, then, that cURL is a full-featured HTTP client, which makes it perfect for interacting with RESTful web services like the ones encouraged by Rails 2. To illustrate, let’s create a small Rails app called ‘tv_show’:

rails tv_show
cd tv_show
script/generate scaffold character name:string action:string
rake db:migrate
script/server

Fire up your web browser and create a few characters. Once you’ve done that, open a new terminal window and try the following:

curl http://localhost:3000/characters.xml

You’ll get a nice XML representation of your characters:

<?xml version"1.0" encoding="UTF-8"?>
<characters type="array">
  <character>
    <id type="integer">1</id>
    <name>George Sr.</name>
    <action>goes to jail</action>
    <created-at type="datetime">2008-03-28T11:01:57-04:00</created-at>
    <updated-at type="datetime">2008-03-28T11:01:57-04:00</updated-at>
  </character>
  <character>
    <id type="integer">2</id>
    <name>Gob</name> 
    <action>rides a Segway</action>
    <created-at type="datetime">2008-03-28T11:02:07-04:00</created-at>
    <updated-at type="datetime">2008-03-28T11:02:12-04:00</updated-at>
  </character>
  <character>
    <id type="integer">3</id>
    <name>Tobias</name>
    <action>wears cutoffs</action>
    <created-at type="datetime">2008-03-28T11:02:20-04:00</created-at>
    <updated-at type="datetime">2008-03-28T11:02:20-04:00</updated-at>
  </character>
</characters>

You can retrieve the representation of a specific character by specifying his ID in the URL:

dce@roflcopter ~ > curl http://localhost:3000/characters/1.xml <?xml version="1.0" encoding="UTF-8"?> <character> <id type="integer">1</id> <name>George Sr.</name> <action>goes to jail</action> <created-at type="datetime">2008-03-28T11:01:57-04:00</created-at> <updated-at type="datetime">2008-03-28T11:01:57-04:00</updated-at> </character>

To create a new character, issue a POST request, use the -X flag to specify the action, and the -d flag to define the request body:

curl -X POST -d "character[name]=Lindsay&character[action]=does+nothing" http://localhost:3000/characters.xml

Here’s where things get interesting: unlike most web browsers, which only support GET and POST, cURL supports the complete set of HTTP actions. If we want to update one of our existing characters, we can issue a PUT request to the URL of that character’s representation, like so:

curl -X PUT -d "character[action]=works+at+clothing+store" http://localhost:3000/characters/4.xml

If we want to delete a character, issue a DELETE request:

curl -X DELETE http://localhost:3000/characters/1.xml

For some more sophisticated uses of REST and Rails, check out rest-client and ActiveResource.

Rails 2 and test/spec

Clinton R. Nixon
Clinton R. Nixon, Senior Developer, March 24, 2008 1

We’ve been flirting with behavior-driven development here at the Labs recently, and have tried out RSpec and test/spec. Both have advantages, but I like test/spec a little more: it works well with existing Test::Unit tests and has a syntax I find more natural.

Somewhere along the path to Rails 2, test/spec stopped working well for me. New test classes — ActiveSupport::TestCase, ActionController::TestCase, and ActionMailer::TestCase — were introduced to eliminate repeated code in Rails tests, and test/spec classes, which inherit from Test::Unit::TestCase, suddenly didn’t transparently work.

Working on a personal project this weekend, I decided to figure out how to use test/spec again. Digging through its code, I found this gem:

def context(name, superclass=Test::Unit::TestCase, klass=Test::Spec::TestCase, &block)
  (Test::Spec::CONTEXTS[self.name + "\t" + name] ||= klass.new(name, self, superclass)).add(&block)
end

So now, if I want to use test/spec in my Rails tests, I just put the superclass after the context name, like so:

# For models
context "User", ActiveSupport::TestCase
  ...
end

# For controllers
context "User Controller", ActionController::TestCase
  ...
end

# For mailers
context "User Mailer", ActionMailer::TestCase
  ...
end

When Cookies Fail

Mark Cornick
Mark Cornick, Web Developer, March 21, 2008 2

Since I installed the Safari 3.1 update the other day, I repeatedly ran into a weird problem: the “Accept cookies” preference is being mysteriously reset to “Never.” This isn’t a tech support forum (and I’m sure “just use Firefox” would be a popular response if it were); rather, I’m here to talk about what happens when I try to use Safari after this bug crops up.

Just about every significant web application uses cookies. Despite the paranoia that accompanied their introduction over a decade ago, cookies are an essential part of the modern web application’s diet. One of the most common uses of a cookie is to persist an authentication state. For instance, when I log in to an application, it’ll send a cookie with a key like “user_authentication” and some value. As long as this cookie gets sent back with each subsequent HTTP request, we know the user’s authenticated. It’s a common pattern, and one that works very well 99% of the time.

That other 1%? That’s what’s happened to me since this Safari bug cropped up. If cookies aren’t enabled in a user’s browser, authentication doesn’t work. The application never gets the cookie it expects to receive, and so assumes the user isn’t logged in. So what does your application do in this case? How does it recover?

A well-behaved application would detect that it’s missing the desired cookie, and present some sort of helpful error message. Gmail, for instance, redirects you to a page stating that Gmail requires cookies, and the user should check the browser’s preferences to make sure cookies are enabled. You don’t get where you want to go, but at least you’re not in the dark.

Then there are the not-so-well-behaved applications. Twitter, for instance, doesn’t provide any useful feedback to indicate that it’s missing its authentication cookie. It just kicks you back to the same login page, without the courtesy of telling you what’s going on. (This is how I discovered there was something going on with my Safari. I tried logging in, which failed. I tried again. Still no dice. It wasn’t until I tried Gmail that I got the clue.)

So, fellow web developer… what does your application do in this case? Try authenticating with cookies turned off. Do you inform the user of the problem, or do you leave the user in the dark? I’m sorry to say that at least one of my applications left the user in the dark. (Consider my wrist slapped.)

It’s very easy for web developers to take cookies for granted. We rely on the built-in session persistence in frameworks like Rails, which invariably depend, in some way, on cookies. We expect that users will have cookies enabled; that expectation is probably valid in most cases. But when it’s not, we need to keep the users’ experience positive by helping them understand what’s going on. Otherwise, you’ve got frustrated users who can’t understand why they can’t get to their previously-favorite application—and it’s all downhill from there.

No Query String? No Problem.

Patrick Reagan
Patrick Reagan, Development Director, March 19, 2008 5

When rewriting URLs on your site with mod_rewrite, you’ll typically want to preserve the query string while redirecting your users to the new location.  You can accomplish this by either clobbering the existing query string:

RewriteRule ^category/tech/?$ http://www.viget.com/extend/ [R=301,L]

Or appending to an existing one (with the QSA option):

RewriteRule ^category/tech/?$ http://www.viget.com/extend/ [QSA,R=301,L]

Both of these rules assume that you want to preserve the query string, but what if you want to remove it entirely?  We needed to do exactly that when moving the existing Viget site over to its new incarnation.  Instead of our news items having antiquated ID-based URLs, each post now has a nice SEO-friendly “slug.” A quick attempt at redirecting one of these news items looked like this:

RewriteCond %{QUERY_STRING} ^id=1889
RewriteRule ^news-detail.html$ http://www.viget.com/blog/nixon_reagan_williams_featured_in_computerworld/ [R=301,L]

If we dissect this, the rule states: for a query string containing ‘id=1889’, permanently redirect the ‘news-detail.html’ URL to this new one and don’t process any other rules.  When it came time to test it out, it mostly worked, but would append ?id=1899 to the URL.  Even though we didn’t specify how to handle query strings, the original was added to the end of the URL – something we definitely didn’t want.  The official mod_rewrite documentation yielded this tip:

By default, the query string is passed through unchanged. You can, however, create URLs in the substitution string containing a query string part. Simply use a question mark inside the substitution string to indicate that the following text should be re-injected into the query string. When you want to erase an existing query string, end the substitution string with just a question mark. To combine new and old query strings, use the [QSA] flag.

After simply adding a question mark to the rewritten URL, we now had the desired behavior:

RewriteCond %{QUERY_STRING} ^id=1889
RewriteRule ^news-detail.html$ http://www.viget.com/blog/nixon_reagan_williams_featured_in_computerworld/? [R=301,L]

Try it out for yourself.

A Development Community for Viget Labs and Beyond

Every team member here at Viget Labs strives to be an innovator. We members of the development team are no different - that's why we're constantly engaging in community discussions and exploring the unknown that is the next generation of open-source web applications.

Viget Is Hiring!

Viget has job openings for Ruby Developers, Interns, and Front-End Developers. Learn More »

Recent Comments

In my quick testing of this it does still work if you chain items after the cache name:

Category.top_level.other_scope

But important to note is this will still make a call to the database, it will not take advantage of the cache. Of course the actual scope, in this case find_top_level is unchanged and so you can still do any chaining with that, which also of course won’t use the cache.

As a final note though if you’re needing to do much chaining, caching in this way may not be best for your particular situation. The idea of the cache is if you need to retrieve the exact same result set over and over again, and it rarely changes you shouldn’t have to hit the database.