Jan 15 2008

RSpec + JRuby

dastels @ 10:17 pm

My coworker, Paul Zabelin, posted here on some ideas that we’ve been experimenting with using RSpec stories and JRuby.

Tags: 

Jan 15 2008

New and Improved: RSpec 1.1.2

dastels @ 3:48 am

Tonight RSpec-1.1.2 was released. See most of the details on David Chelimsky’s blog.

I take a personal interest in this release as it includes my first active submission to the codebase in some time.

The functionality I added relates to the definition of steps in the new story component. Up until now, you used a string to define a step. For example:

Given "a student named '$name'" do |name|
  #...
end

When "the student is given a grade of $grade" do |grade|
  #...
end

Then "the student should $pass_fail" do |pass_fail|
  #...
end

This would result in stories like the following:

Given a student named 'Mike'
When the student is given a grade of 40
Then the student should fail

Continually talking about “the student” is grating and very un-natural sounding. Sounds downright, bloody legalistic, actually. And a lawyer is the last thing we want to be accused of sounding like… other than maybe Denny Crane.

One approach to this would be to go to something like:

When "he is given a grade of $grade" do |grade|
  #...
end

Then "he should $pass_fail" do |pass_fail|
  #...
end

which would give us:

Given a student named "Mike"
When he is given a grade of 40
Then he should fail

Now, speaking of lawyers, we probably want to make this a little more PC and be able to do this:

Given a student named "Michelle"
When she is given a grade of 60
Then she should pass

We could conceivable create another set of steps for the feminine forms, refactoring to remove the duplication. That might suffice in the simple case, but it’s still rather crude. I’d like to be able to use a regular expression and create steps something like:

When /(he|she) is given a grade of (.*?)/ do |pronoun, grade|
  #...
end

Then /(he|she) should (.*?)/ do |pronoun, pass_fail|
  #...
end

With release 1.1.2, that’s exactly what you can do.

There are a couple things to point out:

  1. Alternatives need to be in a group to limit their scope.
  2. Whatever matches groups such as that (and any others) will be sent into the supplied block as arguments. As such they need to be accommodated by having a block parameter for each of them.
  3. Since this is already a regexp, no internal processing is done to it. With string step names, variables (of the form $<identifier>) are rewritten as (.*?). When using a regexp as the step name where there are variables, we much do this rewriting ourselves.

This new feature provides a new level of flexibility in defining story steps. Have fun with it.

Tags: 

Nov 05 2007

RubyConf 2007

dastels @ 8:04 am

I’m sitting in the airport in Charlotte, on my way home from RubyConf2007.

What a fabulous conference. This the most enjoyable conference I’ve been to in a very long time.

There was so many great talks/ideas/projects, it’s hard to pick highpoints, but these stand out for me (in no particular order):

  • drnic for some very cool tech and sheer entertainment value.
  • Laurent Sansonetti for a jaw-dropping talk/demo about what’s possible with the latest Ruby love from Apple.
  • Charlie & Tom on JRuby
  • Evan on Rubinius… that is one cool project… sign me up
  • and, of course, Matz’s keynote

It’s no wonder the Ruby community is “nice people” when we have someone like Matz at the head of it.

Maybe as rewarding, or even more, than the tech & talks was seeing old friends again and meeting others face to face that to date I’d only known online.

It was an awesome weekend, and I’m eagerly looking forward to the next Ruby conference!

Tags: 

Sep 17 2007

The Art of Computer Programming

dastels @ 10:29 pm

Tonight I was exploring the current state of RubyCocoa (and it looks good), and I noticed a quote from Knuth’s “The Art of Computer Programming”, his 1974 ACM Turing Award Lecture.

Paul Graham has the text of the full lecture here.

This is a great talk, and should be required reading for anyone calling themselves a programmer. I found the last section, especially interesting.. here’s what I consider to be the core bit form it:

“Therefore I want to address my closing remarks to the system programmers and the machine designers who produce the systems that the rest of us must work with. Please, give us tools that are a pleasure to use, especially for our routine assignments, instead of providing something we have to fight with. Please, give us tools that encourage us to write better programs, by enhancing our pleasure when we do so.”

It struck me that Ruby and recent systems from Apple fill Knuth’s request quite nicely. According to Wikipedia, Knuth uses Macs.. I wonder how he thinks they answer his challenge from ‘74. I wonder what his view of Ruby is… Anyone heard anything about that?

Tags: 

Sep 11 2007

