Archive for April, 2007

Hacking Rails HTML form helpers to add CSS classes

I really like using Rails form helpers, but one thing I found myself doing often is assigning classes to elements that I want to style a bit differently than the rest. But, I have to remember to do that for every element that I want it applied to. Luckily the nature of Ruby lets you open up classes and modules to redefine methods, so I was able to hack up the ActionView tag helpers directly in my Rails app. The result is that by default, all form elements generated by the helpers get a CSS class name based on what type of element it is.

How is this useful? Well for instance, I like to style my HTML submit buttons to be fixed width and center aligned. With CSS, I can’t style a submit button specifically because it’s an input element. There are also input elements that can be text fields, radio buttons, and checkboxes, but I don’t want it to apply to them as well. So the path of least resistance to style a submit button exclusively is to assign it a class every time I use it. It can get tedious to have to remember that every time I build a new form in my application.

So here is my solution: hacking the ActionView tag helper module to make any form element automatically have a class assigned to it based on what type of element it is. In the case of a submit element, it will have a class submit_button. Then all I have to do is define the selector for input.submit_button in my stylesheet and I’m done. All submit buttons will now be styled the way I want, and I don’t have to remember to assign them a class every time I place one in a template.

Add this code to your application.rb file (or anywhere you’d like) and your form elements will get nice CSS classes by default. It will even keep any class names you assign to your tags and append the default one at the end. I’ve verified it working using ActionPack-1.12.5 and later (Rails 1.1.6 – 1.2.3).

  module ActionView
    module Helpers
      module TagHelper        def tag(name, options = nil, open = false)
          add_default_class(name,options)
          "<#{name}#{tag_options(options) if options}" + (open ? ">" : " />")
        end        def content_tag(name, content_or_options_with_block = nil, options = nil, &block)
          if block_given?
            options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
            add_default_class(name,options)
            content = capture(&block)
            concat(content_tag_string(name, content, options), block.binding)
          else
            add_default_class(name,options)
            content = content_or_options_with_block
            content_tag_string(name, content, options)
          end
        end        private        def add_default_class(name,options)
          options = Hash.new if options.nil?
          class_name = case name.to_s
          when 'textarea'
            'text_area'
          when 'select'
            'select'
          else
            nil
          end          if class_name.nil? && options.has_key?('type')
            class_name = case options['type']
            when 'text'
              'text_field'
            when 'password'
              'password_field'
            when 'file'
              'file_field'
            when 'radio'
              'radio_button'
            when 'checkbox'
              'check_box'
            when 'submit'
              'submit_button'
            when 'image'
              'submit_image'
            when 'button'
              'button'
            else
              nil
            end
          end
          return options if class_name.nil?
          if options.has_key?('class')
            options['class'] += " #{class_name}"
          else
            options['class'] = "#{class_name}"
          end
        end
      end
    end
  end

Make Graticule failover to other geocoding services

The Graticule Ruby gem makes it super easy to geocode addresses using multiple services. One thing I’ve found in developing Unthirsty is that sometimes a geocoding service either fails to be reached or the address couldn’t be found. So as a precaution, I’ve set up a way for Graticule to failover to another service if a lookup fails. Here’s how.

First, set up a list of your preferred geocoding services. Graticule expects a service name and optionally an access key. Some geocoding services like Ontok don’t require a key. However Google does, and it’s probably the one you’ll want to use over the others. Order your geocoders so your preferred geocoders are listed first.

  PREFERRED_GEOCODERS = [
    {:name => :google, :key => '[your key here]'},
    {:name => :yahoo, :key => 'My Service'}, # yahoo will take any string for a key
    {:name => :geocoder_us, :key => 'My Service'} # same with geocoder.us
  ]

Take your pick of geocoding services. Graticule supports quite a few:

Now it’s time to put those geocoders to use. This method loops through your PREFERRED_GEOCODERS array and sets up a new Graticule instance with the service name and key you provided. It will then try to locate it with a lookup to that service. Calling the locate method on a service instance will raise an exception if an error occurs, so it’s possible to rescue all the different errors you can get while doing a lookup.

Note that this was used in a Rails project, so I’m using logger, a built in Rails logging facility. You may want to substitute your own logging method if you’re not using Rails.

  def geocode(query)
    PREFERRED_GEOCODERS.each{|service|
      begin
        geocoder = Graticule.service(service[:name]).new(service[:key])
        result = geocoder.locate(query)
        logger.info("Found address [#{query}] with service [#{service[:name]}]")
        return result unless result.precision == :country # probably not worth keeping
      rescue Graticule::AddressError => detail
        logger.error("Nothing found [#{query}] with service [#{service[:name]}] (#{detail})")
      rescue Graticule::Error => detail
        logger.error("Nothing found [#{query}] with service [#{service[:name]}] (#{detail})")
      rescue Graticule::CredentialsError => detail
        logger.error("Invalid credentials [#{service[:name]}] with [#{service[:key]}] (#{detail})")
      rescue Errno::ECONNREFUSED
        logger.error("Connection refused to #{service}")
      end
    }
    raise Graticule::AddressError.new("Couldn't find address [#{query}] with any service")
  end

The failover works because since we are in a begin..rescue block, a raised exception will cause it to skip over the return result line, log the exception, then move on to the next service. But if a lookup succeeds, it will return a new Graticule::Location instance that contains the results, and not go any further. If no lookups are successful, it will raise a new Graticule::AddressError exception that you can trap while calling the method in your code.

Also notice I don’t return a result if the :precision attribute is :country There’s pretty much no use for it if you’re going for specific addresses. You may want to adjust it based on your needs. Maybe you only want results that have :address precision.

You now have a failsafe method for geocoding locations in your Rails app. Leave a comment if you found this useful, or if you have any improvements. Enjoy!

How to get HTML elements by partial class name

In all modern browsers there is a way to get all elements that have a specific class name using Javascript. It’s really useful when manipulating elements on a page. But what if you want to get all elements that start with a certain prefix? Well there’s a way to to do by including this snippet in your Javascript files. It requires Prototype to work.

  document.getElementsByPartialClassName = function(value, parentElement) {
    var children = ($(parentElement) || document.body).getElementsByTagName('*');
    var elements = [], child;
    regexp = new RegExp('(?:^|\s)('+value+'.*?)(?:\s|$)');
    for (var i = 0, length = children.length; i < length; i++) {
      child = children[i];
      if (matches = Element.classNames(child).toString().match(regexp)) {
        elements.push(Element.extend(child));
      }
    }
    return elements;
  };

Now, you can get all of your HTML elements that, for instance, have a class name that begins with ‘comment_’:

  var els = document.getElementsByPartialClassName('comment_');

And then do something with them:

  for(var i = 0; i < els.length; i++) {
    el.style.color = '#F00';
  }

I built this function for another project I’ve been working on (which I’ll post about soon). In the project, I was looking for elements that started with a certain prefix, but had different endings (e.g. ‘comment_one’, ‘comment_two’). I used the leftover values to further manipulate the elements. Hopefully this will come in handy for other people as well. Remember you need Prototype for it to work.

Simple weblogUpdates.ping client in Rails

Almost every Rails app I’ve worked on has had some kind of blog component. Not only are blogs an easy way to keep your site updated, but it also brings in search engine traffic. One of the best ways to get your blog indexed fairly quickly is by using ping services such as Technorati. These services come by after you ping them and read your RSS feed, indexing your content and ultimately getting your site out in front of a lot of people.

Luckily Rails comes with a built in XMLRPC client, so adding in ping functionality to your app is dead easy. Below is a snippet I use regularly. You can change out the listed services to include any of these ping services. I choose Technorati and Google first, and add in any others that might be more specific to the type of site I’m developing.

  def xmlrpc_ping
    services = ["http://rpc.technorati.com/rpc/ping","http://blogsearch.google.com/ping/RPC2"]
    services.each{|service|
      begin
        server = XMLRPC::Client.new2(service)
        server.call2('weblogUpdates.ping',"My Blog",'http://www.myblog.com')
      rescue => detail
        logger.info("ping failed for server #{service} (#{detail})")
      end
    }
  end

You may choose to set your ping service list and ping parameters in your environment file. Or, set it to pull from a database so you or your client can edit them through a browser.

  PING_SERVICES = ["http://rpc.technorati.com/rpc/ping","http://blogsearch.google.com/ping/RPC2"]
  PING_BLOG_NAME = 'My Blog'
  PING_BLOG_URL = 'http://www.myblog.com'

Then rewrite the method:

  def xmlrpc_ping
    PING_SERVICES.each{|service|
      begin
        server = XMLRPC::Client.new2(service)
        server.call2('weblogUpdates.ping',PING_BLOG_NAME,PING_BLOG_URL)
      rescue => detail
        logger.info("ping failed for server #{service} (#{detail})")
      end
    }
  end

Unthirsty 2.0 Underway

Almost a year after launch, I’ve finally started developing version 2.0 of Unthirsty, a happy hour finder using Google Maps.

Since launching Unthirsty, I haven’t done much in way of improving it. I think of it as a way to let it sit and mature, which it has wonderfully. Both Jason Glaspey (my partner in crime) and I have discovered a few things that could be improved, both from regular use and from user feedback. But we’ve also come up with a laundry list of new features we want to add. Overall, the most exciting thing about the new and improved Unthirsty will be developing it with Ruby on Rails.

The most exciting thing about the new and improved Unthirsty will be developing it with Ruby on Rails, which I’ve come to know and love in the past 6 months while working on a few projects both freelance clients and my new employer, Instrument Marketing. Finally taking the dive and learning Ruby has been great, and working in the Rails framework has been a great experience. Creating Unthirsty 2.0 in Rails is going to make the daunting list of new features much easier to tackle. If I continued using PHP I would probably never get it done.

A few things that Ruby and Rails will dramatically help with will be:

Keeping track of data relationships
After creating the class relationship map, I counted at least 16 different relationships between entities in the application. Keeping track of all relationships and validations in PHP would have been a nightmare.
Geocoding
I’ve discovered a Ruby gem Graticule, that works with pretty much all the major geocoding services, to geocode addresses into coordinates. It also has methods to calculate distance, and even a SQL query generator that will find locations within a radius of a given point. Although Unthirsty has all this functionality currently, having this completely abstracted in another library is going to make life so much easier.
AJAX Interface to data
I’ve gained a lot more Javascript experience since I initially worked on Unthirsty, including tons of time into working with Prototype. With Rails Prototype helpers, grabbing data and plotting it on the map from the Unthirsty database will be a lot simpler. Outputting data will be a snap since ActiveRecord objects can be converted to JSON with one method. Currently Unthirsty data comes out and is processed by Javascript in a really convoluted way that is hard to maintain.

There will be a ton of exciting new features and improvements coming with this new version of Unthirsty, but we do know that we’re doing something right already. The trick is to keep that rhythm going. I think we’ll be able to.