TDD: I am finally getting it

My friend Mark and I have talked a lot in the past about doing more at unit testing. That's something I've really been focusing on lately and one of the things that has come out of it is that code written for unit tests is subtly different than code not. This became crystal clear to me just a moment ago as I was writing this code:

http://pastie.org/322821

The first way I wrote it was a somewhat untestable way of doing things: in order to test AllowPost my unit test had to know all about our configuration stuff and it just couldn't easily be tested without a bunch of dependencies. Of course, my unit test framework doesn't know about those dependencies without making it jump through a whole bunch of hoops. By re-writing it the second way, the method AllowPost was an atomic unit that could be tested without knowing about the other stuff.

Of course, I want some tests that cover "the other stuff", but that's at a higher level. First, I have to get the lowest levels of tests working before the higher level tests will, otherwise it's all spaghetti.

This may all be old news to you, but for me it really drove home the phrase "test-driven development"...in other words, the tests are actually shaping the code. Suddenly, it makes so much sense why, in order for TDD to be effective, you have to write the tests first. Otherwise, you may end up with code that isn't testable in terms of a unit test framework.

attachment_fu (with rmagick) on windows not working

man, i had a heck of a time getting attachment_fu (using rmagick) on windows working tonight. let me just start with this: if you're having problems, just reboot. it would have saved me an hour.

i followed all the instructions and fixed all the hacks, yet for some reason thumbnails were not generating and the height and width was not being saved. there are lots and lots of articles out there on these topics. most of them discuss how the size isn't being saved, but in my case the size was being saved.

how'd fix it: i rebooted the machine and it worked.

How To Upgrade to Rails 2.0 from 1.2.5

I had to upgrade my Rails app last night to 2.0.2 from 1.2.5. This is a brief overview of what it took to make it happen. I'll write some other posts soon with more details. A little background: I'm using InstantRails and the Aptana RadRails IDE on Windows XP SP2.

