Ruby, iOS, and Other Development

A place to share useful code snippets, ideas, and techniques

All code in posted articles shall be considered public domain unless otherwise noted.
Comments remain the property of their authors.

2006-07-20

SymbolicKeyHash

Symbols aren't strings. They aren't even immutable strings. They are symbols. They are also very good keys for hashes, assuming the code building the hash thought so also. It is often the case, however, that hashes received by your code may be keyed by either strings or symbols, or a mixture thereof, and you'd like to be able to handle either case simply and elegantly. The simplest thing to do is to call #extend on the offending hash with the following module:

module SymbolicKeyHash
  def [](key)
    case key
    when Symbol
      include?(key) ? super(key) : super(key.to_s)
    when String
      include?(key) ? super(key) : super(key.to_sym)
    else
      super(key)
    end
  end
end

That's good as far as it goes, but it really isn't nice to mess with the interface of an argument you've been passed. Instead, we rely on a Proxy to wrap it:

def bar(hash)
  hash = Proxy.new(hash)
  hash.extend SymbolicKeyHash
  do_something(hash[:first_thing], hash[:second_thing])
end
Enjoy!

Labels: ,

2006-07-06

Syntax coloring

Someone asked recently about syntax coloring for Ruby code, specifically in the context of a blog. I responded on the list, but I thought I'd share how I create my posts here. First off, I use Firefox when creating a post on Blogger. This is in part because it's a great browser in general, but more specifically so I can use the mozex extension to work on my post in Vim.

Nearly all of my posts include some Ruby code, and it's much nicer to display it with syntax coloring. I used to use Vim's own HTML conversion, but it's slow and it uses explicit styles with colors. Upon hearing about it, I started using the syntax gem in the following Ruby script:

#!/usr/bin/env ruby

unless [1,2].include? ARGV.size
  $stderr.puts "Usage: #{$PROGRAM_NAME} <syntax> [file]"
  exit 1
end

require 'rubygems'
require 'syntax/convertors/html'

convertor = Syntax::Convertors::HTML.for_syntax ARGV.shift

highlighted = convertor.convert(ARGF.read)
highlighted.sub!(/^<pre>/, "<pre class=\"code\">\n")
puts highlighted

To actually write the code I generally open another window in Vim so I can use non-HTML syntax coloring on it and so I can test and debug it in a separate file. When it's ready I paste it into the blog post and run it through the highlighting script above. The actual coloring comes from the page's CSS rules, which you can see by viewing source. If you have any questions on the process, please leave a comment. Enjoy!

Labels: ,

2006-07-02

RoR: Additional Attributes with STI

If you've decided to use ActiveRecord's Single-Table Inheritance (STI) and you want it to be easy for subclasses to have arbitrary additional attributes, you might be tempted to create an Attribute model and do something like this (there are optimizations to be made, but I'm not bothering):

class StiSuperclass < ActiveRecord::Base
  has_many :attributes

  def method_missing(name, *args)
    name = name.to_s
    setter = /=$/ === name
    expected_args = 0
    if setter
      name = name[0...-1]
      expected_args = 1
    end
    unless args.size == expected_args
      err = "wrong number of arguments (#{args.size} for #{expected_args})"
      raise ArgumentError.new(err)
    end
    attribute = attributes.find { |a| a.name == name }
    if setter
      if attribute
        attribute.value = args[0]
      else
        attribute = Attribute.new(:name => name, :value => args[0])
        attributes << attribute
        args[0]
    else
      attribute ? attribute.value : nil
    end
  end
end

class Attribute < ActiveRecord::Base
  belongs_to :sti_superclass
  serialize :value
end

While that can work, it's inefficient both in terms of database usage and execution speed. Since these attributes don't have any real meaning in the database to begin with, it's much simpler and easier to put them in a column of the base table. Consider this alternate design:

class StiSuperclass < ActiveRecord::Base
  serialize :attributes

  def method_missing(name, *args)
    setter = /=$/ === name.to_s
    expected_args = 0
    if setter
      name = name.to_s[0...-1].to_sym
      expected_args = 1
    else
      name = name.to_sym
    end
    unless args.size == expected_args
      err = "wrong number of arguments (#{args.size} for #{expected_args})"
      raise ArgumentError.new(err)
    end
    if setter
      attributes[name] = args[0]
    else
      attributes[name]
    end
  end

  private

  def attributes
    self[:attributes] ||= {}
  end

  def attributes=(hash)
    attributes.replace(hash)
  end

end

We now have no need for an additional table and lots of joins. Better yet, we can make our additional attributes explicit in the subclasses and do away with method_missing with the following:

class StiSuperclass < ActiveRecord::Base
  serialize :attributes

  def self.additional_attribute(*names)
    names.each { |name|
      name = name.to_sym
      define_method(name) { attributes[name] }
      define_method("#{name}=") { |value| attributes[name] = value }
    }
  end

  private

  def attributes
    self[:attributes] ||= {}
  end

  def attributes=(hash)
    attributes.replace(hash)
  end

end

class StiSubclass < StiSuperclass
  additional_attribute :foo, :bar
end

For those of you who are skeptical about the usefulness of arbitrary additional fields, consider it in the context of my current project. I am developing a framework for writing multiplayer card game backends. The Game model cannot (and should not attempt to) take into account the attributes any possible card game will need. Instead it provides a convenient hook for explicitly defining the additional attributes a particular backend will need.

Enjoy!

Labels: , ,