Assuming the following incomplete code...
class Foo
#an_array = []
def method_catcher(this_var)
unless self.method_defined? this_var
if an_array.include?[this_var]
p "Doing something with a fake method as if it were real." << this_var
else
p "You attempted to call Foo with " << this_var << " this class will now self destruct, and you will be returned to entry."
end
end
end
end
How can I fire the method method_catcher on any method attempted on foo instead of it returning a NoMethodError?
Such as if I called
Foo.totally_not_a_declared_method_or_class_variable
so I could get either response depending on my array instead of erroring out?
Use #method_missing:
class Foo
def method_missing(m, *args, &block)
puts "Called method #{m}"
end
end
Foo.new.asd
# Called method asd
Remember to define also respond_to_missing?
Related
could you please explain me why the class variable cannot be accessed by attribute_accessors?
As i am trying here to have the list of all methods of all subclasses in one array it works a little different. It created array #method_names in every subclass with specific methods for every class ... so i do need to do a loop through subclasses.
What kind of variable/attribute is #method_names?
Thanks!
module First
class First_class
class << self
def info
puts "First_class method info."
puts #subclasses
puts #method_names
end
def inherited(subclass)
puts "#{subclass} located ..."
subclasses << subclass
end
def subclasses
#subclasses ||= []
end
def method_added(method_name)
puts "Method located #{method_name} ..."
method_names << method_name
end
def method_names
#method_names ||= []
end
end
def initialize
puts "Instance of First_class is created."
end
def first_method
end
end
class Second_class < First_class
def self.info
puts "Second_class method info."
puts #subclasses
puts #method_names
end
def second_method
end
def initialize
puts "Instance of Second_class is created."
end
end
class Third_class < First_class
def third_method
end
def initialize
puts "Instance of Third_class is created."
end
end
end
First::First_class.subclasses.each {
|subclass| puts subclass
subclass.method_names.each {
|methodn| puts methodn
}
}
#################UPDATE#########
Ok, maybe I put the question incorrectly.
Basically what is the difference for ##method_names(class variable) and #method_names (instance variable) if i do not create the instance of object? After inserting more inputs into #method_names it still inserts into the same object_id. So what is benefit of ##method_names?
updated to answer updated question.
Classes in ruby can have class variables. However if you modify the class level variable, ALL instances will be modified. This is not recommended but will illustrate the point. But also see this answer
class Foo
##bar = 'bar'
attr_accessor :bar
def initialize
#bar = 'bar'
end
def class_bar
##bar
end
def change_class_bar string
raise ArgumentError unless string.is_a?(String)
##bar = string
end
end
a = Foo.new
b = Foo.new
# change the class variable ##bar
b.change_class_bar 'wtf?'
# see both instances are changed because objects are passed by referrence
print 'a.class_bar is: '
puts a.class_bar
print 'b.class_bar is: '
puts b.class_bar
# change one instance only
a.bar = 'only a has changed'
print 'a.bar is: '
puts a.bar
print 'b.bar is still: '
puts b.bar
run this and you should get output:
a.class_bar is: wtf?
b.class_bar is: wtf?
a.bar is: only a has changed
b.bar is still: bar
original answer left here
#method_names is an instance variable of an instance of the class from which it was instantiated. However it cannot be accessed for read/write unless those attributes are defined with getter or setter methods defined.
ff = First::First_class.new
Instance of First_class is created.
=> #<First::First_class:0x00007fde5a6867b8>
ff.method_names
NoMethodError: undefined method `method_names' for #<First::First_class:0x00007fde5a6867b8>
Did you mean? methods
Now if you call ff.methods you will see all methods defined through standard Ruby inheritance.
As a side note, class names in Ruby conventionally use PascalCase see PascalCase. Mixed_case is discouraged.
I'm doing some programming that involves asynchronous callbacks in Ruby, and need to pass the callback method to another object (which may or may not be statically called). The issue I have is the syntax of the callback to the instance - I know it's fairly complicated, but I'm not sure I can make it simpler. Here's what I have:
class OBJA
def self.staticMethod(text, returnCall)
puts "objA.staticMethod: #{text}"
OBJB.send(returnCall, "Call back from objA.staticMethod")
end
def instanceMethod(text, returnCall)
puts "objA.instanceMethod: #{text}"
OBJB.send(returnCall, "Call back from objA.instanceMethod")
end
end
class OBJB
def starterMethod
OBJA.staticMethod("foo", :returnedCall)
OBJA.new.instanceMethod("bar", :returnedCall)
end
def returnedCall(text)
puts text
end
end
You can execute it by doing the following:
b = OBJB.new
b.starterMethod
Thanks!
The issue I have is the syntax of the callback to the instance
You have to call an instance method with an instance. And if you call a class method on a class, e.g. OBJB.send(...), the class method has to be defined.
class OBJA
def self.staticMethod(text, methName)
puts "objA.staticMethod: #{text}"
OBJB.send(methName, "Call back from objA.staticMethod")
end
def instanceMethod(text, methName)
puts "objA.instanceMethod: #{text}"
OBJB.new.send(methName, "Call back from objA.instanceMethod")
end
end
class OBJB
def starterMethod
OBJA.staticMethod("foo", :returnedCall)
OBJA.new.instanceMethod("bar", :returnedCall)
end
def self.returnedCall(text)
puts text
end
def returnedCall(text)
puts text
end
end
b = OBJB.new
b.starterMethod
--output:--
objA.staticMethod: foo
Call back from objA.staticMethod
objA.instanceMethod: bar
Call back from objA.instanceMethod
You could also pass blocks to the OBJA methods:
class OBJA
def self.staticMethod(text, &block)
puts "objA.staticMethod: #{text}"
block.call("Call back from objA.staticMethod")
end
def instanceMethod(text, &block)
puts "objA.instanceMethod: #{text}"
block.call("Call back from objA.instanceMethod")
end
end
class OBJB
def starterMethod
OBJA.staticMethod("foo") {|str| puts str}
OBJA.new.instanceMethod("bar") {|str| puts str}
end
end
b = OBJB.new
b.starterMethod
--output:--
objA.staticMethod: foo
Call back from objA.staticMethod
objA.instanceMethod: bar
Call back from objA.instanceMethod
Or, more illustrative of the closure:
class OBJA
def self.staticMethod(text, &block)
puts "objA.staticMethod: #{text}"
block.call
end
def instanceMethod(text, &block)
puts "objA.instanceMethod: #{text}"
block.call
end
end
class OBJB
def initialize
#x = 1
#y = 2
end
def starterMethod
OBJA.staticMethod("foo") {puts instance_variable_get(:#x)}
OBJA.new.instanceMethod("bar") {puts instance_variable_get(:#y)}
end
end
b = OBJB.new
b.starterMethod
--output:--
objA.staticMethod: foo
1
objA.instanceMethod: bar
2
I don't know if this will help you or not, but this trick is used all over the Ruby frameworks. Ruby being the Wild West of programming languages, it will actually allow you to ignore the closure. That's useful when you want to accept a block from some code, but you don't want to execute the block in the context in which the block was defined--instead you want to execute the block in a context you create.
class OBJA
#x = 10 #Instance variables attach themselves to whatever object is
#y = 20 #self at the time they are created. Inside a class, but outside
#any defs, self is equal to the class, so these statements
#create what are known as 'class instance variables' (##variables
#aren't used in ruby because they don't behave 'correctly').
def self.staticMethod(text, &block)
puts "objA.staticMethod: #{text}"
instance_eval &block #See comment (1) below
end
def instanceMethod(text, &block)
puts "objA.instanceMethod: #{text}"
block.call
end
end
class OBJB
def initialize
#x = 1
#y = 2
end
def starterMethod
OBJA.staticMethod("foo") {puts instance_variable_get(:#x)}
OBJA.new.instanceMethod("bar") {puts instance_variable_get(:#y)}
end
end
b = OBJB.new
b.starterMethod
--output:--
objA.staticMethod: foo
10 #<--CHECK THIS OUT!!
objA.instanceMethod: bar
2
(1) When you call a method without a receiver, ruby uses self to call the method. Inside a class method, self is equal to the class, so the instance_eval() call is equivalent to:
OBJA.instance_eval &block
instance_eval() is used to change the value of the self variable to the receiver. But self was already equal to OBJA?? What instance_eval() succeeds in doing is changing the value of the self variable that it's block sees! By converting the block variable to become instance_eval's block, you actually change the blocks context, i.e. the variables that the block's code sees.
At the moment your callback OBJB.send is called on the class, but your returnedCall method is an instance method. There are two ways to fix this:
Call the callback on an instance instead of on the class by changing lines OBJB.send(... to
OBJB.new.send(...
Or by making the callback method a class method by changing def returnedCall(... to
def self.returnedCall(text)
I have a module that is included in another module, and they both implement the same method.
I would like to stub the method of the included module, something like this:
module M
def foo
:M
end
end
module A
class << self
include M
def foo
super
end
end
end
describe "trying to stub the included method" do
before { allow(M).to receive(:foo).and_return(:bar) }
it "should be stubbed when calling M" do
expect(M.foo).to eq :bar
end
it "should be stubbed when calling A" do
expect(A.foo).to eq :bar
end
end
The first test is passing, but the second one outputs:
Failure/Error: expect(A.foo).to eq :bar
expected: :bar
got: :M
Why isn't the stub working in this case?
Is there a different way to achieve this?
Thanks!
-------------------------------------UPDATE----------------------------------
Thanks! using allow_any_instance_of(M) solved this one.
My next question is - what happens if I use prepend and not include? see the following code:
module M
def foo
super
end
end
module A
class << self
prepend M
def foo
:A
end
end
end
describe "trying to stub the included method" do
before { allow_any_instance_of(M).to receive(:foo).and_return(:bar) }
it "should be stubbed when calling A" do
expect(A.foo).to eq :bar
end
end
This time, using allow_any_instance_of(M) results in an infinite loop. why is that?
Note you cannot directly call M.foo! Your code only seems to work because you mocked M.foo to return :bar.
When you open A metaclass (class << self) to include M, you have to mock any instance of M, that is adding to your before block:
allow_any_instance_of(M).to receive(:foo).and_return(:bar)
module M
def foo
:M
end
end
module A
class << self
include M
def foo
super
end
end
end
describe "trying to stub the included method" do
before do
allow(M).to receive(:foo).and_return(:bar)
allow_any_instance_of(M).to receive(:foo).and_return(:bar)
end
it "should be stubbed when calling M" do
expect(M.foo).to eq :bar
end
it "should be stubbed when calling A" do
expect(A.foo).to eq :bar
end
end
I want to wrap all class methods for a module with logging, like this:
module SomeModule
def self.foo
puts "bar"
end
class << self
SomeModule.methods(false).each do |method|
alias_method "old_#{method}".to_sym, method
define_method method do |*args|
puts "Called method: #{method}"
send "old_#{method}", *args
end
end
end
end
SomeModule.foo
#-> Called method: foo
#-> bar
That works perfectly. But what if I wanted the wrapping to only happen when I called a method? How could I make this happen when you call
module SomeModule
def self.foo
puts "bar"
end
def self.add_logging_to_all_methods
#???
end
end
SomeModule.add_logging_to_all_methods
SomeModule.foo
#-> Called method: foo
#-> bar
I’m not going to ask what you want this for, but here it is:
module SomeModule
def self.foo
puts "bar"
end
def self.add_logging_to_all_methods
eigenclass = class << self; self; end
methods(false).each do |method|
eigenclass.class_eval do
alias_method "old_#{method}".to_sym, method
define_method method do |*args|
puts "Called method: #{method}"
send "old_#{method}", *args
end
end
end
end
end
SomeModule.add_logging_to_all_methods
SomeModule.foo
Be aware that this also adds “logging” to add_logging_to_all_methods, but only after invoking it, so if you only invoke it once, you should not see anything wrong.
What eigenclass means is the “instance” where you add this methods foo and add_logging_to_all_methods. By returning self inside the class << self; end block I’m getting that instance. Then I ask the block to be evaluated in the context of that instance, which does more or less the same as your previous method.
There may be an easier way to do it.
You can apply that on all classes:
ObjectSpace.each_object.select { |o| o.is_a? Class }.each do |klass|
klass.class_eval do
methods(false).each do |method|
alias_method "old_#{method}".to_sym, method
define_method method do |*args|
puts "Called method: #{method}"
send "old_#{method}", *args
end
end
end rescue nil
end
Ah nevermind, just placing the whole class << self block in the method works okay.
When I have the following:
class Foo
def bar
puts "#{__method__} was called and found within #{self}"
end
def method_missing(meth, *args, &blk)
puts "#{meth} was called and was not found within #{self}"
end
end
foo = Foo.new
foo.bar
# => bar was called and found within #<Foo:0x100138a98>
foo.baz
# => baz was called and was not found within #<Foo:0x100138a98>
I assume that when the method is found, the method dispatch looks a bit like so:
foo.bar was asked to be called
Search methods defined within #<Foo:0x100138a98>
Method `bar` found
Call the `bar` method
And for methods not found:
foo.baz was asked to be called
Search methods defined within #<Foo:0x100138a98>
Method `baz` not found
Search methods defined within the parent of #<Foo:0x100138a98>
Method `baz` not found
And so on until it hits the parent that has no parent
Loop back around and see if #<Foo:0x100138a98> has a `method_missing` method defined
Method `method_missing` found
Call the `method_missing` method
I would like to step in like so:
foo.bar was asked to be called
Search methods defined within #<Foo:0x100138a98> to see it has a `method_dispatched` method
Method `method_dispatched` found
Calling `method_dispatched`
Search methods defined within #<Foo:0x100138a98>
...
This would allow developers to do something like below:
class Foo
def bar
puts "#{__method__} was called and found within #{self}"
end
def method_missing(meth, *args, &blk)
puts "#{meth} was called and was not found within #{self}"
end
def method_dispatched(meth, *args, &blk)
puts "#{meth} was called within #{self}..."
puts "continuing with the method dispatch..."
end
end
foo = Foo.new
foo.bar
# => bar was called within #<Foo:0x100138a98>...
# => continuing with the method dispatch...
# => bar was called and found within #<Foo:0x100138a98>
foo.baz
# => bar was called within #<Foo:0x100138a98>...
# => continuing with the method dispatch...
# => baz was called and was not found within #<Foo:0x100138a98>
This brings me to the question..
Is this possible?
I'm not aware of a callback for what you're looking for. I would read up on Ruby Delegators for a possible solution that's more elegant than what I've sketched below.
You can wrap the object and intercept on method_missing.
class A
def foo
puts "hi there, I'm A"
end
end
maybe have B inherit A?
class B
def initialize
#a = A.new
end
def method_missing(m, *args, &block)
puts "hi there, I'm in B"
#a.send(m, *args, &block) if #a.respond_to? m
puts "all done"
end
end
Sort of a Ruby beginner here, but I have some working solution. So please comment on the code if you think something is not ruby-esque.
The idea is to alias the methods that you have and undef_method the original method. This will create a alias for all your instance methods. You can then have a method_missing that can call method_dispatched and then the actual method.
class Test
def foo
"foo"
end
def method_dispatched(meth, *args, &blk)
puts "#{meth} was called within #{self}..."
puts "continuing with the method dispatch..."
end
def method_missing(name, *args, &block)
method_dispatched(name, args, block) #Calls your standard dispatcher
if (respond_to?('_' + name.to_s)) # Check if we have a aliased method
send('_' + name.to_s)
else
"undef" #Let the caller know that we cant handle this.
end
end
instance_methods(false).each do |meth|
if meth != 'method_missing' and meth != 'method_dispatched'
alias_method "_#{meth}", meth
remove_method(meth)
end
end
end
t = Test.new
puts t.foo()
puts t.undefined_method()