Archive for 2009/11:

Quick and dirty hack for RSpec's before(:all)


RSpec is generally nice testing framework. It supports before and after hooks which can be invoked before/after each test case or all test cases. before(:all) is a little confusing though. It runs your "before" block before all "describes" and "contexts", also nested ones. Here's example:

describe User do
  before(:all) { puts "preparing for war" }
  it "should foo" do
    ...
  end
  context "active" do
    it "should bar" do
      ...
    end
  end
  context "inactive" do
    it "should baz" do
      ...
    end
  end
end

I would expect "preparing for war" to show up once. But before(:all) was called thrice. First, for top level "describe", then two times for "contexts". There were some suggestions to change this behaviour or to add and option to skip call for nested groups but nothing has changed recently. People are even trying some crazy hacks like this.

What I needed was to wipe database for every model spec and every request spec because factory generated records made my build unstable (due to uniqueness validations). After trying few things I ended up with using before(:all) with some condition. First, I've added before_top_level_group method to Spec::Runner::Configuration and saved it in spec/before_top_level_group.rb:

$_groups = []

class Spec::Runner::Configuration
  def before_top_level_group
    before(:all) do
      top_level_group = self.class.to_s[/^.+ExampleGroup::([^:]+)/, 1]
      unless $_groups.any? { |g| top_level_group == g }
        $_groups << top_level_group
        yield
      end
    end
  end
end

Then in spec_helper I used it like this:

require 'before_top_level_group'

Spec::Runner.configure do |config|
  config.before_top_level_group do
    # re-migrate db for each top-level group (it usually equals one *_spec.rb file)
    DataMapper.auto_migrate!
  end
end

Voila! I know it's a dirty hack but it works for me and I'll be using it until RSpec is patched or I switch my testing framework to something else (Bacon looks nice).

View Comments

More Rack::Shell goodies for all Rack worshippers


Rack::Shell got a lot of attention lately and I received some feature requests/ideas from great ruby hackers. Daniel Neighman, currently working on Pancake, pointed me towards Bryan Helmkamp's rack-test. This awesome piece of code is now used by racksh to simulate HTTP requests to your Rack application. Yay!

% racksh
Rack::Shell v0.9.4 started in development environment.
>> $rack.get "/"
=> #<Rack::MockResponse:0xb68fa7bc @body="<html>...", @headers={"Content-Type"=>"text/html", "Content-Length"=>"1812"}, @status=200, ...

Check out README for details. Here are just few examples what's possible:

$rack.get "/", {}, { 'REMOTE_ADDR' => '123.45.67.89' }
$rack.header "User-Agent", "Firefox"
$rack.post "/users", :user => { :name => "Jola", :email => "jola@misi.ak" }

Now you can build and test Sinatra apps in single racksh session like this.

Another nice thing in new version is support for session setup through config files. Rack::Shell supports configuration file .rackshrc which is loaded from two places during startup: user's home dir and application directory (in this order). You can put any ruby code in it, but it's purpose is to setup your session, ie. setting headers which will be used for all $rack.get/post/... requests.

For example to set user agent to Firefox and re-migrate db if loaded environment is test put following in .rackshrc:

# .rackshrc

$rack.header "User-Agent", "Firefox"
DataMapper.auto_migrate! if $rack.env == "test"

You can also make requests in config file:

# .rackshrc

$rack.put "/signin", :login => "jola", :password => "misiacz"

This will ensure you are always logged in when you start racksh :)

Full documentation and sources are on github, gems for all versions on gemcutter.org. Enjoy!

View Comments

Rails-like console for any Rack based ruby web app


I always miss script/console from Rails while developing my Sinatra apps, especially ones built with DataMapper where I need to auto-migrate my db. Sinatra doesn't come with any comparable solution as it's not a full framework, but rather library for creating simple web apps. Recently I tried Heroku platform and their "heroku console" command inspired me to create something similar - racksh aka Rack::Shell.

racksh is a console for Rack based ruby web applications. It's like Rails' script/console or Merb's merb -i, but for any app built on Rack. You can use it to load application environment for Rails, Merb, Sinatra, Camping, Ramaze or your own framework provided there is config.ru file in app's root directory.

