Last checked about 14 hours ago.
83 people have subscribed to this feed.
post frequency (last month)
PostRank™
From Err the Blog - Home, 9 days ago,
0 comments
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!”
From Err the Blog - Home, 1 month ago,
0 comments
GitHub is a pretty fun site to work on, I’m not gonna lie. On more than one occasion, we thought it would be pretty cool to setup a service allowing public projects to receive donations.
Pledgie being the venerable service it is, I decided one night it couldn’t possibly be that hard to integrate it with GitHub. It’s a pretty standard Rails site with some simple forms to make the magic happen (eg. setup a donation page).
They don’t have an API (yet), but in this day and age, you really don’t need one if you procure the proper tools.
Enter Mechanize. You can do all I’m about to describe with just Net::HTTP, but seriously, who wants to do that?
Step 0: Drive girlfriend to airport, buy a case of Anchor Steam, and turn off the Xbox.
Step 1: Sign up for a Pledgie account, cause GitHub’s a regular user after all.
Step 2: Write the interface on GitHub to accept the user’s Paypal address.
Step 3: Figure out the form fields I should be filling out to login and create a new pledge on Pledgie.
Step 4: Write the Mechanize code:
def pledgify(email) agent = WWW::Mechanize.new page = agent.get('http://pledgie.org/accounts/login') form = page.forms[1] form['account[login]'] = GitHub::PledgieUser form['account[password]'] = GitHub::PledgiePass page = agent.submit(form) link = page.links.text(/Create A Campaign/) page = agent.click(link) form = page.forms[1] form['campaign[title]'] = "FooBarz" form['campaign[paypal]'] = email form['campaign[description]'] = "The best project evar!" form['campaign[end_date(1i)]'] = 10.years.from_now.year.to_s page = agent.submit(form, form.buttons.last) update_attribute(:pledgie, page.uri.to_s[/\d+/]) end
Step 5: Take the pledgie attribute we just grabbed and put a cool badge in their repository’s detail box.

