简体   繁体   中英

Ruby: Do something when extending an instance with a module

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"

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

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") .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM