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.