Close and Go BackBack to Viget

Tips and Tricks

Testing for HTML Tags in Rails Plugins

Brian Landau
Brian Landau, Web Developer, June 04, 2008 5

When creating Rails plugins that add ActionView helpers, we often test to ensure they produce specific tags with specific attributes. When testing this type of assertion within controller tests, we have the very useful assert_tag and assert_select; but in plugin tests and elsewhere, these aren't available. Adding this functionality to your own tests turns out to be somewhat convoluted.

The first realization you come to is that assert_tag and assert_select can't be used because they test against the response body of a controller. Since we're trying to test functionality independent of other components, using ActionController when it's not necessary is not recommended. You might be tempted at this point to just test against a regular expression and forget using these methods. That is doable, but a little unmanageable in the long run. First off, attributes can't be expected to be in a specific order, and their order doesn't matter, so you don't want to test for that. This will even initially make your regexes fairly complicated. Here's an example of testing for a specific name attribute:


def test_for_name_attribute
   tag = text_field_tag('login')
   assert_match /<input\s+(([\w^(name)]+="([^"'><]+)?"\s+)+)?name="login"((\s+[\w^(name)]+="([^"'><]+)?")+)?(\s+)?\/>/, tag
end

As you can see, even matching one attribute can result in a extremely long and ugly regex. What if we want to match multiple attributes and nested tags? This quickly becomes untenable.

If you look to include the component used by assert_tag to find and match specific tags, you will find require 'html/document'. Try just requiring this by doing this at the top of your test:


require 'test/unit'
require 'rubygems'
require 'active_support'
require 'action_view'
require 'html/document'

You'll find you get a no such file to load -- html/document (MissingSourceFile) error. If you dig around in the Rails code, you'll find it lives in action_controller/vendor/html-scanner. To be able to include html/document, you need to first include action_controller.

Now that we have html/document included, let's make a test helper method to match a tag specification. This takes little change from the original assert_tag method.

def assert_tag_in(*opts)
   target = HTML::Document.new(opts.shift, false, false)
   opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
   assert !target.find(opts).nil?, 
     "expected tag, but no tag found matching #{opts.inspect} in:\n#{target.inspect}"
end

With this, you can now use assert_tag_in to do the same test as above:

def test_for_name_attribute
   tag = text_field_tag('login')
   assert_tag_in tag, :input, 
     :attributes => {:type => 'text', :name => 'login'}
end

The whole code for a sample test_helper.rb file for plugins can be found in this pastie.
Also, look at the documentation for assert_tag for all the options available.

From this, an assert_no_tag_in could easily be made. You could use a similar technique to make a assert_select_in, although assert_select is much more complex and often overkill for simple helpers where you aren't testing as large a number of tags or as complicated as nesting.

Update:

Given the interest in a hpricot version of the assert_tag_in, I've put together one:

def assert_tag_in(target, match)
   target = Hpricot(target)
   assert !target.search(match).empty?, 
   "expected tag, but no tag found matching #{match.inspect} in:\n#{target.inspect}"
end

With this you can then write a similar test like this:

def test_for_name_attribute
   tag = text_field_tag('login')
   assert_tag_in tag, 'input[@name="login"]'
end

The alternative test_helper.rb using hpricot can be found in this pastie.

Make Apple’s Address Book Pull from Exchange

Tony Pitale
Tony Pitale, Web Developer, May 28, 2008 1 If you've ever wished you knew who you worked with but were to afraid to ask...entourage.

Continue reading "Make Apple’s Address Book Pull from Exchange"

Problems With Attachment_fu and a Dedicated Image Model

Ben Scofield
Ben Scofield, Development Director, May 19, 2008 0

I recently ran across a real head-scratcher with an application I’ve been working on. I’ve got a polymorphic relationship from several different models to an Image model, which is implemented with attachment_fu. Each of the polymorphic models can have one and only one image, but those images each have many thumbnails (which are handled via attachment_fu).

To start, then, my polymorphic models each include the Imageable module, which in part looks like this:

module Imageable
  def self.included(base)
    base.class_eval do
      has_one :image, :dependent => :destroy, :as => :record
      # ...
    end
  end
end

The problem that we saw was that sometimes - for some records, on some pages, but repeatably - the public_filename coming back for a given thumbnail had two size modifiers attached. For instance, what should have looked like /uploads/0000/0123/test_large.png came back /uploads/0000/0123/test_large_large.png. The database records were all correct, and all calls to public_filename directly returned the correct result. After a couple of hackish attempts to work around this, though, I realized that the underlying problem was that, when I eagerly loaded the image record, one of the thumbnails was being returned instead of the parent image.

The solution ended up being simple:

module Imageable
  def self.included(base)
    base.class_eval do
      has_one :image, :dependent => :destroy, :as => :record, :conditions => 'parent_id IS NULL'
      # ...
    end
  end
end

By adding the parent_id IS NULL condition, the thumbnails are all excluded from any call to record.image, so the only row returned will always be the parent image.

Just something to keep in mind for next time!

From TextMate to ExpressionEngine

Patrick Reagan
Patrick Reagan, Development Director, May 01, 2008 10

Between writing code, documentation, and blog posts, I spend a lot of my time working in TextMate. It's an efficient text editor, but it becomes a bit of a headache when it's time to work on a new blog post. The real problem is my process:

  1. Open TextMate and start collecting my thoughts for a post in some cohesive fashion (optional)
  2. Finalize most of the content and basic formatting
  3. Copy the content into ExpressionEngine, tweak the formatting, add hyperlinks
  4. Preview the post and publish to the site

The reason for all this masochism is my dislike for editing content in a web-based form, WYSIWYG or otherwise. I love my editor enough that I'm willing to jump through some hoops in order to use it for the bulk of my editing work. But, there's a better way to do all this.

Continue reading "From TextMate to ExpressionEngine"

Building an Environment From Scratch With Capistrano 2

Matt Swasey
Matt Swasey, Web Developer, April 30, 2008 7

I recently attended the Pragmatic Programmers Advanced Rails Studio. Overall, I thought it was great. Even though I've had personal experience in most of the topics covered, it filled in a lot of gaps in my personal knowledge. I came out of the three days feeling more well-rounded as a Rails developer. I also met some cool people.

On the topic of deployment, Chad Fowler offered that Capistrano is really just an automated remote shell. That got me thinking: if you could do anything through Capistrano that you could do on the command line, you could not only automate the deployment of the application, but the construction and configuration of the environment in which that application runs.

I rebuilt my 256 slice at Slicehost with a brand new Ubuntu Hardy 8.04 install to test this out. I wanted to see if I could automate the installing and configuring of everything it would take to run a Rails application through Apache and Passenger. It worked! What follows are the steps I took to make it happen.

Continue reading "Building an Environment From Scratch With Capistrano 2"

A Development Community for Viget Labs and Beyond

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

Viget Is Hiring!

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

Upcoming Events

Jack Speaks at WebExpo - September 16
Jack's Talking Agile Sept. 16-19

Recent Comments

I have used several different strategies for achieving this, and have settled on the plugin seed_fu to accomplish this task. Because it uses plain ruby files you have quite a few more options for manipulating your seed data as opposed to using fixtures. I have found that this lets me create seed data that is much less brittle than fixtures can be. You can find seed_fu on...