RSpec 1.1.4 and Helpers

July 1st, 2008

Last week, RSpec 1.1.4 was released; and given my optimistic disposition I immediately upgraded. Happily, the upgrade was almost seamless. Two interdependent issues kept this from being a flawless upgrade:

  1. A new deprecation warning on our helper specs (no one really likes to see deprecation warnings)
  2. A bug in the new way to write helper specs (a show stopper)

Lets see what happens when we run our spec1.

Wonderland$ spec spec/helpers/dog_helper_spec.rb 
Modules will no longer be automatically included in RSpec version 1.1.4.  Called from ./spec/helpers/dog_helper_spec.rb:8
.Modules will no longer be automatically included in RSpec version 1.1.4.  Called from ./spec/helpers/dog_helper_spec.rb:16
.
Finished in 0.393129 seconds
2 examples, 0 failures

Lets examine the helper spec that generated these warnings:

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe DogHelper do

  describe "name_or_description()" do
    it "should return a description for a dog without a name" do
      dog = mock_model(Dog, :name => nil, :sex => 'male', :breed => 'Labrador', :color => 'black')
      name_or_description(dog).should == "(male Labrador, black)"
    end
  end

  describe "owner_link()" do
    it "should return a link to the owner" do
      owner = mock_model(User, :login => 'sally_smith', :to_param => 'sally_smith')
      dog = mock_model(Dog, :owner => owner)
      owner_link(dog).should =~ /users\/sally_smith/
    end
  end
end

So, what does this deprecation warning tell us? We are being warned that calls to name_or_description and owner_link, which are called on the implicit self of the RSpec it blocks, are going to fail in the future when the helper module (DogHelper in this example) is no longer automatically included by RSpec.

Or put another way, calling helper methods like name_or_description directly, exactly as one might in a view, will no longer work.

The new way to call helper methods is through the helper attribute of RSpec, giving access to an ActionView::Base instance with the helper module included. Lets go ahead and make this change to our two helper calls:

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe DogHelper do

  describe "name_or_description()" do
    it "should return a description for a dog without a name" do
      dog = mock_model(Dog, :name => nil, :sex => 'male', :breed => 'Labrador', :color => 'black')
      helper.name_or_description(dog).should == "(male Labrador, black)"
    end
  end

  describe "owner_link()" do
    it "should return a link to the owner" do
      owner = mock_model(User, :login => 'sally_smith', :to_param => 'sally_smith')
      dog = mock_model(Dog, :owner => owner)
      helper.owner_link(dog).should =~ /users\/sally_smith/
    end
  end
end

Lets try this out!

Wonderland$ spec spec/helpers/dog_helper_spec.rb 
.F
1)
NoMethodError in 'DogHelper owner_link() should return a link to the owner'
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.url_for
(eval):17:in `user_path'
/Users/leshill/src/rails/rspec114/app/helpers/dog_helper.rb:9:in `owner_link'
./spec/helpers/dog_helper_spec.rb:16:
Finished in 0.398686 seconds
2 examples, 1 failure

Hmmm, we have gotten rid of the deprecation warnings, but we now have a new (and truly unexpected!) failure.

This is a bona-fide bug in 1.1.4, and might lead to our having to rollback to 1.1.3 if there was no easy workaround. Thankfully, there is a fix already available for 1.1.5 , and that fix can be easily adapted within our own specs.

Lets add this before block to our spec:

before(:each) do
    # Patch until 1.1.5
    helper_controller = @controller
    helper.instance_eval { @controller = helper_controller }
  end

And then try it out:

Wonderland$ spec spec/helpers/dog_helper_spec.rb 
..
Finished in 0.40663 seconds
2 examples, 0 failures

Much better!

1 I am using a mock spec to demonstrate the issue, the minimal Rails app which fits around this spec is left as an exercise for the reader.

A participation deficit

May 11th, 2008

Clay Shirky is brilliant, and I have been reading his essays for a very long time1. Recently, he has been discussing his notion of a Cognitive Surplus . Good stuff; if you have not read it, go read it now.

The headline is that if we just switch 1% of yearly TV consumption hours to ‘participation’ we get the equivalent of 100 Wikipedia projects a year (that is development, comments, authors, editors, talk; the whole corpus as it stands today).

