Autonomous Machine

Posts tagged with Ruby

Convert string values to Ruby objects

On a project at work we've started to use environment variables to toggle features on and off in the application. We're just getting into feature toggling, and this seemed to be the best lightest-weight way to get started.

Since environment variables are strings, and some of our values will be objects of other types, we needed a way to convert these strings into Ruby objects. A preliminary search yielded many options that utilized complicated regular expressions and other gymnastics. There had to be a better way.

Enter YAML. It supports serializing the types we needed:

irb(main):001:0> require 'yaml'
=> true
irb(main):002:0> YAML.load('true')
=> true
irb(main):003:0> YAML.load('108')
=> 108
irb(main):004:0> YAML.load('just a string')
=> "just a string"

Beautiful.

  • October 03, 2012
  • Article
  • Ruby

Running Ruby 1.9.3 on Heroku Cedar

Heroku now supports specifying a Ruby version in an application's Gemfile:

# Gemfile
ruby '1.9.3'

This requires Bundler 1.2.0, which can be installed with the --pre flag:

gem install bundler --pre

You also need to move 'bin' to the beginning of your application's PATH so the correct Ruby binary is found when starting your web processes:

heroku config:add PATH=bin:vendor/bundle/ruby/1.9.1/bin:/usr/local/bin:/usr/bin:/bin --app YOU_APP_NAME

Commit the Gemfile changes and push to Heroku, and your app should be running under Ruby 1.9.3!

  • May 23, 2012
  • Article
  • Heroku, Ruby

Store Hashie::Mash objects in Mongoid Documents

I've started using Mongoid's support for abitrary field types on a project. It involves working with a lot of nested hashes that are the result of parsing JSON, and I've found wrapping a Hashie::Mash around these data structures helps keep the code interacting with them clean:

doc.data.key.another_key.and_another_key
# vs
doc.data['key']['another_key']['and_another_key']

Since Hashie::Mash objects are just a subclass of Ruby's standard Hash, they can be stored in a field that's defined as a Hash in Mongoid:

class Doc
  include Mongoid::Document
  field :data, type: Hash
end

But when these documents are loaded back out of the database, the data field will contain a standard Hash. I'd like these hashes to be Hashie::Mash instances. To accomplish this, I defined a new column type for Mongoid called Mashed:

class Mashed
  include Mongoid::Fields::Serializable

  def deserialize(object)
    Hashie::Mash.new(object)
  end

  def serialize(object)
    Hash[object]
  end
end

That's all that is required to create a new column type in Mongoid, which can be specified just like any other in your document class:

class Doc
  include Mongoid::Document
  field :data, type: Mashed
end

Now I can save and load documents with Hashie::Mash fields. This is a trivally simple example, but by replacing the implementations of deserialize and serialize above, just about any kind of object could be persisted in a Mongoid document.

  • March 24, 2012
  • Article
  • MongoDB, Mongoid, Ruby

Teaching Ruby/ProgressBar to play nice with log files

From my pull request:

We have several scripts we use during application development that monitor long running processes with ruby-progressbar. We also have these scripts running in automated environments (such as CI), and the output in these situations is very noisy. We could alter the scripts, but I think it would be nicer if this library could gracefully handle these situations.

This patch adds a baseline behavior in non-tty environments so that things like log files aren't filled with every frame of the progress bar animation.

This was merged into the master branch, so one can pick up the change by using a git source in her Gemfile.

  • December 17, 2011
  • Article
  • Ruby

Cucumber, Capybara, Selenium, and Alerts

Although I try to avoid alert boxes in general, sometimes using them makes sense, especially when initially building out a feature.

This morning I was using Cucumber (and Capybara) to test deleting an object on a small project, and I needed to figure out how to click OK to dismiss in an alert and have the scenario continue. Here's the Cucumber step I ended up using:

When /^I click OK in the alert box$/ do
  page.driver.browser.switch_to.alert.accept
end

