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.