This the Cognitive Surplus; previously the surplus was masked by mind-numbing television until we switched from just being able to consume media to consuming, producing, and sharing media.

We are in a new media landscape, transformed irrevocably from mass consumption of media due to the communication advances of the last few decades.

Those advances enabled us to erase a participation deficit. The way the deficit manifested for me was an apparent inability to be alone (so says my wife) in spite of being a person who values solitude. Before the nascent intertubes, I had no way to participate. I could (and did) watch television. But I also did many other things, including hack on computers. Only there was no sharing, no participation. Once the nascent intertubes arrived, I2 and many others did participate. Our participation accelerated on the wave of Moore’s and Metcalfe’s laws to transform society in ways we could not anticipate, but that we were already exploring. We still cannot anticipate how we are changing society, and we are still exploring.

If you have read this, take a moment and write your own blog entry or tweet or upload a photo/video or post a message or whatever-social-sharing-thing-you-might-do and revel in your participation.

1 A decade?

2 My first email (lost forever) was in 1982.

A little bit of history

May 8th, 2008

I am a bit late on the history meme. Here I am:

Wonderland$ history | awk '{a[$2]++}END{for(i in a){print a[i] " " i}}' | sort -rn | head
149 git
60 spec
46 ls
45 gst
42 cd
28 rake
14 ssh
8 ss
7 houston
7 gco

Things to note1:

  1. 3 are git
  2. 3 are Rails development related
  3. 4 are common unix commands

1 4 are aliases

Recently while discussing jQuery, we got onto the topic of AJAX. This prompted me to declare:

Before AJAX, we used stone knives and bearskins to make our web apps

Actually, we were using JavaScript and XML to drive our web apps, we just did not call it AJAX, and it was a very challenging task. In spite of the difficulty of building it, we stuck with it and we were pretty darn happy with our pre-web 2.0 web2.0 app; so much so that we actually had people coming by just to see our UI in action.

I went back and looked for some of our old code, hoping to find the CVS repository1, but only finding a tarball of version 1.4. The following code is from client/script/services.js and dated August 24, 2001—although some version of this code would have existed in the spring of 2000:

