Happy New Year
describe "Year 2008" do
it "should be happy" do
Date.new(2008, 1, 1).year.should be_happy
end
end
Best wishes to all my readers for a happy 2008!
describe "Year 2008" do
it "should be happy" do
Date.new(2008, 1, 1).year.should be_happy
end
end
Best wishes to all my readers for a happy 2008!
Since I have been spec-driving Rails code, and since I have very limited table space at home right now, I have been experimenting with something I’d never liked before: an electronic test and refactoring list. So far, it’s not too bad.
Like many people, I like to avoid keeping lists in my head, since I tend to forget things. Normally, I use an index card and write tests from the top and refactorings from the bottom, crossing each out as I perform them. It works well. So well, in fact, that I generally question anyone who attempts to use an electronic system, rather than a paper system. The idea, of course, is that we tend to fixate on electronic systems, whereas we use paper systems simply and quickly. Using the computer for anything that doesn’t benefit from automation seems counterproductive to me. Still, since we’re low on table space, I’ve had to try something else, and RSpec gives me the opportunity to maintain a test list without having to implement multiple failing tests (or specs) at once.
With RSpec, one can describe a test with prose without implementing it, simply by invoking it without a block.
describe "The thing I'm spec-driving" do it "should do something important" it "should do something else important" it "should do this third, equally important, thing" end
When it’s time to implement the spec, add the corresponding block. In the meantime, RSpec reports the other specs as “pending”, rather than attempting to execute them. I had never thought it was a good idea, before, but I’ve been trying it recently, and I’m less annoyed by it than I used to be. That might be because I’m more comfortable spec-driving than I was years ago when I started; but it might just be because my personal taste has changed. I can’t say. What I can say is that I haven’t managed to get lost yet, and it takes little extra effort to describe a handful of specs up front. If it starts bothering me, I’ll be sure to describe my experience.
Recently, I integrated Textile into this blog via RedCloth. As I started to write articles in my Adventures in RSpec series, I found I wanted to use footnotes. Since Textile supports them, I figured I was in luck; however, I soon found a hyperlink collision when two fragments of Textile markup appear on the same page, each with the same-numbered footnote. RedCloth simply generates a hyperlink to target fn1 for footnote #1, so if there are two footnote #1s on the same page, they both link to target fn1, making it the equivalent of a race condition.
I looked for guidance to the Textpattern folks. A certain Mary told me that Textile has fixed this problem, but likely RedCloth has not. After some investigation, I agreed that this was the case. I looked through the Textile source, to the extent I can understand PHP, and saw their fix was simple: keep a table of footnote numbers to unique IDs, then use the unique ID in place of the number in the hyperlink and target. I figured that I could do that.
It took a couple of hours, and I’ll spare you the details. Here is what I wrote:
require 'digest/sha1'
class UniqueFootnoteIdGeneratingRedCloth < RedCloth
def initialize(markup)
@footnote_ids_by_number = {}
super(markup)
end
def next_footnote_id
# SHA1 isn't significant; I just figured it'd make a good unique ID
Digest::SHA1.hexdigest(rand(2**64).to_s)
end
def to_html(*rules)
self.scan( /\b\[([0-9]+?)\](\s)?/ ) do |match|
@footnote_ids_by_number[$1] = next_footnote_id
end
super
end
private
def footnote_ref(text)
text.gsub!( /\b\[([0-9]+?)\](\s)?/ ) do |match|
"<sup><a href=\"#fn#{@footnote_ids_by_number[$1]}\">#{$1}</a></sup>#{$2}"
end
end
def textile_fn_( tag, num, atts, cite, content )
atts << " id=\"fn#{ @footnote_ids_by_number[num] }\""
content = "<sup>#{ num }</sup> #{ content }"
atts = shelve( atts ) if atts
"\t<p#{ atts }>#{ content }</p>"
end
end
I ended up copy/pasting more code than I wanted to, so I’ll have to submit something to why for his perusal. Perhaps this will make it into a future version of RedCloth.
Today I’m waiting at Così Cafe for our train to Washington DC for Agile 2007, so it’s time to add the next part of the “preview” feature. I have an internet connection, so I can afford to be a little more aggressive with the story I tackle next, because Google knows all. I think I’ll try to build the real-time preview I imagined earlier this morning. Now I don’t think I can spec-drive the real-time aspect of the preview, but I can certainly spec-drive showing the preview in a nearby window while I’m typing in the posting content text area. To get a sense for how that would look, I do some quick UI design.
In order to make room, I shrink the “content” text area and put the live preview, as it’s called, below it. This looks reasonable to me. In the process, I make one design decision: the <div> that contains the live preview text has the ID content-live-preview, which I’ll need to spec-drive the RJS I’ll have to write.
Speaking of spec-driving RJS, I have no idea how to do that, so I need to read a little about that before I get started.
Unfortunately, after reading Rails Recipes, I’m convinced that I need to implement this first before I can learn how to spec-drive it for next time. I find this happens a lot with Rails: it’s almost too simple. I paste an implementation into my view and inspect it manually. When I do that, it becomes clear that I need an action to handle the live preview, so although I didn’t drive the controller behavior with an automated test or spec, I’m certain I need something. Now I think I can spec-drive the controller behavior. Since this is familiar territory, this should go smoothly. Let’s see.
According to Rails Recipes, all I need to do in my action is render the default template without a layout, which means I will need to move the live preview markup to a new live_preview.rhtml template. I’ll do that first, to make sure it will be wired together properly.
After a few false starts, I have these specs:
describe "Live Preview for weblog postings" do
it "should have a section to display the live preview" do
assigns[:posting] = Posting.new(:content => "Some @textile@ content.")
render "/postings/live_preview"
response.should have_tag("div#content-live-preview")
end
it "should be able to handle nil content" do
assigns[:posting] = Posting.new(:content => nil)
render "/postings/live_preview"
response.should have_tag("div#content-live-preview")
end
end
This is the live_preview.rhtml template:
<div id="content-live-preview" class="entry">
<%= textilize(@posting.content) if @posting.content %>
</div>
That decidedly doesn’t work. When I consult Rails Recipes, I realize I should display the request parameter posting[content] and not the Ruby object @posting.content. For this, I need to learn how to stub the request parameters in the view. Evidently this is as simple as assigning to a params Hash. I fix the specs to match this new information.
describe "Live Preview for weblog postings" do
it "should have a section to display the live preview" do
params[:posting] = { :content => "Some @textile@ content." }
render "/postings/live_preview"
response.should have_tag("div#content-live-preview")
end
it "should be able to handle nil content" do
params[:posting] = { :content => nil }
render "/postings/live_preview"
response.should have_tag("div#content-live-preview")
end
end
I’m on a green bar, but the UI doesn’t work. Firebug tells me that my controller live_preview method works, but the corresponding <div> is not updating. I don’t know how to spec-drive this, so I need to hack for a while. It’s 14.44; let’s see how long I need to hack.
Fortunately, only about five minutes. I had one <div> too many.
After fixing that, and making a few small changes, it looks like I’m done. I would have preferred to spec-drive the observe_field code, but at least I have less untested code than I used to. Now I can remove the preview button and the code that went with it. It served its purpose; now it can go to its rest.
I decided, at last, to investigate RSpec in the course of my work in Rails. I should be clear about something: I’m not writing Rails code for profit, only for fun and the experience, so I’m learning slowly and not really pushing the envelope the way I would on the job. Read this series with that in mind.
Here is the story I want to implement next: “Preview postings”. That story is for this very weblog. When I add an entry, I want to be able to preview it without publishing it. Of course, now that I write that, I realize that I might also want to preview edits to existing entries, but that’s another story. I suppose this story is “Preview new postings”. It’s good to know that I can still negotiate scope with myself!
Not being a big RSpec user yet, rather than get stuck writing a story test, I thought I’d do my bit of design, dive in, then RSpec-drive my model1.
First, I had to install RSpec and RSpec on Rails. That wasn’t too hard. I followed these instructions and issued these commands in the root of my Rails project:
ruby script/plugin install -x svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec ruby script/plugin install -x svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec_on_rails ruby script/generate rspec
After a few seconds, I could run my specs using the new Rake tasks in my project, like spec. When I run my specs, of course I have none, but I do see weird ANSI codes in my output. I’ve asked a question about that, and when I get an answer, I’ll let you know what I found out.
It’s time to write a spec. My design has a class Posting, so I figured I would add the notion of “publishing” a posting, so that I could save a posting without publishing it. This would allow me to preview it. For this, I decided to add a published_at timestamp. For that, I wrote this spec:
describe Posting do
it "should not yet be published" do
Posting.new.should_not be_published
end
end
What I find neat and disturbing at the same time is that RSpec magically looks for a method published? on Posting because I’ve used the “should… be” syntax. I think it’s neat, but what disturbs me is that I have to know where the underscores go. I expect to write that as Posting.new.should not_be_published or Posting.new.should_not_be published. Not exactly “least surprise” for me2.
I satisfy this spec by adding a published? method than answers false and move to the next spec.
describe WeblogController do
it "should be published when it has a published timestamp" do
new_posting = Posting.new(:published_at => DateTime.now)
new_posting.should be_published
end
end
To satisfy this spec, I add the column published_at to the postings table. When I migrate, though, I should mark all the current postings as published, so I make them published as of the time they were created. It’s not perfectly accurate, but it will do. I haven’t been tracking the last updated at timestamp. Here is the migration I wrote:
class EnablePreviewForPostings < ActiveRecord::Migration
def self.up
add_column :postings, :published_at, :datetime, :default => nil
Posting.find(:all).each { | each |
each.update_attribute(:published_at, each.created_at)
}
end
def self.down
remove_column :postings, :published_at
end
end
When I migrate the database, I see that all is well. Now I can implement published? like so:
class Posting < ActiveRecord::Base
def published?
!self.published_at.nil?
end
end
I’m green, so I can continue3. The essence of preview is that I can save the entry, display it, but not include it in the published entries. To that end, I need to retrospec the method that grabs all the postings so it’ll exclude unpublished ones. This is the first time I’m spec-ing a controller, so I’ll learn something. I create my new spec at $RAILS_ROOT/spec/controllers/posting_controller_spec.rb and write this:
describe WeblogController do
fixtures :postings
it "should only select published entries when browsing" do
get 'browse'
postings = assigns[:postings]
postings.size.should == 3 # 4 articles, 3 published
end
end
Unfortunately, this doesn’t find my existing postings fixture. I get this rude answer:
No such file or directory - /Users/jbrains/Workspaces/dauphin/jbrains.info/spec/fixtures/postings
(I should note that that’s not the exact output, but the exact output tripped some security filter at TextDrive, so I couldn’t paste it in. That only cost me a half hour to figure out. Thanks, TextDrive.)
So what does Google have to say about this? Curiously, one article says Lose the fixtures with rSpec, which I like, so I’m inclined to try it. I change my spec to this:
describe WeblogController do
it "should only select published entries when browsing" do
Posting.create!(
:published_at => nil,
:title => "Irrelevant detail", :content => "Irrelevant detail"
)
get 'browse'
postings = assigns[:postings]
Posting.find(:all).size.should 1
postings.size.should 0
end
end
I don’t like the irrelevant details, but when I try to switch to test doubles, it’s a dead end, so I stick with this spec and try to satisfy it. By adding the condition published_at is not null, I get the job done. The problem, though, is that there are two code paths in my browse method, depending on whether the URI is /browse or /browse/12. The latter means “show only articles in category 12”, which supports my entry tags. I wrote this before I knew about custom routes, so I could consider refactoring to a custom route and simplifying browse, but that’s more work than I’m willing to take on at 01.25, so I’ll leave a REFACTOR comment and add a spec for the /browse/12 case.
describe WeblogController do
it "should only select published entries when browsing within a single category" do
category = Category.create!(:name => "Irrelevant detail")
Posting.create!(
:published_at => nil,
:title => "Irrelevant detail", :content => "Irrelevant detail"
).categories << category
get 'browse', :id => category.id
postings = assigns[:postings]
Posting.find(:all).size.should 1
postings.size.should 0
end
end
That spec is violated, so I satisfy it. After some refactoring, browse looks like this:
class WeblogController < ApplicationController
def browse
if params[:id].nil?
@postings_pages, @postings = paginate :postings, :order_by => default_order, :conditions => [published_condition], :per_page => 5
else
# REFACTOR Move this to a custom route
@postings_pages, @postings = paginate_collection(:per_page => 5, :page => params[:page]) {
Posting.find_by_sql([
%Q(
select * from postings where postings.id =
any (select posting_id from tags where category_id = ?)
and (#{published_condition})
order by #{default_order}
), params[:id]
])
}
end
end
private
def published_condition
"published_at is not null"
end
end
I’m pretty happy with that, and I can’t think of another spec that might be violated, so I move on to the “preview” action. I imagine this is as simple as adding a “preview” button to the form where I add a posting which creates the entry without publishing it, then display it a new window. This way I can close the window, go back, then press “save” when I’m ready. I might even rename “save” to “publish” to emphasize the fact that I’d be publishing the entry. I need to do some more retrospec-ing (“retrospecking”?) to prepare for that.
describe PostingsController do
it "should not publish a new entry when I preview it" do
post 'preview'
new_posting = assigns[:posting]
new_posting.should_not be_published
end
end
I can satisfy that easily enough.
def preview @posting = Posting.new(params[:posting]) @posting.save redirect_to :action => "read", :id => @posting end
Rather than pop up a window, I’ll just remember to use the “Back” button. My customer side wants a real-time preview, and it’s now 01.54, so since I’m not prepared to do that now, my programmer side will ship the simplest thing that could work, get feedback, then add the cool Web 2.0 crap. The next step is to add my preview button, but the scaffold code doesn’t directly handle two submits for the same form, so I need to do some research. The best solution I find is to add a name attribute to my buttons and “case” on the name in my controller method. Not pretty, but adequate. I refactored my new entry form to prepare for that, and when I did, I noticed I couldn’t see any new entries. Of course, that’s because create doesn’t publish. I was hoping to do that later, but I guess I need to do it now. Since I’m on a green bar, I can retrospec create.
describe PostingsController do
it "should publish a new entry when I publish it" do
post 'publish'
new_posting = assigns[:posting]
new_posting.should be_published
end
end
Evidently my subconscious insists I rename create to publish, so I do that. Now my spec is violated, so I satisfy it. In the process, I introduce the new method publish on Posting, which I’ve spec-driven.
class Posting < ActiveRecord::Base
def publish
self.published_at = DateTime.now
save
end
end
This makes the change to the controller easier.
def publish
@posting = Posting.new(params[:posting])
if @posting.publish
update_tags_for(@posting)
flash[:notice] = 'Posting was successfully created.'
redirect_to :controller => 'weblog'
else
render :action => 'new'
end
end
I only had to change @posting.save to @posting.publish. That looks like that works, so I try it manually through the UI4. Now I can publish a new entry, so I need to be able to preview one. It’s time for another spec.
describe PostingsController do
it "should preview when I press the 'Preview' button" do
post 'submit_new', :submit => "preview"
assigns[:posting].should_not be_published
end
end
Satisfying this spec is pretty straightforward, so I retrospec for the ‘Publish’ case. After a little refactoring, I have this:
class PostingsController < ApplicationController
def submit_new
self.send(params[:submit].downcase)
end
end
Clever, no? Perhaps too clever, but time will tell. It works for me. I’d worry about a security hole if I weren’t the only user who could submit this form.
A quick manual test reveals a problem: “Couldn’t find Posting without an ID.” It looks like I’m missing a preview-related spec, so I add one… and when I do, all hell breaks loose. Here’s the short version of the story:
authorize filter wasn’t right, so I fixed it.Great how that happens, no? So I search for something on the topic of how to stub out filters, and I read this, which suggested I write
controller.stub!(:login_required).and_return(false)
to indicate that there’s no need to login. I tried that and it didn’t work, so I added this instead:
describe PostingsController do
before(:each) do
controller.stub!(:authorize)
end
end
Now everyone is authorized in my specs. With that distraction out of the way, I can get back to the point.
describe PostingsController do
it "shouldn't preview an incomplete entry" do
post 'preview', :title => "", :content => ""
assigns[:posting].should be_new_record
assert_redirected_to :action => "new"
end
end
This is violated because I redirect to the wrong place. It’s an easy fix, but with a little manual testing, I see that while “Publish” gives me nice error messages when it fails, “Preview” doesn’t. Upon further inspection, it’s because preview redirects to new on failure, rather than merely rendering it, as publish does. I should write a spec for that. This time, I add some expectations to the last spec I wrote.
describe PostingsController do
it "shouldn't preview an incomplete entry" do
post 'preview', :title => "", :content => ""
assigns[:posting].should be_new_record
response.should_not be_redirect
response.should render_template("new")
end
end
I notice that I had typed assert_redirected_to up there. Old habits die hard, especially when the fingers can work without the conscious mind. Frightening. This spec is violated because I redirect, so I fix that. Now I see that publish and preview are structurally similar.
def publish
@posting = Posting.new(params[:posting])
if @posting.publish
update_tags_for(@posting)
flash[:notice] = 'Posting was successfully created.'
redirect_to :controller => 'weblog'
else
render :action => 'new'
end
end
def preview
@posting = Posting.new(params[:posting])
if @posting.save
redirect_to :action => "read", :id => @posting.id
else
render :action => "new"
end
end
The differences? The method they invoke on @posting and the true branch of the if statement. It takes a little to refactor this, but I arrive at the following.
class PostingsController < ApplicationController
def publish
save_posting :publish, :on_success => lambda { | posting |
update_tags_for(posting)
flash[:notice] = 'Posting was successfully created.'
redirect_to :controller => 'weblog'
}
end
def preview
save_posting :preview, :on_success => lambda { | posting |
redirect_to :action => "read", :id => posting.id
}
end
private
def save_posting(save_action_symbol, events)
@posting = Posting.new(params[:posting])
if @posting.send(save_action_symbol)
events[:on_success].call(@posting)
else
render :action => 'new'
end
end
end
Now, I think, the feature works. Just to be sure, I try a few manual tests. I would prefer to rely on specs, but it’s 03.21 and I don’t plan on much more retrospec-ing tonight. One thing I notice is the “preview” view doesn’t say anything about being a preview, so I should fix that.
describe PostingsController do
it "should tell me a preview is a preview" do
post 'preview', :posting => {
:title => "Irrelevant detail",
:content => "Irrelevant detail"
}
response.should redirect_to(
:action => "preview",
:id => assigns[:posting].id
)
end
end
Making that pass is easy: I redirect to preview, rather than read. A little manual inspection reveals that nothing about the “preview” template makes it look like a preview, so I add a little color to make that more obvious. I’ll go with this for now:
<% @title = "Preview '#{@posting.title}'" %>
<div style="border: 1px solid; padding: 5px; background: pink">
<%= render :partial => "posting" %>
<p style="text-align: right">preview</p>
</div>
It looks reasonably good, and although I have to use the browser’s Back button to edit the draft, and although the “edit” button in the entry doesn’t do what I expect, I can live with it for now.
So, my first RSpec experience has been a good one. In spite of a couple of minor problems, I felt I proceeded steadily, if a little slowly. I imagine I could get used to this quite quickly. RSpec on Rails is a terrific plug-in.
Still, I’d prefer not to have to create real entries in order to write specs for my controllers. I’m sure there’s a better way, but learning what that is will have to wait for another day. It’s 03.43, I have to pack and get some sleep before traveling from Philadelphia to DC for Agile 2007. Before I go, though, I should deploy.
In the process of preparing to commit my changes to my code repository, I updated the RSpec plug-ins, and can no longer run my specs. It looks like a problem in one of the plug-ins. Needless to say I’m not impressed with that. Fortunately, it’s not a big problem: I simply needed to do script/generate rspec again, and all was well. As a result, I’d better freeze my plug-ins before deploying. How do I do that?
So while that took longer than it usually does, I was able to preview this entry with the new feature, so I’m satisfied with it. The next step might be to add real-time preview or the primitive preview feature when editing an entry. Either way, I learned a lot, and I think I’ll be more comfortable spec-ing my next new feature.
1 What’s the verb for doing BDD? In TDD, we say “test-drive”. What do I say when doing BDD with RSpec? Write me and let me know.
2 I tried Posting.new.should_not_be published, but it didn’t work. Shame.
3 You can change some of the language of TDD, but you’ll take my green bar from my cold… well, you get the idea.
4 Yes; I wrote untested, unspec-ed code. I’m not proud of it. Why do you think I’m writing this article?