I'm just going to leave this here:

If you aren’t interested in Ruby, metaprogramming, or ridiculous things, move swiftly on ;)


So @MattWynne totally nerd-sniped me this evening, by asking this:

Is is possible to dynamically add ActiveModel::Naming compliance to an object at runtime? My meta-fu is letting me down.

As far as I can figure out, it’s not doable, but I can get ‘pretty’ close, here’s what I learned:

ActiveModel::Naming is normally invoked like this:

class Foo
  extend ActiveModel::Naming
end

Which lets you do things like:

Foo.model_name #=> 'Foo'

x = Foo.new
x.class.model_name #=> 'Foo'

So how can we add this functionality at runtime? The obvious way is:

x = Foo.new
x.class.extend(ActiveModel::Naming)

But if we do that, then we may as well have done it initially, as we are modifying the class not just the instance. This will affect all ‘Foo’s in our system, which isn’t really what we want.

We could try to modify the eigenclass of an instance, like so:

require 'active_support/all'
require 'active_model'

class Foo
  def eigen
    class << self
      self
    end
  end
end

x = Foo.new
x.eigen.extend(ActiveModel::Naming)

But when doing a classwise lookup, ruby uses the actual class, not the eigen class:

x.class.model_name #=> wrap2.rb:14:in `<main>': undefined 
                   #   method `model_name' for Foo:Class 
                   # (NoMethodError)

So, we could override Foo#class, to return the eigenclass (now we are getting a bit crazy):

require 'active_support/all'
require 'active_model'

class Foo
  def eigen
    class << self
      self
    end
  end
  def class
    eigen
  end
end

x = Foo.new
x.eigen.extend(ActiveModel::Naming)

But this doesn’t work either, as now ActiveModel::Naming get’s confused as it’s not in a named class:

x.class.model_name #=> Class name cannot be blank. You 
                   #   need to supply a name argument 
                   #   when anonymous class given 
                   #   (ArgumentError)

So the best I can come up with is what is in the file below. This instantiates a wrapper class with the same name (dynamically) as the original class, extends it with ActiveRecord::Naming, and forwards all calls to the original. The one caveat is that our class is now within a module, so the names will have the module name in… I don’t know how this affects form_for, etc