Here's what I did:


  • Installed Instant Rails 2.0.

  • Checked out a working copy of my code from SVN

  • Updated environments.rb: RAILS_GEM_VERSION = '2.0.2'

  • Fired it up: didn't work.

  • I'm using acts_as_tree and acts_as_list, so I needed to install those plugins.

  • Fired it up: didn't work.

  • I'm also using Comatose, and there was an error with the "template_root". Bottom line: after reading some message boards, etc. I removed and reinstalled the Comatose plugin and that fixed it.

  • Fired it up: didn't work.

  • Was getting this error:

    Status: 500 Internal Server Error
    A secret is required to generate an integrity hash for cookie session data. Us
    e config.action_controller.session = { :session_key => "_myapp_session", :secret
    => "some secret phrase of at least 30 characters" } in config/environment.rb


    Well, that message says it all, so I changed that.

  • Fired it up: was working. Now, navigated my app...errors.

  • Had to change ':dependent => true' to ':dependent => :destroy'

  • Removed the line(s) config/environments/development.rb:12: config.breakpoint_server = true
    from configuration since the setting has no effect anymore.
    Instead, start `script/server` with the "-u" or "--debugger" option (or "-h"
    to see all the options). This wasn't causing an error, but while trying to figure out the dependent => :destroy thing, I read it on someone's blog, so I removed it.

  • Also, had to install the classic_pagination plugin. (Really, every blog out there says I should upgrade to will_paginate, but my blog is about progress vs. perfection. Old school pagination works for me for now, so I'll stick with it.)

  • Fired it up...everything running, but still some functionality isn't working. (Gee...I wish I'd writen *all* the unit tests. Okay, let's debug.)

  • Opened RadRails, imported existing directory into a project.

  • Got this error: rad rails "Cannot find gem for Rails ~>2.0.2.0:"...had to switch workspace because I installed the new Instant Rails and RadRails was still pointing at the old one. (Not an issue if you overwrite, but I'm running my concurrently.)

  • Hmmm...the server runs through Aptana RadRails, but debugger is slow as heck...turns out that I updated ruby-debug-ide when I updated gems and that un-checked the checkbox for using the fast debugger in the IDE. Also, had to install ruby-debug-base. (I don't think this was required before, but once I installed it the debugger worked.)


Whew, well, I haven't found my bug yet, but that's how far I've progressed. Hopefully this will help the next guy out.

Rails vs. ASP.NET (Really, Ruby vs C#)

Where have I been...why the lack of posts: a) holidays, b) knee injury playing basketball (that required surgery to repair) has slowed me down, c) recently, I've started to work on a project where ASP.NET using C# was the requirement.

So, I haven't been working in Rails for a few weeks and it's been a few years since I've used ASP.NET or C#, so I've had to jump back into it a little to refresh myself.

Now, look, I'm not trying to start a religious war here: ASP.NET and Rails each have their own benefits and drawbacks. However, I would like to say this: for simple web sites Rails is so much easier.

Here's a simple example: I've got a URL stored in the database, such as http://www.mysite.com/foo/fee/foh.htm and I need to get the foh.htm (which isn't stored).

ASP.NET with C#

    public string FileName
{
get
{
char[] delim = { '/' };
string[] a = this.Url.Split(delim);
return a[a.Length - 1];
}
}

Rails with Ruby
  def filename
self.url.split('/').last
end

I'm sure I'm not the first person to make this comparison. I just felt compelled to speak out about the many moments during the day where it takes me several minutes longer to do something with ASP.NET and C# than Ruby and Rails.

How to Make Subversion via HTTP work with RailsMachine (and Capistrano)

Last night I setup my site on RailsMachine. Overall, it was a very pleasant experience. One bugaboo that I ran into:

I have my Subversion repository setup using http, not svn+ssh. The repository is hosted at Joyent and I don't recall why I set it up this way, but there must have been a reason.

Anyhoo...so, I'm plugging away with RailsMachine and using Capistrano for the first time and it's all pretty sweet, except for I can't seem to access my svn repository. Cap would reach the point in the script where it made the HTTP request and I would see the HTTP authentication, but I had no way to type in my user/pass combo, and it would fail.

Long story short: RailsMachine requires that you run Cap using a user called "deploy", and those are the credentials that Cap passes via HTTP Authentication to the svn repository. So, I was able to get it to work by simply creating a user for my svn repository called "deploy" with the same password as "deploy" on my RailsMachine server. Everything works fine now!

more on attachment_fu on windows

In my previous post about attachment_fu (with rmagick) on windows, I did something sort of silly in the #create action of my controller: Windows required a little time to catch up because the file system loaded the file slower that attachemnt_fu needed it. To solve that problem, I added "sleep 5" immediately before MyObject#save.

The problem with that hack is every time I need to #save I have to remember to insert the "sleep 5" hack. That's a mistake waiting to happen. So, I moved the sleep to the model, like so:


class MyObject < ActiveRecord::Base
def before_save
sleep 5 ## <<-- pauses so Windows can catch up.
end
end

It's much more logical that this code is part of the model because the delay is fundamentally a part saving the object, not a part of responding to an action.

This also address the notion of keeping your controllers skinny and your models fat. Jamis Buck blogs very eloquently about this notion here.

attachment_fu (with rmagick) on windows

Today, I've been working to get attachment_fu working with rmagick on Windows. At first, it wasn't uploading the file properly and after much Googling implemented these changes to get it working.

1. I added a pause to my #create in order to allow Windows time to catch up. In short, Ruby is moving faster than the Windows file system, and it was trying to create a record for a file that didn't exist yet. Here's what I did:

  def create
@upload = Upload.new(params[:upload])
sleep 5 ## <<-- pauses so Windows can catch up. Probably would be
## smart to make this something like sleep 5 if OS == Windows
## obviously, that's not the right syntax.
if @upload.save
flash[:notice] = 'Upload was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end

I am fully aware that this is a hack, but it gets the job done.

2. I continued to get the error message "Size is not included in the list". Long story short, this is the error message attachment_fu returns when the file size exceeds the limit set in the model. In addition, it provides this message if no file is provided at all which is a duplicate error because validates_as_attachment is already doing a validates_presence_of against the size.

So, in order to create some better error messaging, and following the notions of how to hack a plugin from this blog post, I created a new plugin called attachment_fu_hacks and in init.rb added this code:
klass = Numeric
klass.class_eval do
def to_human
units = %w{B KB MB GB TB}
e = (Math.log(self)/Math.log(1024)).floor
s = "%.1f" % (to_f / 1024**e)
s.sub(/\.?0*$/, ' ' + units[e])
end
end

klass = Technoweenie::AttachmentFu::InstanceMethods
klass.module_eval do

# overrides attachment_fu.rb:342 b/c to provide better error messages
def attachment_attributes_valid?

attr_name = :size
enum = attachment_options[attr_name]
val = send(attr_name)
if !enum.nil? && !val.nil? && !enum.include?(val)
err_msg = enum.end.to_human
errors.add attr_name, "of file is too big (must be less than #{err_msg})"
end

attr_name = :content_type
enum = attachment_options[attr_name]
val = send(attr_name)
if !enum.nil? && !val.nil? && !enum.include?(val)
err_msg = enum.join(', ').gsub(/image\//, '.') unless enum.nil?
errors.add attr_name, "is not supported (must be #{err_msg})"
end
end
end

klass = Technoweenie::AttachmentFu::ClassMethods
klass.module_eval do
# overrides attachment_fu.rb:106 b/c i don't think it's necessary
# to evaluate size or content_type if there is no file
def validates_as_attachment
validates_presence_of :filename
validate :attachment_attributes_valid?
end
end

I recognize that there might be a more idiomatic Ruby way to write attachment_attributes_valid?, but I prefer this because it's very clear as to what's going on.