alert is returning an instance of Selenium::WebDriver::Alert, which also responds to dismiss and send_keys should you need to do something other than just click OK.

  • August 25, 2011
  • Article
  • Capybara, Cucumber, Ruby, Selenium

Inheritable Page Attributes in Rails

On most webpages there are a number of what I call 'page attributes'. These are little bits of data that have to be set based on the context of the app: the page title, a body class, etc.

It's easy enough to just use content_for to handle this scenario:

# in a view template
content_for(:title, 'Epic Page Title')

# in the page layout
yield :title

But I've found I often want to define these things at a higher level, perhaps at the controller level, either using a class method or inside an action body like this:

class UnicornsController
  page_attr :body_class, 'unicorns'
  def party
    # such a special action deserves a special class
    page_attr :body_class, 'unicorn-party'
  end
end

Or even in a view:

<%- page_attr :body_class, 'out-of-control-unicorn-shindig' -%>

This is a contrived example, but the idea is page attributes are inherited the way one would expect (controller class -> controller action -> template), while potentially being overridden at the lower levels. Here's the code:

# in app/controllers/application_controller.rb
include PageAttributes

# in config/initilizers/requires.rb
require 'page_attributes'

# in lib/page_attributes.rb
module PageAttributes
  def self.included(base)
    base.helper_method :page_attr
    base.class_attribute :page_attributes
    base.page_attributes = {}
    base.class_eval do
      def self.page_attr(key, value)
        self.page_attributes[key] = value
      end
    end
  end

  protected

  def page_attr(key, value=nil)
    @page_attributes ||= {}
    if value.nil?
      @page_attributes[key] || self.class.page_attributes[key]
    else
      @page_attributes[key] = value
    end
  end
end
  • June 20, 2011
  • Article
  • Rails, Ruby

Killing Processes in Ruby

Killing a process in Ruby is pretty straight forward. You just need to know the process ID for the process, and which signal you want to send:

Process.kill('INT', pid) # where pid is the process id of the process to be killed

A list of signals supported by your system is available from Signal.list, but knowing which one to use is easier after looking at the definitions. I typically use SIGINT (you can just use INT in Ruby) to kill processes.

Something that I've always found messy (and error prone) is cleaning up a bunch of dependent processes that were spawned as part of an automated script. A common case for this is running integration tests where you have at least one process that is the server, a process that is running tests against that server, and potentially other helper processes for web browser automation, etc.

This is a hard problem because it isn't easy to know the process IDs for all of the child processes of a script. It may spawn additional processes, and commonly the process IDs returned to Ruby when spawning processes end up being incorrect due to redirection or process replacement.

UNIX!

It turns out that there is a simple UNIX solution to this problem: process groups. By sending a signal to a process group instead of a single process ID, we are able to send it to all of a script's child processes without knowing their individual process IDs.

To do this, use Process.getpgrp to get the process group of the current process, and pass it to Process.kill as a negative number, which indicates the argument is a process group and not a process ID. And since the current process is going to receive the signal as well, use trap to catch it and provide a clean exit. In a script used for testing, use a non-zero exit status if any tests failed.

trap("INT") do
  exit
end
Process.kill('INT', -Process.getpgrp)

It appears that it is also possible to use 0 as the second argument to Process.kill, and have the same behavior. For the sake of clarity, though, I prefer the former option.

Everything in this article should work in POSIX environments, but I have no idea if it will work on Windows.

  • June 02, 2011
  • Article
  • POSIX, Ruby

Tweaking Ruby Enterprise Edition's garbage collection settings to speed up tests

I've been looking at ways to get the test suite for a project I'm working on running faster, and I came across slides from a presentation that present several ways to speed up tests. In particular, there are some environment variables that you can use to control the behavior of REE's garbage collector. I've found that using the values suggested in the slides decreases my test run times by 20-30%:

