I have a homework that has to accomplish something similar:
module Foo
def self.bar
yield
end
def helper (number)
p number
end
end
Foo.bar do
helper 5
end
Which of course gives an error, because 'helper' is not defined in Object. But in the task, it says straightforward that Foo has to be used this way:
Foo.bar do
helper 5
end
It does not say where 'helper' is defined though. How can I call this method like this above?
Write code as below :
module Foo
def self.bar
yield
end
end
def helper (number)
p number
end
Foo.bar do
helper 5
end
Put the method helper on the top level. Then helper will become a private instance method of the class Object. You are passing a block to the call Foo.bar, and block being a closure, have the access to its surroundings. So helper 5 will called implicitly by main, top level Object class instance. Now the code will work.
Another way is using Module#include method in the top level to include the Foo module to the class Object.
module Foo
def self.bar
yield
end
def helper (number)
p number
end
end
# this will make available `helper` method as an instance method to the Object class.
include Foo
Foo.bar do
helper 5
end
You could use instance_eval to evaluate the block in the context of your object. Something like this:
class Foo
def bar(&block)
instance_eval(&block)
end
def helper(number)
p number
end
end
Foo.new.bar do
helper 5
end
You could also make bar a class method and call:
class Foo
def self.bar(&block)
new.instance_eval(&block)
end
# ...
end
Foo.bar { helper 5 }
Or returning the instance:
class Foo
def self.bar(&block)
new.tap { |foo| foo.instance_eval(&block) }
end
# ...
end
foo = Foo.bar { helper 5 }
Related
I'm currently doing some metaprogramming with ruby, and I'm trying to isolate the methods of class (that class is in another file, that I get by a require). I can get all the methods, thanks to klass.public_instance_methods(false), but I in the sametime, the array given also have all the attributes of the class. How could I isolate them ? In others related questions on SO, they suggest to use klass.instance_variables but when I do that, it only returns an empty array.
I can't seem to wrap my head around that one. I don't understand why there isn't a method specifically for that already...
For example:
I have in a file this class :
class T
attr_reader:a
def initialize(a)
#a = a
end
def meth
#code here
end
end
And, in another file, i have
require_relative 'T.rb'
class meta
def initialize
methods = T.public_instance_methods(false) #=> here methods = [:a,:meth] but I would want only to have [:meth]
#rest of code
end
end
For class defined like this:
class Klass
attr_accessor :variable
def initialize(variable)
#variable = variable
end
def method
end
end
you can find public non-attr instance methods using public_instance_methods and instance_variables methods.
public_instance_methods = Klass.public_instance_methods(false)
# [:method, :variable, :variable=]
instance_variables = Klass.new(nil).instance_variables
# [:#variable]
getters_and_setters = instance_variables
.map(&:to_s)
.map{|v| v[1..-1] }
.flat_map {|v| [v, v + '=']}
.map(&:to_sym)
# [:variable, :variable=]
without_attr = public_instance_methods - getters_and_setters
# [:method]
This is impossible. Ruby's "attributes" are completely normal methods. There is no way to distinguish them from other methods. For example, these two classes are completely indistinguishable:
class Foo
attr_reader :bar
end
class Foo
def bar
#bar
end
end
You can try to be clever and filter them out based on instance variables, but that is dangerous:
class Foo
# can filter this out using #bar
attr_writer :bar
def initialize
#bar = []
end
end
class Foo
def initialize
#bar = []
end
# this looks the same as above, but isn't a normal attribute!
def bar= x
#bar = x.to_a
end
end
I have self-writed gem
module GemNamespace
class Foo; end
class Bar
def foo
#foo ||= Foo.new
end
end
end
Also I have application
module ApplicationNamespace
class Foo < GemNamespace::Foo; end
class Bar < GemNamespace::Bar; end
end
When I call foo method at my application it returned me instanceof GemNamespace object:
bar = ApplicationNamespace::Bar.new
puts bar.foo
=> #<GemNamespace::Foo:0x007f849d8169f0>
But I want get object of ApplicationNamespace how I can do this without redefine foo method
Your Problem is not, that you have several Namespaces, but that GemNamespace::Bar is tightly coupled to GemNamespace::Foo.
You could use something like this:
class Bar
def initialize(klass)
#klass = klass
end
def foo
#foo ||= #klass.new
end
end
So instead of only ever using GemNamespace::Foo within Bar, you could pass any class.
Your current version of the foo method will allways refer to GemNamespace::Foo because its context is set at definition (not at execution). Instead you could get the module of the current executing class dynamically. I don't think there is a build-in method that does this so you have to get it manually:
def foo
#foo ||= self.class.name.split("::")[0..-2].inject(Kernel) { |s, c| s.const_get c }.const_get("Foo").new
end
This will work for any number of nested modules.
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 see how to dynamically add a method to an instance in Ruby with def [instance].[methodname]; [...]; end.
However, I'm interested in attaching a method that exists in another location to a given instance. e.g.
def my_meth
puts self.foo
end
class MyCls
attr_accessor :foo
end
my_obj = MyCls.new
my_obj.my_meth
How could I simply attach my_meth to my_obj so that the method call in the final line of the foregoing code would work?
You could use include or extend to add a module to your class, eg. extend:
module Foo
def my_meth
puts self.foo
end
end
class MyCls
attr_accessor :foo
end
my_obj = MyCls.new
my_obj.extend(Foo)
my_obj.foo = "hello"
my_obj.my_meth
Unless you have a need to mix-in a module on the fly like this it's generally better to include your module like so:
class MyCls
include Foo
attr_accessor :foo
end
What is the super for in this code?
def initialize options = {}, &block
#filter = options.delete(:filter) || 1
super
end
As far as I know it's like calling the function recursively, right?
no... super calls the method of the parent class, if it exists. Also, as #EnabrenTane pointed out, it passes all the arguments to the parent class method as well.
super calls a parent method of the same name, with the same arguments. It's very useful to use for inherited classes.
Here's an example:
class Foo
def baz(str)
p 'parent with ' + str
end
end
class Bar < Foo
def baz(str)
super
p 'child with ' + str
end
end
Bar.new.baz('test') # => 'parent with test' \ 'child with test'
There's no limit to how many times you can call super, so it's possible to use it with multiple inherited classes, like this:
class Foo
def gazonk(str)
p 'parent with ' + str
end
end
class Bar < Foo
def gazonk(str)
super
p 'child with ' + str
end
end
class Baz < Bar
def gazonk(str)
super
p 'grandchild with ' + str
end
end
Baz.new.gazonk('test') # => 'parent with test' \ 'child with test' \ 'grandchild with test'
If there's no parent method of the same name, however, Ruby raises an exception:
class Foo; end
class Bar < Foo
def baz(str)
super
p 'child with ' + str
end
end
Bar.new.baz('test') # => NoMethodError: super: no superclass method ‘baz’
The super keyword can be used to call a method of the same name in the superclass of the class making the call.
It passes all the arguments to parent class method.
super is not same as super()
class Foo
def show
puts "Foo#show"
end
end
class Bar < Foo
def show(text)
super
puts text
end
end
Bar.new.show("Hello Ruby")
ArgumentError: wrong number of arguments (1 for 0)
super(without parentheses) within subclass will call parent method with exactly same arguments that were passed to original method (so super inside Bar#show becomes super("Hello Ruby") and causing error because parent method does not takes any argument)
I know this is late but:
super method calls the parent class method.
for example:
class A
def a
# do stuff for A
end
end
class B < A
def a
# do some stuff specific to B
super
# or use super() if you don't want super to pass on any args that method a might have had
# super/super() can also be called first
# it should be noted that some design patterns call for avoiding this construct
# as it creates a tight coupling between the classes. If you control both
# classes, it's not as big a deal, but if the superclass is outside your control
# it could change, w/o you knowing. This is pretty much composition vs inheritance
end
end
If it is not enough then you can study further from here
Bonus:
module Bar
def self.included base
base.extend ClassMethods
end
module ClassMethods
def bar
"bar in Bar"
end
end
end
class Foo
include Bar
class << self
def bar
super
end
end
end
puts Foo.bar # => "bar in Bar"
Super in a method of a class , say test_method, is used to call another method with same name i.e test_method of a parent class.
The code written above and below the super keyword will be executed normally and the whole bunch of code action of the method of super class will be included at the place of super keyword.