Step 6: Watch the millions pour in for GitHub’s hard-working open source committers.
The obvious caveat here is that it relies on Pledgie not drastically changing the structure of its HTML, but it’s incredibly satisfying to throw something together like this in such a short amount of time.
PS. If you’re on GitHub and wondering how you missed the original announcement, the post is here: http://github.com/blog/57-getting-paid-the-open-source-way
From Err the Blog - Home, 1 month ago,
0 comments
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.
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?
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.
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.
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 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’.
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.
From Err the Blog - Home, 4 months ago,
0 comments
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.
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.
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.
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.
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.
From Err the Blog - Home, 5 months ago,
0 comments
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.
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))"
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.
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
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.
From Err the Blog - Home, 5 months ago,
0 comments
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.
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.
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.
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.
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.
From Err the Blog - Home, 5 months ago,
0 comments
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.
From Err the Blog - Home, 6 months ago,
0 comments
In a timely holiday manner, we present to you a short list of will_paginate resources. Please enjoy responsibly.
Sightings
Since its inception, millions of people have paginated billions of records using will_paginate. A few notable examples:
Err Free's FamSpam MTV's Real World Casting Beast Warehouse DeMontrond RV (with ajax!) Grabb.itKnow a site that belongs on this list? Let us know in the comments.
New FeaturesJust 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 ViewsFor 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 PaginationThis 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 RailsCastThe 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 ActiveRecordThere'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 IntegrationBrandon 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 IntegrationWhile 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 IntegrationPerhaps 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 PluginsA 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 BugtrackerFound 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 GroupDid 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 RDocFinally, 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 wrapHey, where did 2007 go? I'm getting sucked into 2008 faster than Perl into obscurity.
Keep paginatin', Railers. See you next year.
From Err the Blog - Home, 7 months ago,
0 comments
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 PluginsIt’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
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.rbCaution: 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.rbclass 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_cropperOkay, 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.
From Err the Blog - Home, 8 months ago,
0 comments
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!
From Err the Blog - Home, 9 months ago,
0 comments
Chris made migrations sexy. So sexy, in fact, that DHH committed a variation of his plugin to trunk.
I’m not here to make things sexy. I’m here to make migrations drop-dead gorgeous.
Remember that file that no one uses and normally leaves of subversion?
No, not doc/README_FOR_APP. I’m talking about db/schema.rb of course.
Turns out with this plugin I wrote, it’s the baddest file in your source code.
“How bad, PJ?” you ask.
So bad that when you change your schema.rb from
ActiveRecord::Schema.define(:version => 1) do
create_table :posts do |t|
t.string :title
t.text :body
end
end
to
ActiveRecord::Schema.define(:version => 1) do
create_table :posts do |t|
t.string :title
t.text :body
t.integer :published
end
create_table :comments do |t|
t.string :name, :url
t.text :body
t.integer :post_id
end
end
and run
$ rake db:auto:migrate
it’ll execute the following:
-- add_column("posts", :published, :integer)
-> 0.0096s
-- create_table(:comments)
-> 0.0072s
Pretty slick. Run the task again and nothing will happen, just like regular migrations, but change the file and the plugin will do its best to figure out what you’ve done.
What it is ChiefThe plugin’s logic is fairly straightforward. It’ll look at the tables & fields you’ve defined in the schema, compare those to the tables & fields in the database, and figure out what it needs to add and drop.
I’ll be adding support for indexes (added, check below) and type changes shortly.
LimitationsNow, I’m sure you’ve been thinking of all of the cases where this isn’t going to work. Right off the bat, it’s clear you couldn’t use this if you wanted to change the name of an existing column. There’s no way for auto_migrations to know your actual intention, it would drop the original column and create the new one.
But, Can It Scale?!That having been said, this plugin doesn’t prevent you from running regular migrations if you feel the need. The only thing to keep in mind is that running regular migrations will overwrite schema.rb, so you’ve been forewarned if you’ve spent a long time making the file look pretty.
I’m willing and able to accept patches to make auto_migrations more useful, though. My knowledge of ActiveRecord is seriously lacking, so if there’s room to clean stuff up, please let me know.
Check it OutWarehouse: http://plugins.require.errtheblog.com/browser/auto_migrations
SVN: svn://errtheblog.com/svn/plugins/auto_migrations
Bugs/Patches: http://err.lighthouseapp.com/projects/466-plugins/tickets
1% Inspiration and 99% PerspirationThe concept for this plugin came when I noticed a migrations branch had been added to DataMapper. Being able to auto-migrate with DM seemed so logical since the fields are defined in the models. It wasn’t until Chris pointed out that schema.rb could be used for the same purpose that I realized auto migrations were possible in Rails as well.
If you’re curious how the auto migration code works for DM, take a look at the rake task I built for vJot. Interestingly enough, due to the way the migrations have been tentatively built for DM, the code is significantly shorter compared to auto_migrations and I know even less about DM than ActiveRecord.
Wait, you mean you aren’t subscribed to Err’s changeset feed? vJot has been powered by DataMapper since the end of July, but I’ll save that for another post.
Breaking NewsI’ve just committed support to handle indexes. Using the original example, I’ll add a simple index:
ActiveRecord::Schema.define(:version => 1) do
create_table :posts do |t|
t.string :title
t.text :body
t.integer :published
end
add_index :posts, :published
create_table :comments do |t|
t.string :name, :url
t.text :body
t.integer :post_id
end
end
Followed by:
$ rake db:auto:migrate
-- add_index("posts", ["published"])
-> 0.0216s
Actually, I’m not sure I really need that index, I’ll comment it out for now:
ActiveRecord::Schema.define(:version => 1) do
create_table :posts do |t|
t.string :title
t.text :body
t.integer :published
end
# add_index :posts, :published
create_table :comments do |t|
t.string :name, :url
t.text :body
t.integer :post_id
end
end
And auto-migrate again:
$ rake db:auto:migrate
-- remove_index("posts", {:name=>"index_posts_on_published"})
-> 0.0187s
Dynamite.
From Err the Blog - Home, 9 months ago,
0 comments
Things are really looking up for Ambition. It’s been almost two weeks since the initial release and I thought I would catch y’all up on the progress we’ve made.
The current version is 0.2.2, so grab that while you can and follow along. Supplies are limited.
$ sudo gem install ambition -y
Databasein’Ambition has gone from generating generic SQL to respecting your database of choice (as long as it’s MySQL or PostgreSQL).
Check it:
# Postgres
User.select { |m| m.name =~ /chris/ }
SELECT * FROM users WHERE users."name" ~ 'chris'
# MySQL
User.select { |m| m.name =~ /chris/ }
SELECT * FROM users WHERE users.`name` REGEXP 'chris'
How cool is that? And because Postgres supports case insensitive regular expressions, so do we:
User.select { |m| m.name =~ /chris/i }
SELECT * FROM users WHERE users."name" ~* 'chris'
Escaping is also DB-specific, whether you like it or not. Oh, and OFFSET works with Postgres now.
Don’t see your database of choice on our (short) list? Feel free to send patches—we happily accept them.
Toyin’If you just want to dip your toe in, we’ve added a test/console inspired by will_paginate’s.
You have to create the database yourself, but once that’s setup this irbish script will create a schema and load it with fixtures for you to play with and enjoy.
$ mysqladmin create ambition_development
$ cd GEMS/ambition-0.2.2
$ ruby test/console
>> Developer.select { |u| u.salary > 80_000 }.size
=> 9
>> Developer.select { |u| u.salary > 80_000 }.entries
=> [#<Developer:0x271f370 ...> ...]
There might be some rough spots as far as the associations go, but overall it should work pretty nicely.
To use a different database, set an ADAPTER environment variable, e.g.
$ ADAPTER=sqlite3 ruby test/console
Speedin’Some people have been asking about performance. You know how it goes. “Oh, that’s cool, but it’s slow.” “It doesn’t scale.” “Who owns the trademark?”
In the interest of appeasing the unappeasable, I spent some time with ruby-prof. Below are the fruits of my labor, comparing the first and latest versions of Ambition.
Each case runs 10,000 times. The benchmark I used is here. I had to fix some bugs in 0.1.0 to get it to work, but that shouldn’t affect anything.
# Ambition 0.1.0
user system total real
simple select 7.030000 0.020000 7.050000 ( 7.052899)
dual select 8.050000 0.010000 8.060000 ( 8.094917)
join select 7.890000 0.030000 7.920000 ( 8.003906)
dual select w/ sort 13.390000 0.040000 13.430000 ( 13.529581)
dual select w/ stuff 13.600000 0.050000 13.650000 ( 14.107565)
it's complicated 16.030000 0.070000 16.100000 ( 16.213238)
# Ambition 0.2.2
user system total real
simple select 0.910000 0.010000 0.920000 ( 0.921048)
dual select 1.380000 0.010000 1.390000 ( 1.398235)
join select 1.950000 0.020000 1.970000 ( 1.981603)
dual select w/ sort 1.960000 0.000000 1.960000 ( 1.964018)
dual select w/ stuff 2.080000 0.010000 2.090000 ( 2.111435)
it's complicated 2.820000 0.000000 2.820000 ( 2.831330)
So, that’s pretty cool. We’re definitely not slowing anything down—Ambition happily leaves that task up to your app. Zing!
Stubbin’This is an experimental feature, but that’s what we’re all about. Observe:
User.ambition_source = fixtures(:chris, :pj, :_why)
User.detect { |u| u.name == 'Chris' }
If an ambition_source is set, Ambition will run all it instead of the database. The above example runs select on the fixtures array rather than building and executing SQL. Could be cool for functional / unit tests.
Gitin’Development is now riding Git rather than Subversion. Follow along:
$ git clone git://errtheblog.com/git/ambition
If you haven’t already tried Git, this could be a golden opportunity for us both. Other projects like Rubinius, god, and CouchObject are already using this up and coming tool for development, so give it a shot.
Us using Git or Subversion should not affect your ability to use Ambition in a Rails app. You can either require the gem or run gem unpack ambition in your vendor/plugins directory to get it working.
Contributin’Thanks to Matthew King, David Chelimsky, Pratik Naik, Loïc, Louis Rose, John Topley, and François Beausoleil for bug reports, feature requests, and their contributions.
If you didn’t catch the updates on the last post, we added empty?, downcase, upcase, any?, all?, and slice thanks to their hard work.
As always, you can add bugs, make requests, and participate in general over at the Lighthouse bug tracker.
We now have a mailing list, too: http://groups.google.com/group/ambition-rb
Prospectin’We’ve moved our sights from Rack to LINQ. That is, we don’t want to only support other ORMs—we want Ambition to be a query language for SQL, LDAP, XPath, the works. The 1.0 release will be backend-agnostic. Maybe then we’ll change the name to Hubris? Time will tell.
From Err the Blog - Home, 10 months ago,
0 comments
This idea has been kicked around a whole lot, I know. In IRC. Dark alleyways. Sweaty dance halls. Courts of law. It’s this: instead of writing SQL, write Ruby. Generate the SQL behind the curtain.
Something like this:
User.detect { |u| u.name == 'Jericho' && u.age == 22 }
Or this:
User.select { |u| [1, 2, 3, 4].include? u.id }
Or even this:
User.select { |u| u.name =~ 'rick' }.sort_by(&:age)
It’d be cool, right?
Don’t get me wrong, we’re already able to express our queries in Ruby. There’s ez-where and Squirrel, and probably a ton more. But those are DSLs for querying with Ruby, not as Ruby. I want to use the straight-up Enumerable I know and love, nothin’ else. Call me old fashioned.
An Ambitious UndertakingErlang’s Mnesia database is something like what I want: you write your queries in plain Erlang and they are translated into Mnesia-queries by walking the parse tree. Nice trick, but listen up: Ruby has a parse tree, too, and we can get at it pretty easily thanks to ParseTree.
So, we do. Introducing Ambition.
$ sudo gem install ambition -y
Play with it in your Rails console using the ActiveRecord logging hack:
$ script/console >> ActiveRecord::Base.logger = Logger.new(STDOUT) => #<Logger:0x2814134 ...> >> require 'ambition' => []
Some examples using an ActiveRecord model, followed by the SQL executed in the background:
User.first
"SELECT * FROM users LIMIT 1"
User.select { |m| m.name != 'macgyver' }
"SELECT * FROM users WHERE users.`name` <> 'macgyver'"
User.select { |u| u.email =~ /chris/ }.first
"SELECT * FROM users WHERE (users.`email` REGEXP 'chris') LIMIT 1"
User.select { |u| u.karma > 20 }.sort_by(&:karma).first(5)
"SELECT * FROM users WHERE (users.`karma` > 20)
ORDER BY users.karma LIMIT 5"
User.select { |u| u.email =~ 'ch%' }.size
"SELECT count(*) AS count_all FROM users
WHERE (users.`email` LIKE 'ch%')"
User.sort_by { |u| [ u.email, -u.created_at ] }
"SELECT * FROM users ORDER BY users.email, users.created_at DESC"
User.detect { |u| u.email =~ 'chris%' && u.profile.blog == 'Err' }
"SELECT users.`id` AS t0_r0 ... FROM users
LEFT OUTER JOIN profiles ON profiles.user_id = users.id
WHERE ((users.`email` LIKE 'chris%' AND profiles.blog = 'Err'))
LIMIT 1"
And so forth. A big list of examples can be found in the README.
Kicking Around DataA good thing to keep in mind is that queries aren’t actually run until the data they represent is requested. Usually this is done with what I call a kicker method. You can call them that, too.
Kicker methods are guys like detect, each, each_with_index, map, and first (with no argument). Methods like select, sort_by, and first (with an argument) are not kicker methods and return a Query object without running any SQL.
As such, you can garner some information from a Query object:
>> user = User.select { |u| u.name == 'Dio' }
=> (Query object: call #to_sql or #to_hash to inspect...)
>> user.to_sql
=> "SELECT * FROM users WHERE users.`name` = 'Dio'"
>> user.to_hash
=> {:conditions=>"users.`name` = 'Dio'"}
>> user.first # => SQL is run
=> #<User:0x36896e4 ...>
Note the to_hash—Ambition doesn’t actually run any SQL, it just hands this hash to ActiveRecord::Base#find.
Anyway, kickers have useful implications for Rails apps. Take this controller:
class BandsController < ApplicationController
def index
@bands = Band.sort_by(&:name)
end
end
Since no kicker method is called, @bands is just a Query object—no SQL run. The SQL is only run once we call each in our view:
<h1>Rocktastic Bands<h1> <ul> <% @bands.each do |band| %> <li><%= band %></li> <% end %> </ul>
Now, let’s say you grow a bit and want to a) fragment cache and b) reduce queries. Standard stuff.
Two birds, one stone:
<h1>Rocktastic Bands<h1>
<% cache do %>
<ul>
<% @bands.each do |band| %>
<li><%= band %></li>
<% end %>
</ul>
<% end %>
If Rails finds a cached fragment, the SQL is never run. Slick.
The CatchThis is pretty new, so watch the sharp edges. While we aren’t good at executing arbitrary Ruby inside the block, we can handle variables.
Practically speaking, instead of writing:
User.select { |u| u.created_at = 2.days.ago }.first
Write:
date = 2.days.ago
User.select { |u| u.created_at = date }.first
Instance variables and simple method calls work fine, too. Expect full Ruby support in a future release. (Expect it sooner if someone sends in a patch!)
Big DreamsIdeally, this thing could turn into something like Rack for databases. Query DataMapper, Sequel, or ActiveRecord using Ruby’s plain jane Enumerable API. Hey, maybe we can thrown an OODB or two into the mix?
The usual suspects:
Report feature requests, shortcomings, & bugs at Lighthouse. SVN’s at svn://errtheblog.com/svn/projects/ambition Code at http://projects.require.errtheblog.com/browser/ambition RDoc at RockI’ve been running this on Cheat for a few days and it’s going real swell. Check the source for awesomeness like @sheets = Sheet.sort_by(&:title).
Watch this space as we grow up our little ambition.
Update: Oh yeah, KirinDave came up with the name Ambition. Thanks.
Update 2: Okay, added some stuff tonight: You can now do cross-table sort_bys:
User.sort_by { |u| [ u.profile.name, u.ideas.karma ] }
That didn’t work before. Doh. I also added query support for any?, all?, and empty?—they do a COUNT behind the scenes, so feel free to give them crazy conditions. All three are kickers.
Also added were the entries and to_a kickers. User.to_a is the same same as User.find(:all). Works as an all-purpose kicker, too—User.select { |u| u.name == ‘kicker’ }.to_a and whatnot.
Finally, slice is now an alias for []. You guys can thank PJ for that one.
The new gem is out and it’s hot. I added some empty specs for destructive and constructive methods—dangerous thinking, I know. We’ll see.
From Err the Blog - Home, 11 months ago,
0 comments
Have you ever caught yourself daydreaming about the good ol’ days. You know, like 1995. A time where 28.8k modems and CGI scripts were all anyone needed in life.
Now we’re flooded with garbage like DSL and Ruby on Rails, ugh. Those lost in this new age, fear not, for Err the Blog has rewound the clock for you. May I introduce to you our time machine, ErrCount. Cleverly named, I know. It’s that self-deprecating wit that makes us so lovable, right?
These folks and I are revolutionizing the internet by bringing back CGI-style hit counters. Get on board while you still can.
Ok, fine, it’s a Rails app. A single-model app that consists of three views and a Mongrel handler that, uh, handles the counting.
How ErrCount Handles ItI used a handler specifically because of speed. Rails would have done the job just fine, but when our thousands of loyal readers start using the site to track millions of hits, the overhead of the full Rails’ stack would have slowed request times dramatically.
Its structure looks like this:
class Counter < Mongrel::HttpHandler
def process(request, response)
response.start(200) do |head, out|
...
end
end
end
uri "/ctr", :handler => Counter.new, :in_front => true
So, what do we have? A Counter class that inherits Mongrel::HttpHandler which gives us a bunch of handy tools, including the process method that handles the requests coming in, and a uri method that defines which urls are gonna make the magic happen. The :in_front option is important as well, so the Rails dispatcher won’t slow down handle the request.
Lets look at what’s happening inside of ErrCount’s response block, because I’m pretty sure you came here to see real code.
First thing’s first, I grab the site id from the url and select its corresponding row from the database using ActiveRecord. I could have used the mysql gem directly, but since ActiveRecord is loaded anyways, I might as well use it.
id = request.params["PATH_INFO"][/\d+/].to_i
row = ActiveRecord::Base.connection.select_one
"select url, hits from sites where id = #{id}"
There may be a better way to get the id than the way I’ve done it, but I wanted to support urls that looked like /ctr/224.js instead of /ctr?id=224.
Next up is a small check to make sure the counter is installed on the correct site. If it is, we bump the hit count by one with an atomic update. Theoretically, I don’t need a mutex around these AR calls, but I’m sure somebody is going to tell me I’m wrong. (Please do if I am)
if Regexp.new(row["url"]) =~ request.params["HTTP_REFERER"]
ActiveRecord::Base.connection.update
"update sites set hits = hits + 1 where id = #{id}"
end
The meat and potatoes is building the counter image. Using a little CSS trickery, I’m able to reuse the same counter image

