Half-Penny For Your Thoughts

rounded down to the nearest cent



Categories


Recent Articles




Computing

Building a Builder, Part 3

This continues on two previous articles about setting up a custom FormBuilder for use in your Rails applications. See Part 1 (Introduction) and Part 2 (The helper method and related work on the NodeBuilder class). The particular Builder I’m working through is meant to generate simple tables and forms, nothing real fancy, with minimal view code. So, today, I’m going to look at creating a basic table via the builder.

In my last entry, I added the NodeBuilder#to_s method:

def to_s
  "<div>" + @rows.join("<br />") + "</div>"
end

I’m going to flesh this out to create a table instead of a div with line breaks:

def to_s

  # Setup table header
  headers = ""
  @headers.each { |h| headers << @template.content_tag("th", "#{h[:label]}") }

  headers = @template.content_tag("thead",
            @template.content_tag("tr", headers) )

  # Setup content rows of table
  content = ""
  @rows.each do |items|
    row_content = ""
    items.each { |item| row_content << @template.content_tag("td", item) }
    content << @template.content_tag("tr", row_content)
  end
  content = @template.content_tag("tbody", content)

  # Put header rows in content rows into table element
  return @template.content_tag("table", headers + content)
end

This takes information from the @headers and @rows variables and puts that data in a table, great for index views. The trickier part is getting that information into the variables. I’ll use the #field method to do that:

def field(label, content = "", options = {})

  # Allow just a symbol representing the attribute name to be passed. Use a titleized version of the field name for the label
  if label.kind_of?(Symbol) && content.empty?
    content = label
    label = label.to_s.titleize
  end

 # If this is the first item (row) populate the @header array; otherwise, it's already populated and can be skipped.
  if @item == 0
    @headers << {:label => label} # This is a hash because I might want to add more information later.
  end

  # If the content is a symbol, assume it's a method name on the current object.
  if content.kind_of?(Symbol)
    if content == :type # To avoid deprecation warnings
      content = @object[content]
    else
      content = @template.escape_once(@object.send(content))
    end
  end

  # Now, add the content to the @rows variable.
  @rows[@item] << content
end

Well, that’s pretty close to what I want, except that I want to also be able to use the with text_fields, password_fields, etc. So:

# create_labelled_field defines text_field and similar methods.
def self.create_labelled_field(method_name)
  define_method(method_name) do |label, *args|

    # if label is a symbol, assume its an attribute name;
    # use the titleized form of attribute name as the label.
    # In either case, call super without the label to get the content.
    if label.kind_of?(Symbol)
      args = [label] + args
      field(label.to_s.titleize, super(*args))
    else
      field(label, super(*args))
    end
  end
end

# This creates the methods for text_field, etc, which call create_labelled_field
(field_helpers + ["select"]).each do |name|
  create_labelled_field(name)
end

Yay. I may come back later and add some clearer explanations but this works (more or less) and provides a nice starting point to DRY-ing up forms. I may add a few more articles to this section, but they’ll probably be along the lines of “and here’s the goofy stuff you can do now…”


Computing

Selenium with Rails

This past week, I’ve started using Selenium for integration testing of a Rails application. Writing the tests was pretty simple, thanks to the IDE, but setting them up to run using rake took me a bit. If you want the rake file, skip down a bit; first a few of the resources I found and comments thereon.

Okay, then, the post in “Steve’s Blog” got me to where I could run tests, but I finessed my Rake file a bit. My particular goal was to be able to run Selenium in a specific browser based on the rake task, and to have a task that runs several browsers.

namespace :selenium do

  desc 'Run Selenium java server'
  task :server do
    # dir would change based on where the selenium-server is located
    dir = "c:\\path\\to\\server"
    `java -jar "#{dir}selenium-server.jar"`
  end

  BROWSERS = %w{ie iexplore firefox safari opera}

  desc 'Run selenium tests in all major browsers'
  task :all do
    errors = %w(selenium:iexplore selenium:firefox selenium:safari selenium:opera).collect do |task|
      puts task.upcase
      begin
        Rake::Task[task].invoke
        nil
      rescue => e
        task
      end
    end.compact
    abort "Errors running #{errors.to_sentence}!" if errors.any?
  end

  # Create task for running tests in each browser
  BROWSERS.each do | browser |
    Rake::TestTask.new(browser.to_sym => "db:test:prepare") do |t|
      # t.options add the browser to ARGV to the test file.
      t.options = ["-- #{browser}"]
      t.libs << "test"
      t.pattern = 'spec/selenium/**/*_test.rb'
      t.verbose = true
    end
    Rake::Task["selenium:#{browser}"].comment =
      "Run selenium tests in #{browser}"
  end

end

In the test_helper.rb file, I added this to set a global $browser variable:

$browser = ARGV[0] || "iexplore"

$browser = "iexplore" if "ie" == $browser # because I'd rather just type "ie"

And, finally, in the test files:

# Server and URL settings may vary.
@selenium = Selenium::SeleneseInterpreter.new("localhost", 4444, "*#{$browser}", "http://localhost:4445", 10000);

Computing

Building a Builder, Part 2

