A Rails tale of caution: functional tests hanging with controllers in subdirectories

Posted by trevor Tue, 30 May 2006 23:27:05 GMT

This afternoon I was hit by a real headscratcher – documented here for future googlers looking for information on: functional tests hanging controllers subdirectories :-)

I added in some tests for controllers that were in a subdirectory (as in “./script/generate controller boo/hoo“) and suddenly rake started hanging.

I could run individual functional tests just fine but as a suite – forget it.

The source of the problem turned out to be twofold:

  1. in test_helper.rb we alias the get and post test methods so that we can optionally capture all of our output and run it through a validator.
  2. the require line that ./script/generate creates for loading test_helper.rb in your functional tests is relative to the current file’s directory. For functional tests in subdirectories you get a require argument ending with ”/../../test_helper” rather than the one ending with ”/../test_helper” as you do in top-level functional tests.

Even though those two different require lines point to the same file, from require’s perspective they are different – so the file is loaded twice.

And that’s a big no-no if you are doing a simple method alias without checks against doing it twice.

The bulletproof solution is to have tests use File.expand_path() on the argument to require. But if the generators keep spitting it out as they do now it’s one of those “pushing water uphill” situations. I smell a patch.

For now, I’m just wrapping my alias calls with a check of self.instance_methods to make sure the alias hasn’t already been done.

Posted in code | no comments

Living under a rock? Google and Sketchup

Posted by trevor Mon, 29 May 2006 01:16:40 GMT

A few years back, while I was building my house, I downloaded a demo copy of Sketchup to help visualize how everything would look. It’s a fantastic tool for 3D design, one of those rare pieces of software that just makes sense.

Unfortunately it’s also software that costs about $500 USD and as much as I wanted to, I simply couldn’t justify the expense. My love affair with Sketchup was doomed to be just a few short weeks as the demo period quickly slipped away.

Sketchup did leave me with a legacy though: Ruby. It ships with a ruby interpreter and has hooks to extend the software with Ruby scripts. Had I not seen the Ruby/Sketchup integration I probably wouldn’t have given Rails a second look when I stumbled across it a year and a half (gosh!) ago.

Okay, that’s enough nostalgia. The real point here isn’t whether I owe a debt of gratitude to a 3D modeling program for more than a year of fun, full-time work with Rails…

The real point here is that Google bought Sketchup and they are releasing cut-down free versions. I think that’s pretty big news and I have absolutely no idea how I missed it.

They’ve already released the free version for PC and apparently a free version for OS X is in the works as well.

I am so chuffed about this. It’s going to make designing my kids’ backyard playstructure a breeze.

Snippet: using 'inherited' to set filters in controllers

Posted by trevor Fri, 26 May 2006 22:51:37 GMT

One of my apps has a set of controllers in an ’/admin’ directory. They all must have a :login_required before_filter and they all must define an authorized? method that checks if the current user is an administrator.

For a couple of reasons the idea of having a special “AdminController” which everything under ’/admin’ would inherit from (and that set up the filter and authorized? method) kind of irked me.

First, there was the name clash – AdminController becomes ‘admin’ in routes and that clashes with my directory called ‘admin’, so I’d have to choose a name like “AdminBaseController” or some-such. Blech.

Second, I just didn’t like the extra file for the base controller. Call me picky, I can take it.

What I wanted was a way to say “if the controller is in the /admin directory then it needs this filter and this method”.

Class#inherited to the rescue. Here’s what I put in /app/controllers/application.rb:

# For all controllers in the 'Admin' namespace we set
# the login_required before_filter and define an authorized?
# method that checks user.is_administrator? 
def self.inherited(subclass)
  # call super first - otherwise any before_filters we add are lost
  super
  if subclass.name.split('::').first == 'Admin'
    subclass.before_filter :login_required
    subclass.send(:define_method, :authorized?, Proc.new {|user| user.is_administrator?})
    subclass.send(:protected, :authorized?)
  end
end

This is fine for my present needs. If controllers under /admin start to need any more shared behavior I’ll just bite the bullet and do the AdminBaseController thing – but for now this is clean and works.

Posted in code | no comments

Best Practices: a strong case for attr_accessible part 2

Posted by trevor Wed, 24 May 2006 22:31:58 GMT

This is a followup of part 1 which you should read before continuing here.

So… are the requirements satisfied? No.

It’s possible for any user to delete a project that they don’t actually own. More specifically, it’s possible for a user to arbitrarily assume ownership of any project they know the id of. After that, the project is theirs to do with as they choose.

The code that allows you to assume project ownership is here:

class UserController < ApplicationController
  def save
    current_user.update_attributes(params[:user])
  end
end

class User < ActiveRecord::Base
  attr_protected :is_administrator
  has_many :projects
end

Spotted it yet?

Read more...

Posted in code | 9 comments

Best Practices: a strong case for [redacted] part 1

Posted by trevor Wed, 24 May 2006 03:10:00 GMT

Tonight I decided to tip over my blog-post idea pickle jar and see if anything interesting fell out. This is the first one that didn’t get crumpled up and thrown in the trash (gotta love that pickle jar).

Instead of just blathering on I’d like to show you a few lines of code and invite you to answer a pop quiz:

class UserController < ApplicationController
  def save
    current_user.update_attributes(params[:user])
  end
end

class ProjectController < ApplicationController
  def destroy
    @project = Project.find(params[:id])
    if ( @project.user_id == current_user.id ||
      current_user.is_administrator? )
      @project.destroy
    end
  end
end

class User < ActiveRecord::Base
  attr_protected :is_administrator
  has_many :projects
end

class Project < ActiveRecord::Base
  belongs_to :user
end

Before anyone gets too bent out of shape – don’t worry about the stuff you don’t see. Everything you need is there.

Here’s the requirements we’ve been given:

  1. Users should not be able to grant themselves administrative capability
  2. Only administrators or the project’s owner can delete a project

And here’s the pop quiz: are those requirements satisfied? Why or why not?

Bonus points (for what they’re worth) will be given if you can tell me what I’m going to make “a strong case for” in the title of this post.

I’ll post my answers tomorrow (Wednesday) at 10:00 PM GMT.

[Edit] someone asked so I’ll clarify: the current_user method in the controllers always returns the correct user.

[Edit] another clarification: we can assume that actions not shown in ProjectController such as edit/save perform the same checks as ProjectController#destroy before doing anything.

[Edit] Part 2 is available now.

Posted in code | 11 comments