export RUBY_HEAP_MIN_SLOTS=1000000
export RUBY_HEAP_SLOTS_INCREMENT=1000000
export RUBY_HEAP_SLOTS_GROWTH_FACTOR=1
export RUBY_HEAP_FREE_MIN=500000
export RUBY_GC_MALLOC_LIMIT=1000000000

I saved this script to my path, and when I want run Ruby with these settings, I prepend the command I'm running with 'rmem'. Or, as suggested by the slides, you can add the settings to your profile. Be careful, as they cause Ruby processes to use significantly more memory.

  • August 21, 2010
  • Article
  • Code, Rails, Rspec, Ruby

ocrunner provides prettier OCUnit output

The testing landscape in the Objective-C world is much more sparsely populated than it is in the Ruby world, with the best two options being OCUnit and Google's customized version of OCUnit which ships as a part of their Google Toolbox for Mac. If you haven't tested with OCUnit before, Apple has a nice article to get you started.

Not in Kansas Anymore

Coming from the Ruby world, I was accustomed to tests running constantly in a console as wrote code. When I started doing iPhone development, it took me a while to setup a confortable working environment. Figuring out the best way to run tests was the biggest component of this period of acclimation.

After working on my first iPhone app for a few weeks, I found running tests via the Xcode GUI to be occasionally useful for debugging, but in general I felt having to switch between targets in Xcode in order to build the tests provided to much resistance. I started using the xcodebuild command line programs to run tests, which was great, except the output is hopelessly verbose, which makes it quite difficult to see if a test failed.

Ruby to the Rescue

OCRunner is a little Ruby wrapper for xcodebuild. It will run your tests with xcodebuild and display the results in a concise and colorful manner:

Screenshot of OCRunner in action

To try it out:

gem install ocrunner
cd /your/project/directory
ocrunner --auto

The --auto options will rerun tests whenever an Objective-C or header file is modified. ocrunner --help will tell you about all the available options. The code is on GitHub.

In Closing

I'm now thinking that the best way to test Objective-C code probably involves MacRuby, but if you're using OCUnit, OCRunner will give you a more streamlined workflow for TDD.

  • May 25, 2010
  • Article
  • Code, OCUnit, Objective-C, Ruby

Carmen now supports states for nine countries

Thanks to Andriy Tyurnikov and Tobias Schmidt for recently adding support for the Ukraine and Germany, respectively. And to the others that have fixed bugs or brought issues to my attention.

Carmen is a collection of country and state names and abbreviations, along with replacements for the state_select and country_select plugins that leverage this data.

  • December 16, 2009
  • Article
  • Code, Rails, Ruby

Convert HTML to Haml within Textmate

I have been integrating a lot of well built HTML templates into Rails applications this week. I've found Haml to be the best templating language, and I like to convert preexisting templates to use it as I add them to a project.

The Haml gem installs a command line executable that performs this conversion, html2haml. The program takes whatever is passed as standard input, performs the conversion, and writes the result to standard out. For a while I was using command this in combination with the pbcopy/pbpaste commands (I work on a Mac) to apply this to the content of the clipboard:

pbpaste | html2haml | pbcopy

This worked, but the extra copying and pasting was annoying. So I made a TextMate command to eliminate the tedium:

Screenshot of the TextMate bundle editor