Finally adding part 2 in my not-exactly-a-series series on create Rails Builders. Here’s the first part. As I mentioned way back then, one of my goals was to write a Builder that allowed me to send a full block without any ERB <%= %>’s and such. Now, in many cases, that would not be a good thing. After all, in many cases, most of an rhtml is html, not Ruby. But my target for this builder is “simple” forms and tables that are basically lists of attributes. So, html out.

The key lines from the helper method are:

builder = NodeBuilder.new(collection, object_name, self, {:list_type => :col}, block)
collection.each { |object| yield builder.row, object }
concat(builder.to_s, block.binding)

So, there’s three methods I’ll need for sure in my Builder class:

  1. initialize
  2. row
  3. to_s

I think taking this out of order may make the most sense, so I’m going to start with to_s. Starting very simply:

class NodeBuilder < ActionView::Helpers::FormBuilder
  def to_s
    "Hello World"
  end
end

The concat(builder.to\_s, block.binding) take the takes the to_s output and places it into the view. So, in this case, regardless of what is in the node_for block in the view, it’s going to be replaced with “Hello World”. Let’s make it do something more useful:

def to_s
  "<div>" + @rows.join("<br />") + "</div>"
end

Now the to_s is going to return a series of lines based on whatever is in that @rows instance variable. Somehow, we need to populate @rows. That’s where the yield line comes in.

collection.each { |object| yield builder.row, object }
says that for each object in collection (an enumerable passed to the node_for helper method), we’re going to yield
builder.row
and that object in the collection back to the block in the view. So what does row do?

The node_builder.row needs to return the node_builder object. The block can then be:

<% node_for :person, @person, :url => { :action => "update" } do |f, object|
  f.text_field "First name", :first_name
  ...
end %>

That “f” is a NodeBuilder instance. So f.text_field says send text_field method to the NodeBuilder object (created by the node_for helper). Oh, boy. So the row method has some work to besides just returning self (which is why the row method exists at all.

  1. it needs to create a variable for node_builder.text_field to stick its return value into, which can then be accessed by to_s.
  2. row needs to set a variable indicating what object in the collection we’re on. (I’ll probably talk about why I’m always using a collection of some sort in a later entry; or, I might forget).

(by the way, calling the method “row” makes sense for my usage; there’s probably a better generic name for it).

def row
  @item ? @item += 1 : @item = 0
  @object = @collection[@item]
  @rows[@item] = ""

  return self
end

So each time the yield is called, row tells the node_builder instance that we’re working with the next object, and the returns the whole node_builder object for use by the block. It also adds an entry to the @rows Array, which text_field and other methods will make use of.

initialize could do a lot but for now, let’s just initialize that @rows Array.

def initialize(collection, object_name, template, options = {}, proc = Proc.new)
  @rows = []
end

One more thing before I end this article, a way too simple text_field method:

def text_field(*args)
  @rows[@item] << super(*args)
end

Okay, that was a lot to get to using <% instead of <%=, but it also sets up for later. Continued at some point…


Computing

Wonderful Rails Plugins

There are many Rails plugins out there these days. Yay. Since plugins tend to target a specific audience, fulfilling a small, well-defined purpose, it’s easy to miss the one you’re needing. So, hey, I thought I’d share a few of my most-often-used / favorites for anyone who wanders along.

  1. auto_complete: This plugin is a good one for Rails 2.0 apps. The auto_complete (and other AJAX) functionality was moved out of the Rails core and into plugins, part of a larger effort to move non-core features into plugins. This plugin is where the auto_complete_for goodness now resides.
  2. paginating_find: Ah, pagination. Good fun is Rails’ land. Sometimes you need custom pagination. And sometimes you just want pagination that works well. There’s several plugins out there, this one is my preference. The igvita.com blog has an entry on how to use this plugin to make very nice page links.
  3. ruby-whois: Ha ha. Actually, this one is mine, and incomplete. But it works for my needs, and can be used as a “normal” library. Anyway, it helps with accessing whois information for domains.
  4. exception_notification: Cheap and easy email notifications when something goes wrong on a production site.
  5. userstamp: Like timestamps, but with user ids. I tried to implement this functionality on my own before finding this plugin. Much easier with the plugin, for me.
  6. engines: Engines make plugins a bit more powerful, easing the inclusion of views, migrations, etc. in plugins. It can be tempting to overuse this functionality, but it can also come in very handy in the right situations.

Computing

Rails 2.0

I just read on the ruby on rails weblog the announcement of Rails 2.0 preview release. The thing that initially struck me is that all the changes sound like good ideas to me. Yay! In particular, I’m glad to see the semicolons in resource routes disappear, just because they kept striking me as odd.

Anyway, yay for continued Rails progress. I guess I’ll pick an app in the next couple of weeks and give a shot at migrating to 2.0. The other nice thing is that I feel comfortable with leaving applications at 1.2.3 (or jumping up to 1.2.4), as I have been quite satisfied with that.

To me, the central theme of 2.0 is that of a simple system with many plugins rather than a complex stand-alone. I generally like this because, hey, I rarely use most of the features many applications/libraries offer, and I like the opportunity to control what features I use. It also makes migrations to a new version easier because I know at a glance what features I am using.

Continued good things from the Rails developers and community.