by using divs with different background positions for each number to create something that looks like:

counter = "var counter='<style>#{Style}</style>"
row["hits"].to_s.split(//).each do |num|
counter += %{<div class="ctr" style="background-position:}
counter += %{#{150 - (15 * num.to_i)}px 0;"></div>}
end
counter += ";document.write(counter);"
All that’s left is serving the javascript with the proper header.
head["Content-Type"] = "text/javascript" out.write counter
Something that’s easy to forget should you chose to write your own is to start mongrel with a -S so it knows about the handler you just wrote:
mongrel_rails start -S counter.rb
That’s all there is to it, feel free to check out the entire app here:
Warehouse: http://projects.require.errtheblog.com/browser/counter
SVN: svn co svn://errtheblog.com/svn/projects/counter
Site: http://errcount.com
How Sake Handles ItBut, you’re not limited to just using Mongrel handlers with Rails apps. You can use these things on your own and that’s just what Chris did with Sake. Check it:
require 'sake' unless defined? Sake
require 'mongrel'
class Sake
module Server
extend self
def start(args)
if index = args.index('-p')
port = args[index+1].to_i
else
port = 4567
end
daemoned = args.include? '-d'
config = Mongrel::Configurator.new :host => "127.0.0.1" do
daemonize(:cwd => '.', :log_file => 'sake.log') if daemoned
listener(:port => port) { uri "/", :handler => Handler.new }
end
trap("INT") { config.stop }
config.run
unless daemoned
puts "# Serving warm sake tasks on port #{port}..."
end
config.join
end
class Handler < Mongrel::HttpHandler
def process(request, response)
uri = request.params['PATH_INFO'].sub(/^\//, '')
status = uri.empty? ? 200 : 404
body = status == 200 ? Store.to_ruby : 'Not Found'
response.start(status) do |headers, output|
headers['Content-Type'] = 'text/plain'
output.write body
end
end
end
end
end
Sake::Server.start(ARGV) if $0 == __FILE__
The only major difference is he’s using Mongrel::Configurator directly so he could daemonize it and define when the server should start and stop. Other than that, you use Mongrel handlers the exact same way as you would when using them with Rails.
Check out the code, along with the rest of sake here:
Warehouse: http://projects.require.errtheblog.com/browser/sake
SVN: svn co svn://errtheblog.com/svn/projects/sake
Post: http://errtheblog.com/post/6069
Happy Handling.
From Err the Blog - Home, 11 months ago,
0 comments
The main problem with fixtures, for me, has always been how unfun they are. They literally suck the fun out of anything they’re around. You throw them in your test/ directory, then suddenly testing is, like, work. But it’s not work, dammit. This is Ruby, dammit.
So, keep them fun. Write your fixtures in Ruby.
Step 1: FixtureScenarios
Tom Preston-Werner has this FixtureScenarios plugin. The project home page explains:
This plugin allows you to create ‘scenarios’ which are collections of fixtures and ruby files that represent a context against which you can run tests.
Pretty cool. Instead of just test/fixtures, you can have (for instance), test/fixtures/users and test/fixtures/beta. Then, in your test class, you call scenario :users in place of the normal fixtures :users, :posts, :images call. This will pull all the YAML files from test/fixtures/users into your test database. When you need a completely separate set of fixtures for another feature, it’s as easy as scenario :beta.
Scenarios can also be nested. You can create a test/fixtures/users/banned directory filled with YAMLy files and call it with scenario :banned. This’ll pull in both the test/fixtures/users fixtures and all the test/fixtures/users/banned fixtures. It will also, by default, pull in all the test/fixtures fixtures.
The real beauty of this is when you load up your code six months down the road, recently returned from your jaunt up Everest, and need to add a feature. Fast! Just add a new scenario—forget about worrying on your old tests, they won’t be affected.
This plugin is also great when BDDin’ with test/spec or RSpec. You can have separate contexts in one spec file, all using different fixture scenarios as needed. Maybe you want to assert hardcore pagination behavior—you can load up 10,000 records! But only for that one scenario, leaving your other scenarios sane.
Grabbit:
$ script/plugin install \ http://fixture-scenarios.googlecode.com/svn/trunk/fixture_scenarios
Step 2: FixtureScenarioBuilderOkay, like I said, we need to write our fixtures in Ruby.
By default, FixtureScenarios will load any Ruby files it finds in your fixture directories. Cool, but not really sufficient. We need transactional fixtures, we need the database cleared out after test runs, we need a clean database when tests start. We really need all the cool features YAML fixtures offer.
And we can get them, with FixtureScenarioBuilder. This is an add-on to Tom’s FixtureScenarios plugin. AKA you need FixtureScenarios for it to work.
Grabbit:
$ script/plugin install \ svn://errtheblog.com/svn/plugins/fixture_scenarios_builder
Step 3: scenarios.rbAfter you’ve installed the plugin, create a scenarios.rb file and place it in your test/fixtures directory. It is in this file you build your fixture scenarios. In Ruby.
Like this:
scenario :blog do
%w( Tom Chris Kevin ).each_with_index do |user, index|
User.create! :name => user, :banned => index.odd?
end
end
As soon as I run a test, test/fixtures/blog will get created with a users.yml file inside it. It’ll look like this:
chris: name: Chris id: "2" banned: "1" updated_at: 2007-05-09 09:08:04 created_at: 2007-05-09 09:08:04 kevin: name: Kevin id: "3" banned: "0" updated_at: 2007-05-09 09:08:04 created_at: 2007-05-09 09:08:04 tom: name: Tom id: "1" banned: "0" updated_at: 2007-05-09 09:08:04 created_at: 2007-05-09 09:08:04
Notice we’ve got intelligent fixture names. FixtureScenarioBuilder did its best to guess these by trying name, username, then title. If it can’t figure out a name, it falls back to stories_001 or whatever.
As for quick, iterative, agile development: whenever you modify scenarios.rb, your YAML fixtures will get re-built. Any errors in your scenarios.rb file will be clearly surfaced when building.
FixtureScenarioBuilder also supports nesting fixtures:
scenario :blog do
%w( Tom Chris ).each do |user|
User.create! :name => user
end
end
scenario :banned_users => :blog do
User.create! :name => 'Kevin', :banned => true
end
As you may guess, this creates test/fixtures/blog and test/fixtures/blog/banned_users. Rake-like and tasty.
Ruby fixtures are super because you can easily throw around associations, forget about those updated_at & created_at fields, easily refactor fixtures after making sweeping schema changes, and don’t need to worry about join tables.
I’ve been doing fixtures this way for a few months now and really dig. FixtureScenarios and FixtureScenarioBuilder are like a one-two knock out punch of… uh… test data.
Step 4: Real Life And AllFor a more complex example, check out this scenarios.rb file. It uses nesting, multiple scenarios, and all sorts of Ruby goodness (for a code review site, ya heard).
You can get a lot of this stuff by ERBing your YAML files, but really, that sucks. I like having my habtm join tables generated for me, I am comfortable defining associations and whatnot in Ruby. It’s how I think.
These days, many people are hacking their database or mocking to get around the fixture pain. Other fixture solutions are the fixture references plugin, this patch, and QuickFix. I’m sure there are countless others. Got a good solution? Leave a comment, plz.
Step 5: Get Out of HereFor further explanation, check out the README. Browse the code. Find a bug. Etc.
This lil’ plugin is fairly new, so any bugs and all suggestions are appreciated.
Thanks, and goodnight.