Using anonymous modules and prepend to work with generated code

… and check why 5600+ Rails engineers read also this

Using anonymous modules and prepend to work with generated code

In my previous blog-post about using setters one of the commenter mentioned a case in which the setter methods are created by a gem. How can we overwrite the setters in such situation?

Imagine a gem awesome which gives you Awesome module that you could use in your class to get awesome getter and awesome=(val) setter with an interesting logic. You would use it like that:

class Foo
  extend Awesome
  attribute :awesome
end

f = Foo.new
f.awesome = "hello"
f.awesome
# => "Awesome hello"

and here is a silly Awesome implementation which uses meta programming to generate the methods like some gems do.

Be aware that it is a bit contrived example.

module Awesome
  def attribute(name)
    define_method("#{name}=") do |val|
      instance_variable_set("@#{name}", "Awesome #{val}")
    end
    attr_reader(name)
  end
end

Nothing new here. But here is something that the authors of Awesome forgot. They forgot to strip the val and remove the leading and trailing whitespaces. For example. Or any other thing that the authors of gems forget about because they don’t know about your usecases.

Ideally we would like to do what we normally do:

class Foo
  extend Awesome
  attribute :awesome

  def awesome=(val)
    super(val.strip)
  end
end

But this time we can’t. Because the gem relies on meta-programming and adds setter method directly to our class. We would simply overwrite it.

Foo.new.awesome = "bar"
# => NoMethodError: super: no superclass method `awesome=' for #<Foo:0x000000012ff0e8>

If the gem did not rely on meta programming and followed a simple convention:

module Awesome
  def awesome=(val)
    @awesome = "Awesome #{val}"
  end

  attr_reader :awesome
end

class Foo
  include Awesome

  def awesome=(val)
    super(val.strip)
  end
end

you would be able to achieve it simply. But gems which need the field names to be provided by the programmers don’t have such comfort.

Solution for gem users

Here is what you can do if the gem authors add methods directly to your class:

class Foo
  extend Awesome
  attribute :awesome

  prepend(Module.new do
    def awesome=(val)
      super(val.strip)
    end
  end)
end

Use prepend with anonymous module. That way awesome= setter defined in the module is higher in the hierarchy.

Foo.ancestors
# => [#<Module:0x00000002d0d660>, Foo, Object, Kernel, BasicObject]

Solution for gem authors

You can make the life of users of your gem easier. Instead of directly defining methods in the class, you can include an anonymous module with those methods. With such solution the programmer will be able to use super`.

module Awesome
  def awesome_module
    @awesome_module ||= Module.new().tap{|m| include(m) }
  end

  def attribute(name)
    awesome_module.send(:define_method, "#{name}=") do |val|
      instance_variable_set("@#{name}", "Awesome #{val}")
    end
    awesome_module.send(:attr_reader, name)
  end
end

That way the module, with methods generated using meta-programming techniques, is lower in the hierarchy than the class itself.

Foo.ancestors
# => [Foo, #<Module:0x000000018062a8>, Object, Kernel, BasicObject]

Which makes it possible for the users of your gem to just use old school super

class Foo
  extend Awesome
  attribute :awesome

  def awesome=(val)
    super(val.strip)
  end
end

…without resort to using the prepend trick that I showed.

Summary

That’s it. That’s the entire lesson. If you want more, subscribe to our mailing list below or buy Fearless Refactoring.

You might also like