Reputation: 617
I'm trying to to define a class called "HTML" to extend Nokogiri - which uses modules.
I tried the following:
require 'nokogiri'
class HTML
include Nokogiri
end
and
require 'nokogiri'
class HTML
extend Nokogiri
end
but so far it's been impossible that the class HTML inherit of all the functions in nokogiri. So stuff like are not working:
doc = HTML.new
doc.HTML(open('http://www.google.com/search?q=tenderlove'))
undefined method `HTML' for # (NoMethodError)
Does anyone know how I could manage to program a class that inherits of all the methods of a module?
Upvotes: 1
Views: 3123
Reputation: 89133
Nokogiri doesn't have any instance methods to inherit:
irb> Nokogiri.instance_methods
#=> []
But normally, you would use extend
% ri extend ---------------------------------------------------------- Object#extend obj.extend(module, ...) => obj ------------------------------------------------------------------------ Adds to obj the instance methods from each module given as a parameter. module Mod def hello "Hello from Mod.\n" end end class Klass def hello "Hello from Klass.\n" end end k = Klass.new k.hello #=> "Hello from Klass.\n" k.extend(Mod) #=> #<Klass:0x401b3bc8> k.hello #=> "Hello from Mod.\n" %
What you want to do is use all the class methods of the Nokogiri module as instance methods of your class. Which is a bit nonstandard, which is why the syntax doesn't support it. Most programmers use ruby modules for the Singleton pattern - there only needs to be one Nokogiri, so other things shouldn't be able to use its methods.
You could do some hacking with UndefinedMethods to get around this, but considering that Nokogiri has some compiled code in the backend, this may produce undefined bugs.
Which isn't to say you can't forward calls to Nokogiri:
# nokogiri_wrapper.rb
require 'rubygems'
require 'nokogiri'
class NokogiriWrapper
def method_missing(meth, *args, &blk)
puts "call for #{meth.inspect}, #{args}, #{blk ? "with block" : "and no block"}"
if Nokogiri.methods.include? meth.to_s
puts "forwarding to Nokogiri"
Nokogiri.send(meth, *args, &blk)
else
puts "falling back to default behaviour"
super
end
end
end
html = "<html></html>"
puts "calling Nokogiri directly"
p Nokogiri.HTML(html)
wrapper = NokogiriWrapper.new
puts "calling Nokogiri through wrapper"
p wrapper.HTML(html)
puts "calling non-Nokogiri method with wrapper"
p(begin
wrapper.scooby_dooby_doo!
rescue NoMethodError => e
[e.message, e.backtrace]
end)
% ruby nokogiri_wrapper.rb calling Nokogiri directly <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html></html> calling Nokogiri through wrapper call for :HTML, <html></html>, and no block forwarding to Nokogiri <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html></html> calling non-Nokogiri method with wrapper call for :scooby_dooby_doo!, , and no block falling back to default behaviour ["undefined method `scooby_dooby_doo!' for #<NokogiriWrapper:0x581f74>", ["nokogiri_wrapper.rb:12:in `method_missing'", "nokogiri_wrapper.rb:29"]]
This is one way to implement the delegator pattern in ruby (another way is to use one of the Delegator classes).
Upvotes: 10