Describing Equivalence Classes in Ruby with RSpec

dastels @ 1:45 pm

Here’s an article I wrote a while ago but didn’t get around to releasing.  Enjoy. 

Describing Equivalence Classes in Ruby with RSpec

Since writing that article, I’ve made good use of the code. Here’s an example of it in action: grouping the cards in a deck into their types. A second method then uses those groups to reassemble the cards into an ordered list.

  CARD_TYPES = ['basic land', 'land', 'creature', 'artifact',
                'enchantment', 'sorcery', 'instant', '']

  def maindeck_cards_grouped
    maindeck_cards.equivalence_classes(*CARD_TYPES) do |card, the_type|
      card.magic_card.card_type.downcase.include?(the_type)
    end
  end

  def maindeck_cards_ordered
    cards_by_class = maindeck_cards_grouped
    (CARD_TYPES.inject([]) {|cards, type| cards << cards_by_class[type]}).flatten
  end

Tags: 

Mar 30 2007

RSpec Autotest now a Rails Plugin

admin @ 12:30 am

RSpec Autotest now a Rails Plugin:

“Posted by Nick Sieger Wed, 15 Nov 2006 15:46:00 GMT

Inspired by a posting on the RSpec list and recent comments stating that my Auto RSpec hack wasn’t working, I’ve bitten the bullet and upgraded to RSpec 0.7.2, and made rspec_autotest a plugin in the process. So, here are the necessary incantations to auto-rspec your project.”

OK, so I’m a bit behind on RSpec developments. But this plugin makes a world of difference when using rspec.

While Nick mentions rspec 0.7.2 in the post, I’m using it with the latest (pre-trunk) 0.9.0 without any issues.

If you use rspec (with rails or not.. see rspec-autotest-for-standalone-projects) you should be using this plugin!

Tags: 

Dec 22 2006

The Ruby Way

admin @ 2:00 pm

I’ve started reading the 2nd edition of “The Ruby Way” by Hal Fulton.

So far it’s been good. I’ve been popping it open somewhat randomly so far, but plan to take a more structured look at it shortly. I enjoyed the overview of OO as applied to Ruby as well as the section on Rubyisms and idioms.

This is a big book, and looks very comprehensive. I’m looking forward to working through it.

One thing I have noticed… and I don’t know Hal so I’m not sure… is that there are comments regarding programming language issues that seem to indicate a lack of familiarity/awareness of Rubys grand-daddies: LISP & Smalltalk. Comments like “in more recent languages such as Java, memory is reclaimed…”   Both LISP and Smalltalk had garbage collection. In fact that’s where the majority of GC research was done. This however is a minor quibble. This is a book about Ruby, and a good one at that.

Tags: 

Oct 29 2006

RSpec On Rails

admin @ 4:58 pm

Nice post by Defiler on RSpec:

RSpec On Rails:

RSpec is mere days away from a new release with greatly improved Rails support.

Since people are currently paying me to write Rails code, rather than plain old standalone Ruby (hint hint), I’ve been waiting for these features before making serious use of RSpec.

As an exercise, I ‘ported’ the acts_as_authenticated controller tests to RSpec. The results were fairly interesting. Subjectively, I find it more readable than the test/unit version. Objectively, one of the test/unit test cases doesn’t get run because there are two methods with the same name.

So far, so good. Anyone who thinks that RSpec is ‘only’ about a different set of terminology should give it a serious try first.

(Via ~:caboose.)

Tags: 

Oct 29 2006

Using TinyMCE with Rails/AJAX

admin @ 11:56 am

Our current project is basically a very structured (i.e. niche/focused/custom) content management system. There are a few places where we want to give the client the ability to edit some HTML page content. The client is tech-savvy, but not in the “I enjoy slinging HTML” way. Ergo, we needed WYSIWYG HTML editing capability. After some research we decided that TinyMCE was the way to go. We just had to make it work the way we wanted it to.

Here are my notes on getting TinyMCE working nicely in a Rails/AJAX environment.

Start by grabbing the rails plugin (and read the material) from here and install it as per the instructions.

Allowable options to uses_tiny_mce are documented here… there are lots of them

I figured that having followed the directions in the above, my work was over. In fact it was just beginning. The above will work fine if you have a page, with a textarea that you want to be WYSIWYG. Our requirements were a bit more involved. The textarea in question was in a partial that was rendered via a remote updater call (via a link_to_remote in a list on the page). The main issue here is that the textarea didn’t exist when the page was rendered… so TinyMCE had to be hooked up to it later… when it was injected into the DOM tree. Some digging through support forums and I found what I needed. This required a bit of java script in the partial… after the textarea:

<%= form_remote_tag :url => {:action => 'edit_page', :id => @page},
                    :before => "tinyMCE.triggerSave(true,true)" %>
  <b>Page Content:</b><br />
  <%= text_area :page, :content, :rows => 15, :cols => 150 %>
  <br /><br />
  <%= submit_tag "Update" %>
  <script type="text/javascript">
  //<![CDATA[
    tinyMCE.execCommand('mceAddControl', true, 'page_content');
  //]]>
  </script>
<%= end_form_tag %>

The next issue was getting the contents out of TinyMCE and accessible to the parameter construction for the remote call. After a bit of research I ended up with the following:

<%= form_remote_tag :url => {:action => 'edit_page', :id => @page},
                    :before => "tinyMCE.triggerSave(true,true)" %>

Normally the save is trigger by a page unload, I needed to force it to happen before the remote call happened. Putting the call to force (aka trigger) the save in the :before script of the form submission remote call worked great. So now I had a bigger problem. TinyMCE was getting hooked up to the textarea. If the user picked another page item from the list, the div would be refilled with a different rendering of the partial… with a different textarea node. The old textarea would be gone… out from under TinyMCE. I needed a way to reconnect to the new textarea. More digging turned up this example. It gave me the final bit of the puzzle. My final solution includes the following in application.js:

bTextareaWasTinyfied = false; //this should be global, could be stored in a cookie...

function setTextareaToTinyMCE(sEditorID) {
	var oEditor = document.getElementById(sEditorID);
	if(oEditor && !bTextareaWasTinyfied) {
		tinyMCE.execCommand('mceAddControl', true, sEditorID);
		bTextareaWasTinyfied = true;
	}
	return;
}

function unsetTextareaToTinyMCE(sEditorID) {
	var oEditor = document.getElementById(sEditorID);
	if(oEditor && bTextareaWasTinyfied) {
		tinyMCE.execCommand('mceRemoveControl', true, sEditorID);
		bTextareaWasTinyfied = false;
	}
	return;
}

These two functions are used to disconnect from an existing textarea and reconnect to the newly rendered one. In the list item that causes the rendering:

<%= link_to_remote "<span class=\"listTitle\">#{page.title}</span>",
                   {:update => "editPage",
                    :url => {:action => :get_page, :id => page},
                    :before => "Effect.Fade('editPage',
                                            {duration: 0.25,
                                             queue: 'end',
                                             afterFinish: function(effect) {
                                               unsetTextareaToTinyMCE('page_content')}})",
                    :complete => "Effect.Appear('editPage', {duration: 0.5, queue: 'end'})"},
                    :title => "Edit #{page.title}"  %>

To avoid visual weirdness the disconnect is delayed until the fade has completed. Once the new version of the partial has been loaded, it’s faded back in. The relavant bit of the partial is here:

<%= form_remote_tag :url => {:action => 'edit_page', :id => @page},
                    :before => "tinyMCE.triggerSave(true,true)" %>
  <b>Page Content:</b><br />
  <%= text_area :page, :content, :rows => 15, :cols => 150 %>
  <br /><br />
  <%= submit_tag "Update" %>
  <script type="text/javascript">
  //<![CDATA[
    setTextareaToTinyMCE('page_content');
  //]]>
  </script>
<%= end_form_tag %>

The last thing was to add a Done/Cancel button to the form:

<input type="button"
       value="Done"
       onclick="Effect.Fade('editPage',
                            {duration: 0.25,
                             queue: 'end',
                             afterFinish: function(effect) {
                               unsetTextareaToTinyMCE('page_content')
                             }})" />

Now I have a WYSIWYG textarea in a partial that’s rendered via a remote call.. and it all works smoothly and exactly as required.

Tags: 

Oct 24 2006

Encapsulation in Action

admin @ 5:35 pm

I’m an OO bigot, plain & simple. One of my soapboxes/sacred-cows/hot-buttons/whatever is encapsulation… or more to the point, the lack of it.

I was writing some code recently and thought it might make a nice example.

I was just starting with a rough idea: I have a Form object that has FormPages. Pages can be moved around, added, deleted, etc. But they need to be ordered… pages of a form get filled out in some logical sequence.

So here’s my initial spec:

context "A form" do

  setup do
    @form = Form.new
    @form.form_pages << FormPage.new(:name => ‘one’, :number => 3)
    @form.form_pages << FormPage.new(:name => ‘three’, :number => 2)
    @form.form_pages << FormPage.new(:name => ‘two’, :number => 1)
  end

  specify “should be able to order its pages” do
    @form.ordered_pages.collect {|page| page.number}.should_eql [1, 2, 3]
  end
end

Some code to make this work:

class Form < ActiveRecord::Base
  has_many :form_pages

  def ordered_pages
    form_pages.sort
  end
end

class FormPage < ActiveRecord::Base
  belongs_to :form
  has_many :form_fields

  def <=>(other_page)
    number <=> other_page.number
  end
end

And that works just fine. BUT I’m totally violating any semblance of encapsulation. Look at FormPage for starters. We’re reaching into other_page and pulling out its number. Not so bad maybe.. it’s an instance of FormPage too. But it’s still breaking encapsulation. It can be done cleaner with a bit of double dispatch fun. Try this:

class FormPage < ActiveRecord::Base
  belongs_to :form
  has_many :form_fields

  def <=>(other_page)
    other_page.compare_number_to(number)
  end

  def compare_number_to(other_page_number)
    other_page_number <=> number
  end
end

That still works, and things are kept nicely encapsulated.

Now let’s look at the spec:

specify "should be able to order its pages" do
  @form.ordered_pages.collect {|page| page.number}.should_eql [1, 2, 3]
end

That collect is leaving a bloody trail in its wake as it hacks its way through the pages, ripping out numbers like the organ collectors in Monty Python’s “The Meaning of Life”. What are the alternatives? Well.. to support sorting we just wrote FormPage#compare_number_to(other_page_number). Let’s see if we can’t use that:

specify "should be able to order its pages" do
  @form.ordered_pages.each_with_index do |page, index|
    page.compare_number_to(index + 1).should_eql 0
  end
end

That does it. Notice that this works because we had that method to use, and the FormPages in the fixture were number sequentially.. we could clean it up even more by numbering the pages starting with 0 (which is much more ruby-esque anyway):

context "A form" do

  setup do
    @form = Form.new
    @form.form_pages << FormPage.new(:name => ‘one’, :number => 2)
    @form.form_pages << FormPage.new(:name => ‘three’, :number => 1)
    @form.form_pages << FormPage.new(:name => ‘two’, :number => 0)
  end

  specify “should be able to order its pages” do
    @form.ordered_pages.each_with_index do |page, index|
      page.compare_number_to(index).should_eql 0
    end
  end
end

Nice. We can go further… I don’t really care for the exposure of the array of pages. I’d feel better with an addPage method:

class Form < ActiveRecord::Base
  has_many :form_pages

  def add_page(aPage)
    form_pages << aPage
  end

  def ordered_pages
    form_pages.sort
  end
end

That lets us have our context setup be like this:

setup do
  @form = Form.new
  @form.add_page(FormPage.new(:name => 'one', :number => 2))
  @form.add_page(FormPage.new(:name => 'three', :number => 1))
  @form.add_page(FormPage.new(:name => 'two', :number => 0))
end

That’s much nicer from a pure OO perspective. “But,” you say, “that’s not overly Rails-like!” So… it IS very OO. It’s up to you where you draw the line. But remember… just because we can ignore encapsulation doesn’t mean we should. Doing so couples our code to the schema, and the goal of OO is to manage and minimize the coupling in our code. For a one-off, throw-away, 15 minute hack.. who cares.. but a mission critical application needs more thought & care. And it’s amazing how many one-off, throw-away, 15 minute hacks turn into mission critical apps.

David Chelimsky suggested making one more step and removing the reliance on the comparison method. After all, that is an implementation detail and relying on it from the spec makes it hard to change… just like any dependancy. So:

context "A form" do

  setup do
    @form = Form.new
    @pages_in_order = [
      FormPage.new(:id => 10, :name => 'two',  :number => 0),
      FormPage.new(:id => 5,  :name => 'one',  :number => 1),
      FormPage.new(:id => 13, :name => 'zero', :number => 2)
    ]
    @form.add_page(@pages_in_order[2])
    @form.add_page(@pages_in_order[1])
    @form.add_page(@pages_in_order[0])
  end

  specify "should be able to order its pages" do
    @form.ordered_pages.each_with_index do |page, index|
     page.should_equal @pages_in_order[index]
   end
  end
end

This final version is much better. I’ll blame my not seeing it on being sick at the time.

Tags: 

Next Page »