The actual command is this (it's a bit hard to see above):

echo $TM_SELECTED_TEXT | html2haml

You could add a key binding as well, but so far I haven't bothered to.

  • November 13, 2009
  • Article
  • Code, Mac, Rails, Ruby

Add an ActiveRecord scope for easier eager loading

In the optimization phase of a project, one of the first things I do is look for pages that could benefit from the use of ActiveRecord's eager loading. Since named_scopes were introduced in Rails 2.1, most of my model loading has involved a chain of scopes, and it is always frustrating to have to change

@blogs = Blog.published.active

to

@blogs = Blog.published.active.scoped(:include => [:slugs, :topic => :slugs])

It's a small difference, but one I've never really liked. It seems like an obvious choice would be to add a named_scope to all subclasses of ActiveRecord::Base and have that handle the :include parameter. I finally wrote the 11 lines of code required to do this today:

module ActiveRecord
  module EagerLoadScope
    def self.included(base)
      base.named_scope :eager_load, lambda { |*inclusion|
        {:include => inclusion}
      }
    end
  end
end

ActiveRecord::Base.class_eval { include ActiveRecord::EagerLoadScope }

And now that line looks like this:

@blogs = Blog.published.active.eager_load(:slugs, :topic => :slugs)

Again, it's a small improvement, but one I consider worthwhile.

  • October 28, 2009
  • Article
  • Code, Rails, Ruby

Hiding parts of a view while in development

Most of my recent projects have involved working with a front end developer, who would provide HTML/CSS templates with dummy content to be integrated into a Rails backend. During the process of wiring up the views, I often wish to hide a section of a template or partial until the models and controllers are setup to support them.

I've started using this little view helper to hide code in a view, while providing a console message to remind me to go back and address the issue when the time is right:

def hide(&block)
  if Rails.env == 'development'
    match, file, line = caller.first.match(/([^:]+):(\d+).+/).to_a
    Rails.logger.info "** Hiding content on line #{line} of #{file}"
  end
end

It's simple to use, especially in HAML:

- hide do
  = some_view_code_that_will_not_execute

The helper will print something like this to your console:

** Hiding content on line 14 of /Users/jimb/Projects/demo/app/views/objects/show.html.haml
  • September 11, 2009
  • Article
  • CSS, Rails, Ruby

Install Passenger and nginx from source on Ubuntu

Passenger 2.2.3 came out this week, and while up until now I was using the Ubuntu packages from Brightbox (great blokes, really), I was having some problems with serving caches files and I didn't want to wait for the package to be updated. The package itself can be found on Github if you'd like to see it in more detail.

I used a combination of two guides to get everything working: one at Slicehost's wiki and another blog post by someone whose name may or may not be Johnny Pez.

It can be a little confusing to jump back and forth between the two, so I've posted a compiled set of steps here. After working through this article, you can continue on with the second Slicehost article for setting up the rest of your environment.

Passenger

I'm going to assume you have Ruby and RubyGems installed. If not, do that first before continuing.

Install the Passenger gem:

sudo gem install passenger

Then you need to build the nginx extension, which will be needed when we compile it.

cd `passenger-config --root`
sudo rake nginx

nginx

You'll need to install nginx's prerequisites:

sudo apt-get install libpcre3 libpcre3-dev libpcrecpp0 libssl-dev

Then, download the nginx source and unpack it. Everyone seems to be using the legacy stable version of nginx, and I haven't tried any of the newer ones yet.

wget http://sysoev.ru/nginx/nginx-0.6.37.tar.gz
tar xzvf nginx-0.6.37.tar.gz

Now we need to configure nginx.

cd nginx-0.6.37
./configure --sbin-path=/usr/local/sbin --with-http_ssl_module  \
--add-module=`passenger-config --root`/ext/nginx

You can change the configure flags if you want (see the comments on the Slicehost article), but I've left them alone so the rest of Slicehost's articles can be followed without modification.

Assuming the configure succeeded, all that's left is to compile and install.

make
sudo make install

All Systems Go

To make sure everything is working, start nginx:

sudo /usr/local/sbin/nginx

Visit your server's IP, and you should see the nginx welcome screen. This is good! Kill nginx:

sudo kill `cat /usr/local/nginx/logs/nginx.pid`

Now continue on with the Slicehost series to complete your nginx configuration. You'll also probably want to mirror the default configuration layout (these articles specify Intrepid, but the steps will be pretty close on most recent versions of Ubuntu).

  • June 19, 2009
  • Article
  • Code, Installation, Rails, Ruby

Interrogation creates boolean accessors from named_scope declarations

This is a little hack I came up with today when I got tired of duplicating logic in scopes and accessor methods. If I define a method in a model like this:

class Submarine < ActiveRecord::Base
  named_scope :on_surface, :conditions => {:location => 'surface'}
  named_scope :submerged, :conditions => {:location => 'under water'}
end

I think I should be able to interrogate the model to see if it is in these scopes without needing to define Submarine#on_surface? or Submarine#submerged? This is what I want to be able to do:

sub = Submarine.first => #<Submarine id: 1, location: 'surface'>
sub.on_surface? => true
sub.submerged? => false

Interrogation will allow for exactly this. The first version would fire a database query on each call, but I've since added in an attempt at parsing simple conditions against a model's attributes, with a fallback to the database for complicated conditions.

One last thing

The largest problem with this approach is an object's state is not always in sync with the database. I've thought about this a lot, and at this point I think it's best to raise an exception when using these methods on dirty objects. I think handling dirty objects is beyond the scope of this little hack.

The code is on Github.

  • June 05, 2009
  • Article
  • Code, Rails, Ruby

Duplicate a Mac OS X Terminal window with Ruby and Appscript

Even though OS X's Terminal now supports multiple tabs, I still find myself using lots of separate windows so I can see everything at once. A common pain point was opening a second Terminal window and changing to the current working directory (I do this a lot from the root of Rails apps: one window for running the server, and one for git and other work).

I've since written a little Ruby script that essentially duplicates the current Terminal window. Save the following as dup and put it somewhere on your path:

#!/usr/bin/env ruby
#
# Duplicates a terminal window, optionally running the passed command
# % dup (creates new window)
# % dup command arg1 arg 2 (new window, plus runs passed command)

require 'rubygems'
require 'appscript'

include Appscript

def quote(command)
  command.gsub(/(\$`\\!")/){|m|"\\\\#{m}"}
end

path = ENV['PWD']
terminal = app('Terminal')
coords = terminal.windows.first.position.get
dimensions = terminal.windows.first.size.get

command = "cd \"#{quote(path)}\" && clear"
command << " && #{quote(ARGV.join(' '))}" unless ARGV.empty?

terminal.do_script(command)
terminal.windows.first.size.set([dimensions[0], dimensions[1]])
terminal.windows.first.position.set([coords[0] + dimensions[0] + 10, coords[1]])

Appscript is required, but it is only a sudo gem install rb-appscript away. The script will pass and arguments passed to it to the new terminal window. One common use for me is dup script/server.

  • May 30, 2009
  • Article
  • Code, Mac, Ruby

Reloading Rails plugins in development mode

In Rails 2.3 you can cause your plugins to be reloaded with each request by adding this to your development.rb file:

config.reload_plugins = true

This was possible before, but it required altering Dependencies.load_once_paths and other black magic. I've always had issues with these approaches, and so it's nice to see official support for such a useful feature.

  • April 23, 2009
  • Article
  • Code, Rails, Ruby

Carmen: A Rails plugin for geographic names and abbreviations

On a recent project I became tired of state select and country select, the ubiquitous Rails plugins for providing a list of state or country names in views. I didn't like that their data was defined in code. Or that their data was defined in the view, so validating a model field against them was messy.

I started to refactor country select by moving the list of countries into a Geography module. But then I needed a list of states, and their abbreviations, and it struck me as odd that I needed two plugins that worked differently in order to provide what seemed to be some very basic functionality to my app.

Well she sneaks around the world, from Kiev to Carolina...

Carmen is the result of my frustration: a small unified library that handles country and state names and abbreviations. It stores its data lists in YAML, so it's easy to edit and add new state lists.

Carmen's primary use is providing a list of countries (or states within country), and, optionally, their abbreviations.

Carmen::states('US') => [['Alabama', 'AL'], ['Arkansas', 'AR'], ... ]
Carmen::state_names('US') => ['Alabama', 'Arkansas', ... ]
Carmen::state_codes('US') => ['AL', 'AR', ... ]

Similar methods are available for countries. It also has some convenience methods to assist in converting between names and abbreviations:

Carmen::country_name('US') => 'United States'
Carmen::country_code('Canada') => 'CA'
Carmen::state_code('Manitoba', 'CA') => 'MB'
Carmen::state_name('AZ', 'US') => 'Arizona'

Any of the state methods that require a country code will use a default country code if none is supplied. This defaults to 'US', but can be easily changed for those outside the US:

Carmen.default_country = 'CA'

And of course, it's simple to use Carmen in your model validations:

class Address < ActiveRecord::Base
  validates_inclusion_of :country, :in => Carmen::country_codes
end

Carmen also supplies view helpers that work the same as those in country select or state select, so it should be a drop in replacement in most cases. I've posted the docs, and the code is on Github.

  • April 01, 2009
  • Article
  • Code, Rails, Ruby

Using will_paginate with DataMapper and Sinatra

Yes, it can be done. But not using prebuilt gems, since we need to use the agnostic branch of the will paginate repository for DataMapper support. Here's what I did to install the necessary version of will_paginate:

git clone git://github.com/mislav/will_paginate.git
cd will_paginate
git checkout --track -b agnostic origin/agnostic # check out agnostic branch
gem build will_paginate.gemspec # build the gem
sudo gem install will_paginate-3.0.0.gem # install the gem

You could also create a fork, merge the code from agnostic into the master branch, push to GitHub, and have your own username-will_paginate gem to install. I decided this wasn't worth the effort, at least for the project I was working on.

Models

When will_paginate is required, it does some checks to see if it is running under Rails or Merb, and if so, mixes the class methods and view helpers into the base classes for you. If you're working with another framework (such as Sinatra), you'll need to require the finders and view helpers on your own:

require 'will_paginate'
require 'will_paginate/finders/data_mapper'

At this point, YourModelName#paginate will return paginated collections. But we need to be able to render pagination links as well...

Views

will_paginate has abstracted out the view logic into a few modules, and all it depends on is the implementation of the WillPaginate::ViewHelpers::LinkRenderer#url method. To get the view helper working, I added the following to the bottom of my Sinatra app's file (based on the supplied the merb adapter):

require 'will_paginate/view_helpers/base'
require 'will_paginate/view_helpers/link_renderer'

WillPaginate::ViewHelpers::LinkRenderer.class_eval do
  protected
  def url(page)
    url = @template.request.url
    if page == 1
      # strip out page param and trailing ? if it exists
      url.gsub(/page=[0-9]+/, '').gsub(/\?$/, '')
    else
      if url =~ /page=[0-9]+/
        url.gsub(/page=[0-9]+/, "page=#{page}")
      else
        url + "?page=#{page}"
      end      
    end
  end
end

helpers WillPaginate::ViewHelpers::Base

The code assumes the page number will be in the query string; it could easily be modified to work in other situations.

  • February 17, 2009
  • Article
  • Code, Ruby

Obvious Tip 1: Short Circuiting Logic While Debugging

Who hasn't forced a conditional statement one way or the other to force part of a complicated system to always trigger during a bout of (potentially late night, potentially ill-advised) hackery? I've learned the hard way to always take the twenty seconds to add a warning so I don't forget to remove the hack. Of course, specs are going to fail if I forget to, but it's easy to spend time looking in the wrong places for a fix to something as sturdy as if statements.

puts('! OVERRIDING SOME LOGIC !')
if true || some_method_that_may_or_may_not_return_true?
  ...
end

Make sure to put the reminder outside the conditional. Depending on what you're working on, you can use a framework logger if you have one. I used puts above because the code I'm working on (which sparked this entry) is standalone.

  • January 20, 2009
  • Article
  • Code, Ruby

Using blocks in Rails custom date formats

A not-so-well known feature of ActiveSupport is its ability to handle blocks as definitions for custom date and time formats. Here's one I just added to a project to give nice short dates, stripping out any leading zeros:

ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS.merge!(
  :shorty => lambda {|date| date.strftime("%m/%d/%y").gsub(/0?(\d+)\/0?(\d+)\/(\d+)/, '\1/\2/\3')}
)

You can drop that in your environment.rb; a better option is to place it in a file inside config/initializers.

  • January 15, 2009
  • Article
  • Rails, Ruby

Steer WebDriver from Ruby with Backseat

I've been wanting to try out WebDriver for a while now, and this week I finally had a day to devote to kicking its tires. So far, I like pretty much everything about it. Oh, except that right now you have to write your tests in Java.

Backseat is a little Ruby library that uses Rjb to wrap the WebDriver classes in a nice interface. My goal is to integrate with Rspec eventually, but for now, only the basics work.

Here's an example (you can see the original Java source at the WebDriver site):

require 'backseat'
include Backseat

Backseat.load!('/Users/jimb/src/webdriver/trunk') # path to webdriver root

driver = Backseat::Driver.new(:firefox)

driver.get('http://www.google.com/webhp?complete=1&hl=en')

element = driver.find_element input(:name => 'q')
element.send_keys('Cheese')

wait :until => lambda { driver.has_child?(table(:class => 'gac_m')) &&
                        driver.find_element(table(:class => 'gac_m')).displayed? }

driver.find_elements(td(:class=> 'gac_c')).each do |e|
  puts e.text
end

driver.close # quit Firefox

For the most part, I've stuck to the original WebDriver API, but there are a few differences. One is the usage of a set of helpers for building XPath locators (used as arguments to find_element above), and the wait method which sure beats the way it was done in Java. There will be API changes going forward as I start using this for some real testing.

I have, of course, set up a repo at Github.

  • October 17, 2008
  • Article
  • Ruby

Simple Rspec Markup Matcher

It seems like every project I've worked on recently involves some kind of markup processing. Here's what I've been using to test my markup processing:

module Spec
  module Matchers
    module Markup
      class MatchMarkup
        def initialize(expected)
          @expected = expected
        end

        def matches?(target)
          @target = target
          standardize(@target).eql?(standardize(@expected))
        end

        def failure_message
          "expected\n#{@target}\nto match\n#{@expected}"
        end

        def negative_failure_message
          "expected\n#{@target}\nto not match\n#{@expected}" 
        end

        # alphabetize the order of attributes
        def standardize(markup)
          clean_whitespace(markup).gsub(/( [a-zA-Z_]*="[^"])+"/) do |match|
            ' ' + match.strip!.split(' ').sort.join(' ')
          end
        end

        def clean_whitespace(markup)
          markup.
          gsub(/\s+/, ' '). # cleanup whitespace
          gsub(/>\s/, '>'). # kill space after tags
          gsub(/\s</, '<'). # space before tags
          gsub(/\s\/>/, '/>'). # space inside self-closing tags
          strip
        end
      end

      def match_markup(markup)
        MatchMarkup.new(markup)
      end
    end
  end
end

This matcher does two things that are helpful. It standardizes the order of attributes, which is crucial since most of the time HTML attributes are specified in Ruby using a hash and therefore their eventual order can't be guaranteed. It also removes all whitespace, so your specs can be easier on you.

Here's a totally contrived example:

it "should wrap with a div" do
  input = <<-HTML
    <p>
        In order to attain the greatest possible clearness,
        let us return to our example of the railway carriage supposed
        to be travelling uniformly.
    </p>
  HTML
  
  output = <<-HTML
      <div class="klass" id="div_3">
        <p>
          In order to attain the greatest possible clearness,
          let us return to our example of the railway carriage supposed
          to be travelling uniformly.
        </p>
      </div>  
  HTML
  
  wrap_with_div(input, :class => 'klass', :id => 'div_3').should match_markup(output)
end
  • October 01, 2008
  • Article
  • Rails, Rspec, Ruby