Reputation: 1839
I'd like to execute some code when an instance is extended with Object#extend. A bit like initialize
when instantiating a class but for a module.
Here is the extended documentation example:
module Mod
def hello
"Hello from Mod.\n"
end
end
class GoodKlass
def hello
"Hello from GoodKlass.\n"
end
end
class BadKlass
# something totally different
end
good = GoodKlass.new
good.hello #=> "Hello from GoodKlass.\n"
good.extend(Mod) #=> #<GoodKlass:0x401b3bc8>
good.hello #=> "Hello from Mod.\n"
For example I'd like to display a warning or raise if Mod
is used to extend
something else than an instance of GoodKlass
:
bad = BadKlass.new
bad.extend(Mod) #=> raise "Mod cannot extend BadKlass"
Upvotes: 1
Views: 332
Reputation: 110665
Your comment on the question, replying to my comment, confirmed a suspicion I had. What you have done is not to extend the class, but to extend a particular instance of the class. Let's see what your code does.
good = GoodKlass.new
good.hello #=> "Hello from GoodKlass.\n"
GoodKlass.hello #=> NoMethodError: undefined method `hello' for GoodKlass:Class
good.extend(Mod)
GoodKlass.hello #=> NoMethodError: undefined method `hello' for GoodKlass:Class
good.hello #=> "Hello from Mod.\n"
very_good = GoodKlass.new
very_good.hello #=> "Hello from GoodKlass.\n"
As you see, hello
is only defined on the instance good
.
Note
GoodKlass.methods.include?(:hello) #=> false
good.methods.include?(:hello) #=> true
If that's not what you want, there are two possibilities. I reference
class VeryGoodKlass
end
in discussing both.
1. Extend
the class
In your application (ref comments on the question), this approach would allow you to a create a class method File::jpg?
which would be invoked File.jpeg?("cat.jpg")
.
To convert Mod
's instance method hello
to a class method of GoodKlass
you need to extend the class (not an instance of the class), using Object#extend. To prevent other classes from extending the module, use the callback method Module#extended in the module.
module Mod
def self.extended(base)
raise ArgumentError, "Cannot extend #{base}" unless base == GoodKlass
end
def hello
"Hello from Mod"
end
end
class GoodKlass
def self.hello
"Hello from GoodKlass"
end
end
GoodKlass.hello #=> "Hello from GoodKlass"
GoodKlass.extend(Mod)
GoodKlass.hello #=> "Hello from Mod"
VeryGoodKlass.extend(Mod) #=> ArgumentError: Cannot extend VeryGoodKlass
2. Include the module in the class
To add Mod
's instance methods to GoodKlass
(keeping them instance methods) you need to include the module, using Module#include. To prevent other classes from including the module, use the callback method #included in the module.
In your application this would allow you to write an instance method File#jpg?
1 used as follows:
f = File.new('cat.jpg')
f.jpg?
You could do that as follows.
module Mod
def self.included(base)
raise ArgumentError, "Cannot include #{base}" unless base == GoodKlass
end
def hello
"Hello"
end
end
class GoodKlass
end
good = GoodKlass.new
GoodKlass.include(Mod)
GoodKlass.hello #=> NoMethodError: undefined method `hello' for GoodKlass:Class
good.hello #=> "Hello"
VeryGoodKlass.include(Mod) #=> ArgumentError: Cannot include VeryGoodKlass
1. Perhaps File.basename(f.path).end_with?(".jpg")
.
Upvotes: 1
Reputation: 8100
You can define self.extended in the module:
module Mod
def self.extended(base)
raise "Cannot extend #{base}" unless base.is_a?(GoodKlass)
end
def hello
"Hello from Mod.\n"
end
end
Upvotes: 3