BrServices.prototype.issueRequest = function (action) {
...
    var request = brUtils.newXmlDOM(action);
    brUtils.dumpError(request.parseError);
    var tryCount = 0;
    var condition = brServices.isOkay;
    var s = "";
    while (condition == brServices.isOkay) {

        try {
            s += "\n";
            var httpOb = new ActiveXObject("MSXML2.XMLHTTP"); // Was Microsoft.XMLHTTP
            s += "new: " + httpOb.readyState;
            httpOb.open("POST", brServices.brxmlURL, false);
            s += ", open: " + httpOb.readyState;
            httpOb.setRequestHeader("Content-Type", "text/xml; charset='UTF-8'");
            s += ", setRequestHeader: " + httpOb.readyState;

            httpOb.send(request);
...

Just looking at this snippet from our internal library makes me all the happier that we made the thing work at all. Kudos to Mark Judd and Brian Levine, two of our very talented small team, who were responsible for our UI.

1 You already know this, but just in case, CVS was a source code repository that pretty much everyone used before Subversion (unless you bought one from a vendor).

Control your layout

April 22nd, 2008

Recently, I added jQuery ajax tabs to an application. To reuse the existing views, I moved the relevant view code to partials for each required action:

render :partial => "entry", :object => journal_entry

and using something like the following in all the relevant controller methods:

format.html { render :partial => 'index' if request.xhr? }

As I added more features to the ajax tabs, I was conditionally rendering partials in every relevant action, as well as creating a partial. Not DRY.

layout provides a much DRY-er solution without the need for the partials. In application.rb:

layout proc { |controller| controller.request.xhr? ? false : 'application' }

When a controller action is invoked, if it is an XHR, no layout will be used. Otherwise the default Rails layout, ‘application’, will be used1.

1 layout expects the name of a layout to be returned from the proc; in this case, I am returning the name of the default. Returning false indicates that no layout should be used.

include and extend

April 6th, 2008

include and extend are commonly used to add methods to Ruby classes, and sometimes, why we use one or the other is not clear. Both do similar operations, but they are not equivalent. extend is more parsimonious adding only methods, includealso includes constants and module variables.

A simple guideline is to include instance methods and to extend class methods.

Here is an example of a class with an instance method and a class method. We have reopened the class so that we can show the differences from the original empty class:

def list_diff(label, list1, list2)
  puts "#{label}: #{(list1 - list2).sort.join ' '}"
end

class One
end

methods_from_new = One.new.methods
methods_from_class = One.methods

class One
  def instance_method
  end

  def One.class_method
  end
end

list_diff "Instance One.new diff", One.new.methods, methods_from_new
list_diff "Class One diff", One.methods, methods_from_class

when run shows the expected differences:

Instance One.new diff: instance_method
Class One diff: class_method

Now lets create and include a module:

module ModuleTwo
  CONSTANT_TWO = "two"
  def module_method
  end
end

class One
  include ModuleTwo
end

list_diff "Instance One.new diff", One.new.methods, methods_from_new
list_diff "Class One diff", One.methods, methods_from_class
puts "has constant? #{One.constants.sort.join ' '}"

when run shows that module_method and CONSTANT_TWO are now available:

Instance One.new diff: instance_method module_method
Class One diff: class_method
has constant? CONSTANT_TWO

And if we extend another module:

module ModuleThree
  CONSTANT_THREE = 'three'
  def another_module_method
  end
end

class One
  extend ModuleThree
end

list_diff "Instance One.new diff", One.new.methods, methods_from_new
list_diff "Class One diff", One.methods, methods_from_class
puts "has constant? #{One.constants.sort.join ' '}"

When run, shows that another_module_method is now available as a class method, but CONSTANT_THREE is not1:

Instance One.new diff: instance_method module_method
Class One diff: another_module_method class_method
has constant? CONSTANT_TWO

Finally, we can use include instead of extend to include class methods:

module ModuleFour
  CONSTANT_FOUR = 'four'
  def yet_another_module_method
  end
end

class One
  class << self
    include ModuleFour
  end
end

list_diff "Instance One.new diff", One.new.methods, methods_from_new
list_diff "Class One diff", One.methods, methods_from_class
puts "has constant? #{One.constants.sort.join ' '}"
When run, shows that yet_another_module_method is available as a class method, but CONSTANT_FOUR is not2
Instance One.new diff: instance_method module_method
Class One diff: another_module_method class_method yet_another_module_method
has constant? CONSTANT_TWO

1 Remember, include does more3 than extend.

2 For more details, check Programming Ruby: The Pragmatic Programmer’s Guide .

3 Bonus!

module ModuleFive
  CONSTANT_FIVE = 'five'
  def module_five
  end
end

one = One.new
one.extend ModuleFive

list_diff "Instance extends module", one.methods, methods_from_new
puts "has constant? #{one.class.constants.sort.join ' '}"

Instance extends module: instance_method module_five module_method
has constant? CONSTANT_TWO

Patching with git

March 27th, 2008

I patched the Ruby on Rails bundle for TextMate to allow footnotes to catch Haml views, which might be in files named something.haml or something.html.haml.

The change itself is trivial; making a patch using git was something new and, as it turns out, five steps:

1. Grab the source with git

Wonderland$ git clone git://github.com/drnic/ruby-on-rails-tmbundle.git

2. Make your changes

3. Commit locally, from the root of the source treee

Wonderland$ git commit -a -m 'add support for html.haml templates' Wonderland$ git show

4. Generate a patch file

Wonderland$ git format-patch origin

5. Email the patch file

Dan Webb has an alternative to the built-in syntax highlighting provided by Mephisto that uses JavaScript to markup your code by using regular expressions to tokenize the code. Nifty, and based on this DHTML behavior.

Here is a summary of the install:

  1. Use svn to pull from http://svn.danwebb.net/external/rails/plugins/filtered_column_code_highlighter/trunk/ into the vendor/plugins directory
  2. Do either of:
    1. Copy the assets/*.js to your public/javascripts directory
    2. rake install (copies to themes/site-1/javascripts)
  3. Create a new CSS stylesheet (codehighlight.css) with the syntax coloring you want; I used this as a start.
  4. Add the following to your layout.liquid template
{{ 'codehighlight' | stylesheet }}
{{ 'code_highlighter' | javascript }}
{{ 'html' | javascript }}
{{ 'ruby' | javascript }}
{{ 'css' | javascript }}
{{ 'javascript' | javascript }}

Now the following markup:

<filter:jscode lang=”javascript”>document.someScriptThing = “BOO”</filter:jscode>

Generates:

document.someScriptThing = "BOO"

define_method

March 23rd, 2008

While we were waiting for March 20th RubyJax meeting to get started, Steve Bristol entertained us with a Ruby quiz. One of the questions was particularly interesting as it had many possible answers. I will paraphrase the question as:

“How many ways are there to add a method to an existing class in Ruby?”

I have cataloged the answers that were tossed out as well as a few I just added.

1 . Open the class and add a new method

class String
  def method_one
    puts 'method 1'
  end
end

"abcde".method_one

2 . Create a singleton method on an instance

s = "12345"

def s.method_two
  puts 'method 2'
end

s.method_two

3 . Use Kernel#method_missing

class String
  def method_missing(name, *args)
    super unless name == :method_three
    puts 'method 3'
  end
end

"abcde".method_three

4 . Use Module#define_method

class String
  def create_method(name, &block)
    self.class.send(:define_method, name, block)
  end
end

"abcde".create_method(:method_four) { puts 'method 4' }
"12345".method_four

5 . A variation of #1 : include a module in the class

module ExtraMethods
  def method_five
    puts 'method 5'
  end
end

class String
  include ExtraMethods
end

"abcde".method_five

6 . A variation of #4 : use Object#instance_eval

String.instance_eval("define_method(:method_six) { puts 'method 6' }")

"abcde".method_six

7 . We could use Kernel#eval to execute any of the above as well; here we will emulate instance_eval with a binding

class String
  def self.get_binding
    binding
  end
end

eval("define_method(:method_seven) { puts 'method 7' }", String.get_binding)

"abcde".method_seven

Any others?

Monkey Patch!

February 22nd, 2008

So after missing the wisdom[1] of this:

http://xkcd.com/386/

And responding to this:

http://b.lesseverything.com/2008/2/19/haml-doesn-t-like-javascript

I went to bed.

This morning I wrote my first Rails (really Haml) Monkey Patch[2]. This adds some of what Steve wanted: undisturbed inline javascript with variable interpolation. I am not sure what else he wanted as I did not read his blog carefully :)

Throw this into lib/inline_javascript.rb

module Haml
  module Filters
    class InlineJavascript
      HEAD =<<EOH
<script type="text/javascript">
//<![CDATA[
EOH

      FOOT =<<EOF
//]]>
</script>
EOF

      def initialize(text)
        @text = HEAD + text + FOOT
      end

      def render
        @text
      end
    end
  end

  module Precompiler
    def close_filtered(filter)
      @flat_spaces = -1
      filtered = filter.new(@filter_buffer).render

      if filter == Haml::Filters::Preserve
        push_silent("_hamlout.buffer << #{filtered.dump} << \"\\n\";")
      elsif filter == Haml::Filters::InlineJavascript
        # suppress eval option does not apply to us
        flush_merged_text
        js = unescape_interpolation(filtered)
        @precompiled  << "_hamlout.buffer << #{js};"
      else
        push_text(filtered.rstrip.gsub("\n", "\n#{'  ' * @output_tabs}"))
      end

      @filter_buffer = nil
      @template_tabs -= 1
    end
  end
end

Throw this at the bottom of your environment.rb, taken from here http://groups.google.com/group/haml/msg/2d890cf1ede761ea

require 'inline_javascript'

Haml::Template.options = {
   :filters => {
     'javascript' => Haml::Filters::InlineJavascript
   }
}

And then do this in your Haml:

:javascript
  function oh_yea() {
    alert('Hello' + '#{@message}')
  }
%a{ :href =>"javascript:oh_yea()" } Oh Yea!

Update

I decided to make a patch and submit it to the Haml folks directly. This gave me the ‘opportunity’ to get git going.

Nathan Weizenbaum took the time to put in a more comprehensive fix to expose the interpolation functionality to all filters – look for it in an upcoming release.

[1] It’s turtles all the way down.

[2] You do not want to know what my subconscious originally thought up as the way to do this – that was twisted.