I was looking at the Nokogiri source code, and found this:
module Nokogiri
class << self
def XML # some args
# some code
end
end
module XML
# more code
end
end
How come the names don't conflict? How does it know what I'm referring to when then using Nokogiri::XML? Is this best practice? If so, is this just a way to get default module entry-points as is the case here?
xml = Nokogiri::XML(open("www.foo.com/bar.xml"))
They are really look very similar:
module Noko
class << self
def XML # some args
'method'
end
end
module XML
end
end
Noko::XML #access to a module
=> Noko::XML
Noko.XML # access to class method
=> "method"
Noko::XML() #access to a class method too
=> "method"
So, we can say, that without any params this expression behaves like a module, with params - like a method.
More about scope resolution operator in Ruby you can see in this SO question.
Related
I'm playing with some of the very basics of ruby mixins, and for some reason can't access behavior from my module.
Running this on Ruby Fiddle:
module Cats
MEOW = "meow meow meow"
def Cats.meow?
return Cats::MEOW
end
end
class Example
include Cats
def sample
return "it's a sample"
end
end
e = Example.new
puts e.sample
puts e.meow?
This keeps returning NoMethodError: undefined method 'meow?' for #
My understanding of how mixins should work from tutorialspoint makes me feel like I should be able to validly call e.meow?, and get back the same result I would get from calling Cats.meow?.
Here's the code in RubyFiddle.
Incredibly basic, but any ideas where I'm falling down here?
As it turns out, I was being overly specific when defining Cats.meow?. If you want to use a module as a mixin you'll want to define your methods more generally, not with respect to their specific module namespace.
So instead of
def Cats.meow?
...
end
it should have been
def meow?
...
end
This lets you call e.meow?, since the method definition no longer limits it just to the Cats namespace.
Whoops.
As a general rule to using include and extend in Ruby:
If you want to use your module as a namespace
module Outer
module Inner
def self.my_method
"namespaced method!"
end
end
end
You use it like this Outer::Inner::my_method or Outer::Inner.my_method.
And if you want to use the module as a mixin:
# In some cases it makes sense to use names ending in -able, since it expreses
# what kind of messages you can send to an instance or class that mixes
# this module in.
# Like Devise's Recoverable module: https://github.com/plataformatec/devise/blob/f39c6fd92774cb66f96f546d8d5e8281542b4e78/lib/devise/models/recoverable.rb#L24
module Fooable
def foo
"#{self} has been foo'ed!"
end
end
Then you can include it (instances of Something obtain #foo):
class Something
include Fooable # Now Something.new can receive the #foo message.
end
Something.new.foo
=> "#<Something:0x0055c2dc104650> has been foo'ed!"
Or you can extend it (Something itself obtains #foo as a class message):
class Something
extend Fooable # Now Something can receive the #foo message.
end
Something.foo
=> "Something has been foo'ed!"
I understand that Nokogiri is a module and Nokogiri::HTML is a namespaced module:
Nokogiri.class # => Module
Nokogiri::HTML.class # => Module
I understand code like Nokogiri::HTML.fragment(string); fragment is a class method defined in the HTML module (or an 'instance method' if extend self is used)
I don't understand how I can pass an argument to a module like Nokogiri::HTML('string').
I saw that :: not only can be used to access a namespaced class/model, but also to access a class method. In Nokogiri::HTML('string'), HTML is a class method in the Nokogori module, but I couldn't find this in the source code.
HTML is a class (singleton) method of Nokogiri module (you are right, you can use :: instead of .). Source code is here:
module Nokogiri
class << self
###
# Parse HTML. Convenience method for Nokogiri::HTML::Document.parse
def HTML thing, url = nil, encoding = nil, options = XML::ParseOptions::DEFAULT_HTML, &block
Nokogiri::HTML::Document.parse(thing, url, encoding, options, &block)
end
end
#...
end
You can simply check where is your method located by using Method#source_location, e.g:
Nokogiri.method(:HTML).source_location
#=> ["/Users/you/.rvm/gems/ruby-2.3.0#gemset/gems/nokogiri-1.6.8.1/lib/nokogiri/html.rb", 14]
I'm used to extending classes to provide my own encapsulated functionnalities on top of a standard class.
As I'm building a more complete object than a basic URI, I have the option to just reopen the module and add my own class or instance method.
Though it could work, I don't think it is the most elegant way for that.
Let's say I'd like to call it Location, and behave like URI but with some extra class/instance methods. Let's take a basic example, a name method:
require 'uri'
PATH = 'http://example.com/dir1/dir2/file.txt'
module Location
extend URI
class Generic
attr_reader :dummy
def name
File.basename(#path)
end
end
end
e = URI.parse PATH
puts e.path
f = Location.parse PATH
puts f.path
puts f.name
puts f.dummy = "doh"
Here, the problem is that URI module's methods like parse are not available on my now Location module:
$ undefined method `parse' for Location:Module (NoMethodError)
Same error using include URI or extend URI
What is the correct way to extend URI module the way I described above, and to add instance variables (like dummy, here) along existing ones to add functionality ?
Thanks for any tips and advice.
URI.parse is defined as a class method on a module. when you include/extend a module, only its instance methods are copied over. If you use include, they're copied over as instance methods and with extend, as class methods.
That being said, with some metaprogramming it's possible to write a method that copies class methods from one module to another:
# This returns a list of symbols (all the added methods)
def copy_class_methods(from_klass, to_klass)
from_klass.methods(false).each do |method_name|
method_obj = from_klass.method(method_name)
to_klass.define_singleton_method(method_name, &method_obj)
end
end
Then, for example:
module Location
copy_class_methods(URI, self)
end
Location.parse("http://example.com/dir1/dir2/file.txt")
# => #<URI::HTTP http://example.com/dir1/dir2/file.txt>
I'm trying to figure out how to get the name of the class that called a module function in a plugin-based application of mine.
caller seems to give me a file/line number, which is workable, but seems a bit hacky and not idiomatic.
Example code:
module AwesomeModule
def self.get_caller
puts #some unknown code here
end
end
class AwesomeClass
def initialize
AwesomeModule::get_caller
end
end
a = AwesomeClass.new # ideal return => "AwesomeClass"
You typically use ruby modules by including them. Try this:
module AwesomeModule
def get_caller
self.class
end
end
class AwesomeClass
include AwesomeModule
def initialize
get_caller
end
end
a = AwesomeClass.new # "AwesomeClass"
Also, note that in your question get_caller is being called on the AwesomeModule module itself, further complicating the issue.
I have a module MyModule. I dynamically load classes into it. How can I get a list of the classes defined within its namespace?
Example:
def load_plugins
Dir.glob(File.dirname(__FILE__) + '/plugins/*.rb') do |f|
MyModule.class_eval File.read(f)
end
# now how can I find the new classes I've loaded into MyModule?
end
I should say that each f contains something like "class Foo; end".
You can also think of it like this: in Rails, how could I programatically find all classes defined within the ActiveRecord module?
Classes are accessed through constants. Classes defined within a module are listed as constants in that module. So you just need to choose the constants that refer to classes.
MyModule.constants.select {|c| MyModule.const_get(c).is_a? Class}
If you're on rails, you need to access the constants first for them to show up, because they're lazyly loaded.
MyModule::NotAClass = "not a class"
MyModule.constants => [:NotAClass]
MyModule::AClass => :AClass # Access class for it to be autoloaded
MyModule.constants => [:AClass, :NotAClass]
# Now, select the constants which are class instances
MyModule.constants
.map(&MyModule.method(:const_get))
.select { |constant| constant.is_a? Class}
=> [MyModule::AClass]**
If you'd like to get all classes in a module recursively, you can do something like
def get_classes(mod)
mod.constants.map do |c|
const = mod.const_get(c)
if const.is_a? Class
const
elsif const.is_a? Module
get_classes(const)
else
next
end
end.flatten
end
Then, given some module structure like:
module MyModule
module Submodule1
class Class1
end
end
module Submodule2
class Class2
end
end
end
the output looks like:
puts get_classes(MyModule)
# => MyModule::Submodule1::Class1
# => MyModule::Submodule2::Class2