Consider
# sun.rb
class Sunshine
def bright?
return true
end
end
def greeting(greeter)
puts "hello, Sun from #{greeter}"
end
# main.rb
def abc
my_load "sun.rb"
greeting("abc")
return Sunshine.new
end
s = abc
puts s.bright?
greeting("Adrian")
...
Can I have such a my_load here that the greeting("abc") call succeeds, but the latter greeting("Adrian") causes a NoMethodError; but the puts s.bright? call succeeds.
So, synthetically speaking: such that classes,methods from sun.rb are in the scope of the caller of my_load and so that they additionally get garbage collected when not referenced anymore?
First off, a stand-alone (called on the main object) method call will cause a NameError exception if it doesn't exist. You will get a NoMethodError only if you call the method on an object.
nothing #=> NameError
class A; end
A.nothing #=> NoMethodError
This is because when you call nothing on main, it doesn't know if it is a method or a variable. However:
nothing() #=> NoMethodError
Because with the () it now knows it is a method you are trying to call. Just something to watch out for.
Second, if you want a method to work and then not work, use undef.
def greeting(name)
puts "Hello, #{name}"
end
greeting("Chell") #=> "Hello, Chell"
undef greeting
greeting("Chell") #=> NoMethodError
Related
I would like to create an object that accepts method names and prints them out. I should be able to call any method on it. For example,
obj.hello("was")
# => called hello with argument 'was'
obj.ok(["df", 1])
# => called ok with argument ["df", 1]
I don't want to define hello or ok in advance.
Is that possible?
Easy:
class Noop
def method_missing(m, *args)
puts "#{m} #{args.inspect}"
end
end
Noop.new.foo
# => foo []
Noop.new.bar(1,2,3)
# => bar [1, 2, 3]
method_missing is called on every Ruby object when you call a method that does not exist. It usually ends up being handled by Object (the superclass of everything) which raises NoMethodError.
Note that this does not apply to the methods provided by its ancestors (Class, Module, Object, Kernel, BasicObject) which you can inspect by:
class Noop
puts self.instance_methods.inspect
puts self.methods.inspect
def method_missing(m, *args)
puts "#{m} #{args.inspect}"
end
end
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").
I found this neat delegator based 'tee' implementation on SO:
https://stackoverflow.com/a/6410202/2379703
And I'm curious what is means for #targets (instance variable) means in the context of a class method:
require 'logger'
class MultiDelegator
def initialize(*targets)
#targets = targets
end
def self.delegate(*methods)
methods.each do |m|
define_method(m) do |*args|
#targets.map { |t| t.send(m, *args) }
end
end
self
end
class <<self
alias to new
end
end
log_file = File.open("debug.log", "a")
log = Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file)
I get that it defining the methods write/close but #targets isn't even defined at this point since .to (aliased to new) has yet to be called so I'd assume #targets is nil.
Can anyone give an explanation as to the logistics of how this code works? Does ruby not even attempt to access/resolve #targets until the method in question is attempted to be called, which would be by the logger after it was instantiated?
The define_method method is called on a class to create an instance method. Inside that method, the self (and the instance variable) are instances of the class.
For example:
class Foo
#bar = "CLASS"
def initialize
#bar = "INSTANCE"
end
def self.make_method
define_method :whee do
p #bar
end
end
end
begin
Foo.new.whee
rescue NoMethodError=>e
puts e
end
#=> undefined method `whee' for #<Foo:0x007fc0719794b8 #bar="INSTANCE">
Foo.make_method
Foo.new.whee
#=> "INSTANCE"
It is correct that you can ask about instance variables that have never been created, at any time:
class Bar
def who_dat
puts "#dat is #{#dat.inspect}"
end
end
Bar.new.who_dat
#=> dat is nil
The same is true of other aspects of the language. As long as the code in the method is syntactically valid, it may be defined, even if invoking it causes a runtime error:
class Jim
def say_stuff
stuff!
end
end
puts "Good so far!"
#=> Good so far!
j = Jim.new
begin
j.say_stuff
rescue Exception=>e
puts e
end
#=> undefined method `stuff!' for #<Jim:0x007f9c498852d8>
# Let's add the method now, by re-opening the class
class Jim # this is not a new class
def stuff!
puts "Hello, World!"
end
end
j.say_stuff
#=> "Hello, World!"
In the above I define a say_stuff method that is syntactically valid, but that calls a method that does not exist. This is find. The method is created, but not invoked.
Then I try to invoke the method, and it causes an error (which we catch and handle cleanly).
Then I add the stuff! method to the class. Now I can run the say_stuff method (on the same instance as before!) and it works just fine.
This last example shows how defining a method does not run it, or require that it would even work when it is run. It is dynamically evaluated each time it is invoked (and only at that time).
There is a pretty good documentation of the current implementation of refinements in ruby here:
http://ruby-doc.org//core-2.2.0/doc/syntax/refinements_rdoc.html,
but there are some strange corner cases.
First, include module is orthogonal to using module (one include the instance method of module while the other activates the refinement). But there is a trick to include a refinement module itself, see
Better way to turn a ruby class into a module than using refinements?.
def to_module(klass)
Module.new do
#note that we return the refinement module itself here
return refine(klass) {
yield if block_given?
}
end
end
class Base
def foo
"foo"
end
end
class Receiver
include to_module(Base) {
def foo
"refined " + super
end
}
end
Receiver.new.foo #=> "refined foo"
Strangely this refinement module can't be used with using!
m=to_module(Base) {}
m.class #=> Module
using m
#=>TypeError: wrong argument type Class (expected Module)
So using only work on the enclosing module of the refinement modules.
Secondly I wanted to use the above yield trick to be able to pass a Proc to refine (even through it only accepts a block), without resorting to converting the Proc back to source as in
https://www.new-bamboo.co.uk/blog/2014/02/05/refinements-under-the-knife/.
But using yield as in the include example does not work:
def ref_module1(klass)
Module.new do
refine(klass) {
yield
}
end
end
class Receiver1
using ref_module1(Base) {
def foo
"refined " + super
end
}
def bar
Base.new.foo
end
end
Receiver1.new.bar #=> NoMethodError: super: no superclass method `foo'
We see that Receiver1 still use Bar#foo and not the refined method.
Howewer we can use module_eval instead:
def ref_module2(klass,&b)
Module.new do
refine(klass) {
module_eval(&b)
}
end
end
class Receiver2
using ref_module2(Base) {
def foo
"refined " + super
end
}
def bar
Base.new.foo
end
end
Receiver2.new.bar #=> "refined foo"
I don't quite understand why module_eval works here and not the yield method. Inside the refinement block, the 'default_definee' is the refinement module, so module_eval which puts the 'default_definee' to self='the refinement module' should not affect it. And indeed in the 'include' example at the beginning, I get the same result when I use module_eval or a direct yield.
Can anyone explain this behavior?
Context (or binding) is the reason why module_eval works and yield doesn't in your last set of examples. It actually has nothing to do with refinements, as demonstrated below.
Starting with module_eval:
class Foo
def run(&block)
self.class.module_eval(&block)
end
end
foo = Foo.new
foo.run {
def hello
"hello"
end
}
puts foo.hello # => "hello"
puts hello => # '<main>': undefined method 'hello' for main:Object (NameError)
In Foo#run we call module_eval on Foo. This switches the context (self) to be Foo. The result is much like we had simple defined hello inside of class Foo originally.
Now let's take a look at yield:
class Foo
def run
yield
end
end
foo = Foo.new
foo.run {
def hello
"hello"
end
}
puts hello # => "hello"
puts foo.hello # => '<main>': private method 'hello' called for ...
yield simply invokes the block in its original context, which in this example would be <main>. When the block is invoked, the end result is exactly the same as if the method were defined at the top level normally:
class Foo
def run
yield
end
end
foo = Foo.new
def hello
"hello"
end
puts hello # => "hello"
puts foo.hello # => '<main>': private method 'hello' called for ...
You might notice that foo seems to have the hello method in the yield examples. This is a side effect of defining hello as a method at the top level. It turns out that <main> is just an instance of Object, and defining top level methods is really just defining private methods on Object which nearly everything else ends up inheriting. You can see this by opening up irb and running the following:
self # => main
self.class # => Object
def some_method
end
"string".method(:some_method) # => #<Method: String(Object)#some_method>
Now back to your examples.
Here's what happens in the yield example:
def ref_module1(klass)
Module.new do
refine(klass) {
yield
}
end
end
class Receiver1
# like my yield example, this block is going to
# end up being invoked in its original context
using ref_module1(Base) {
def foo
"I'm defined on Receiver1"
end
}
def bar
# calling foo here will simply call the original
# Base#foo method
Base.new.foo
end
end
# as expected, if we call Receiver1#bar
# we get the original Base#foo method
Receiver1.new.bar # => "foo"
# since the block is executed in its original context
# the method gets defined in Receiver1 -- its original context
Receiver1.new.foo # => "I'm defined on Receiver1"
As for module_eval, it works in your examples because it causes the block to be run in the context of the new module, rather than on the Receiver1 class.
I was experimenting with making a DSL and ran across something that confuses me. In my call method I wanted to set an initial value for #mymethod before evaulating the block. It works if I assign to the variable directly:
class Test
class << self
attr_accessor :mymethod
end
def self.call(&block)
#mymethod="foo"
class_eval &block
end
end
Test.call do
puts "mymethod returned: #{mymethod}"
mymethod = "bar"
puts "mymethod is now: #{mymethod}"
end
Which returns:
[1] pry(main)> load 'test.rb'
mymethod returned: foo
mymethod is now: bar
=> true
But I feel like this should work and it doesn't. The only thing that has changed is the # has been removed from the assignment to mymethod so I think it should be using the mymethod= method created by attr_accessor:
class Test
class << self
attr_accessor :mymethod
end
def self.call(&block)
mymethod="foo"
class_eval &block
end
end
Test.call do
puts "mymethod returned: #{mymethod}"
mymethod = "bar"
puts "mymethod is now: #{mymethod}"
end
However the assignment to mymethod from within call fails while the same assignment inside the block succeeds:
[1] pry(main)> load 'test.rb'
mymethod returned:
mymethod is now: bar
=> true
What's going on here? Can someone explain to my why the assignment would fail inside the call method?
in your case, mymethod="foo" will define mymethod local variable
rather than call mymethod= method.
use self.mymethod="foo" instead