Archive for

November 2007

Copying Classes

From across the desk, George asks “can you copy classes in Ruby?”. We talk about it quickly and reason that since everything’s an Object (even classes), you probably can. Since the constant isn’t changed or duplicated (you’re essentially assigning a new one) then it ought to be possible.

Turns out it is!

class First  def initialize    @value = 99  end  def say_value    @value  endendFirst.new.say_value # => 99
Second = First.cloneSecond.class_eval do  define_method :say_value do    @value + 100  endendSecond.new.say_value # => 199

Neat.

I’m not sure quite why you would want to clone a class to take advantage of re-use - rather than extract to a module (and share the implementation that way) or, if there’s a strong relationship that doesn’t violate the LSP etc. then look for some kind of inheritance-based design.

But, I guess you could work some kind of cool ultra-dynamic super-meta system from it. Perhaps someone with way more of a Ruby-thinking brain than me could offer some thoughts?

Posted

Watch out for the Monkey Patch

The project I’m currently working on uses both the Asset Packager and Distributed Assets to ensure we have only a few external assets, and that we can load assets across more than one host - all so that the pages for our site load nice and quick.

Unfortunately, wiring in the Asset Packager plugin caused the Distributed Assets plugin to break, and I spent an hour or two tracking it down yesterday. The cause? Asset Packager redefines the compute_public_path method.

# rewrite compute_public_path to allow us to not include the query string timestamp    # used by ActionView::Helpers::AssetTagHelper    def compute_public_path(source, dir, ext=nil, add_asset_id=true)      source = source.dup      source << ".#{ext}" if File.extname(source).blank? && ext      unless source =~ %r{^[-a-z]+://}        source = "/#{dir}/#{source}" unless source[0] == ?/        asset_id = rails_asset_id(source)        source << '?' + asset_id if defined?(RAILS_ROOT) and add_asset_id and not asset_id.blank?        source = "#{ActionController::Base.asset_host}#{@controller.request.relative_url_root}#{source}"      end      source    end

Distributed Assets works by chaining compute_public_path - decorating the calculated path, adding the asset host prefix onto the url. But, Asset Packager works by defining the method into ActionView::Base. So, when DistributedAssets::AssetTagHelper is included with ActionView::Helpers::AssetTagHelper, it chains a (now) hidden method.

But, the only places that use the new compute_public_path code inside the Asset Packager Helper (which just avoids using the query string timestamp) is within Asset Packager itself.

So, I tweaked the implementation of AssetPackageHelper to

def compute_public_path_for_packager(source, dir, ext, add_asset_id=true)  path = compute_public_path(source, dir, ext)  return path if add_asset_id  path.gsub(/\?\d+$/, '')enddef javascript_path(source)  compute_public_path_for_packager(source, 'javascripts', 'js', false)       end

Beware the monkey patch.

Posted

Meta-programming with instance_exec

Rails makes heavy use of a declarative style around it’s codebase- for example the has_one and belongs_to declarations inside ActiveRecord (amongst others). These are just class methods defined on modules, letting Rails wire up relationships, but they read like fully-fledged statements within a mini-language:

class Student < ActiveRecord::Base  has_many :tutorials

We can take advantage of the same in our code- letting us write in a declarative-style (hopefully revealing stronger intent) and reduce the amount of code we write (by using declarations to meta-program for us). I posted a little while ago that we’d used such an approach for marking attributes as immutable.

Anyway, back to the title of the post- #instance_exec, it’s a method defined in Ruby Facets. Like it’s documentation says, it’s equivalent to instance_eval but also lets you pass parameters- roll on the meta magic.

Let’s say we’re writing a system to calculate the monthly salary payment for an employee. We want to be able to say what the payment is rather than how it’s calculated.

class FullTimeEmployee  include Employee  bill_at {|hours| 50.pounds_sterling * hours}

In the snippet above, we’re making a declaration - defining the relationship between an hourly rate and the number of hours the employee works. We can implement bill_at in Employee as follows:

module Employee  module ClassMethods    def bill_at &block      define_method(:calculate_bill) do |hours|        instance_exec hours, &block      end  ...

Our assertion could be:

assert_equal 500.pounds_sterling, employee.calculate_bill(5.hours)

Posted
Fork me on GitHub