It's purpose is to allow developer to introspect his application and/or make some initial setup, ie. running mentioned DataMapper.auto_migrate!. It's mainly aimed at apps that don't have similar facility (like Sinatra) but can be used without problems with Merb or Rails apps.

How it works? It loads whole application environment like Rack web server, but it doesn't run the app. Simply, methods like use or run which are normally invoked on Rack::Builder instance are being stubbed.

Instalation is as easy as:

gem install racksh -s http://gemcutter.org

Then to open console run following inside rack application directory (containing config.ru file):

racksh

To specify location of config.ru set CONFIG_RU env variable:

CONFIG_RU=~/projects/foobar/config.ru racksh

Executing ruby code inside application environment and printing results is also supported:

racksh Order.all
racksh "Order.first :created_at => Date.today"

Default Rack environment is set to development but it can be changed by setting RACK_ENV env variable:

RACK_ENV=production racksh

Now I don't need to create some kind of console.rb for my new Rack app, I just use racksh. Enjoy!

UPDATE: Read here for more info.

View Comments

Subdomain shared cookies in Merb's specs


I’m currently working on Merb app which makes use of subdomains for user accounts. In order to have user authenticated on both base domain and subdomains I set cookie domain like this in init.rb:

 Merb::Config.use do |c|
    c[:default_cookie_domain] = ".mydomain.com"
  end

Dot at the start of domain name specifies that the cookie will be set for “mydomain.com” and all its subdomains. This works perfectly in browser(s) but I’ve encountered a problem in my request specs. After authenticating at “mydomain.com” app redirects to “username.mydomain.com”. But next request to either “mydomain.com” or “username.mydomain.com” shows that session is no longer authenticated. After some reading through merb-core sources I’ve found that Merb::Test::Cookie can’t properly handle cookies with domain set to ‘.foo.com’ (with dot at the start).

This is because Merb::Test::Cookie#valid? makes some regexp check comparing current request domain with domain from cookie, which fails for cookie domain set to ‘.foo.com’. I’ve created a fix and spec for this scenario. Patch can be found in this commit.

Until it gets merged and released with new Merb version (I hope in 1.1) It was already merged into Merb master branch, but until the 1.1 release the simplest workaround is to monkey patch Merb::Test::Cookie class, ie. in spec_helper.rb like this:

class Merb::Test::Cookie
  def valid?(uri)
    domain_ = domain.start_with?('.') ? domain[1..-1] : domain
    uri_path = uri.path.blank? ? "/" : uri.path
    uri.host =~ Regexp.new("#{Regexp.escape(domain_)}$") &&
    uri_path =~ Regexp.new("^#{Regexp.escape(path)}")
  end
end
View Comments

Finish Him completes them all!


Once again extending JEdit’s feature set. This time word completion. JEdit already has two word completion mechanisms: built-in one and TextAutocomplete plugin. However none of them provides as smooth completion as Netbeans or Textmate’s ones. First I was thinking about creating some BeanShell macro too improve my completion experience but it looked like to much logic for just a macro. So I ended up with new plugin, and this time written in… Scala (finally an opportunity to start learning Scala on real project).

It’s called “Finish Him!” and following comparison shows how it’s different from existing solutions for JEdit.

JEdit’s built in word completion:

  • shows popup when triggered (-1)
  • doesn’t sort suggestions by distance from the caret (-1)
  • searches for suggestions in other (visible) buffers (+1)
    Total: -1

TextAutocomplete:

  • shows popup automatically (-1)
  • doesn’t sort suggestions by distance from the caret (-1)
  • doesn’t search for suggestions in other (visible) buffers (-1)
    Total: -3

Finish Him!:

  • doesn’t show popup when triggered, just inserts next suggestion (+1)
  • sorts suggestions by distance from the caret (+1)
  • searches for suggestions in other visible buffers (+1)
    Total: 3

Finish Him! kills other solutions with score of 3. I know that these things may look like details but after using Netbeans word completion you know that it’s the simplest, fastest and most “correct” (by proper suggestions sorting) word completion. If you’re using JEdit then check it out, it’s now available on Plugin Central so it can be installed directly from JEdit’s plugin manager. For sources look here.

View Comments