Trying to get the name of the currently running function in ruby - ruby

I have a function that I would like to call and have it return the name of the function it was called from. Here is the function:
def get_pos
func = __method__.to_s
puts "You are in #{func}"
end
I understand that __method__ returns the name of the method it is currently being executed in.
I am trying to call get_pos() from test and this is the output I want to get:
def test
get_pos
end
You are in test
Instead I get the following
You are in get_pos
I understand why this is happening. Since __method__ is located inside the getpos function it returns the name of that function.
I know that if i make the following change and pass __method__ as an argument to the function, I'll get the expected result. Which is:
def get_pos(method)
puts "You are in #{method}"
end
def test
get_pos(__method__.to_s)
end
You are in test
The code has been simplified but is part of functionality in a logger where I want to be able to dump data about the current location in the code to a log and know exactly what module,class,function I am in.
Is there a better/cleaner way to do this than passing __method__ as a parameter to the function each time?

Why don't you use __callee__ from Kernel object?
I refactored your code:
def current
puts __callee__
end
def test_caller
current
end
test_caller
Which outputs current in this case.
There are all sorts of interesting methods in the Kernel Object. I recommend to take a look to the API here.

You can use caller_locations which returns an array of Thread::Backtrace::Location instances: (starting at index 1 by default, excluding the current method)
def foo
caller_locations.map(&:base_label)
end
def bar
foo
end
def baz
bar
end
baz
#=> ["bar", "baz", "<main>"]
So foo was called from bar which was called from baz which was called in <main>.

Related

Why can't a class method have the same name as a non-class method?

I'm learning ruby, and noticed that I cannot create a class method called puts:
class Printer
def initialize(text="")
#text = text
end
def puts
puts #text
end
end
The error is:
`puts': wrong number of arguments (given 1, expected 0)
My expectation was that I could use the code like this:
p = Printer.new("hello")
p.puts
It's not just because puts is a built-in method, though. For instance, this code also gives a syntax error:
def my_puts(text)
puts text
end
class Printer
def initialize(text="")
#text = text
end
def my_puts
my_puts #name
end
end
tldr; within the scope of the instance, the puts resolves to self.puts (which then resolves to the locally defined method, and not Kernel#puts). This method overriding is a form of shadowing.
Ruby has an 'implicit self' which is the basis for this behavior and is also how the bare puts is resolved - it comes from Kernel, which is mixed into every object.
The Kernel module is included by class Object, so its methods [like Kernel#puts] are available in every Ruby object. These methods are called without a receiver and thus can be called in functional form [such as puts, except when they are overridden].
To call the original same-named method here, the super keyword can be used. However, this doesn't work in the case where X#another_method calls X#puts with arguments when it expects to be calling Kernel#puts. To address that case, see Calling method in parent class from subclass methods in Ruby (either use an alias or instance_method on the appropriate type).
class X
def puts
super "hello!"
end
end
X.new.puts
P.S. The second example should trivially fail, as my_puts clearly does not take any parameters, without any confusion of there being another "puts". Also, it's not a syntax error as it occurs at run-time after any language parsing.
To add to the previous answer (https://stackoverflow.com/a/62268877/13708583), one way to solve this is to create an alias of the original puts which you use in your new puts method.
class Printer
alias_method :original_puts, :puts
attr_reader :text
def initialize(text="")
#text = text
end
def puts
original_puts text
end
end
Printer.new("Hello World").puts
You might be confused from other (static) programming languages in which you can overwrite a method by creating different signatures.
For instance, this will only create one puts method in Ruby (in Java you would have two puts methods (disclaimer: not a Java expert).
def puts(value)
end
def puts
end
If you want to have another method with the same name but accepting different parameters, you need to use optional method parameters like this:
def value(value = "default value")
end

Namespace collision with top-level methods

I have the following Ruby namespace problem:
I have a number of top-level methods in a "library", among these I have a get_name(param) method.
The problem is that I want to use this get_name(param) method inside a class that has its own get_name() method (without param!).
Of course this results in a problem as the 2 methods are different and the new one in the class overrides the top-level one.
Is there a way to do something like this:
def get_name(param)
puts param
end
class Foo
def get_name()
puts "name"
end
def bar()
self.get_name() #should work and print name, i.e. it uses the get_name method of Foo
get_name("abc") #should work and print abc, i.e. it uses the top-level method
end
end
I would like to know if there is an easy way to achieve this without having to alter the name or scope of the top-level methods. (The goal is to have those available without modification as global methods, but still be able to call instance methods of Foo without errors.
If the library methods are really top-level methods and are not part of any class or module, then, they will get defined as private methods of Object class.
You can do following to invoke it:
def bar()
self.get_name()
Object.send(:get_name, "abc") # Will print "abc" to console
end
The so called "top level method" is only a method of main object, which is only an Object. If you want that method to be able to be called (almost) anywhere, you should make it a kernel method.
module Kernel
def get_name(param)
puts param
end
end
Note that Kernel is almost at the tail of any classes ancestors chain (before BasicObject), which means almost all the objects are a Kernel. So in your Foo class, you can override it like this:
class Foo
def get_name(*args)
return super if args.any?
puts 'name'
end
end
Edit
If you can't make those top level methods kernel methods, you can store the main object in a constant or a global variable, and call those top level methods on it.
$main = self
class Foo
def get_name(*args)
return $main.send(:get_name, *args) if args.any?
puts 'name'
end
end
First off: there is no such thing as a "top-level method". There is exactly one kind of method in Ruby: instance methods.
The method you are referring to is a private instance method of Object. Since Object is a superclass of almost any other class (excluding Object and its superclasses), if you are sure that there are no other methods with the same name somewhere else in the inheritance hierarchy, you could use Method#super_method to get access to the method and then call it:
def get_name(param)
puts param
end
class Foo
def get_name
puts 'name'
end
def bar
get_name #should work and print name, i.e. it uses the get_name method of Foo
method(:get_name).super_method.('abc') #should work and print abc, i.e. it uses the top-level method
end
end
Foo.new.bar
# name
# abc
Alternatively, if you don't know whether there are any other methods by the same name within the inheritance hierarchy, you could grab a reference to the UnboundMethod directly from Object and then bind and call it:
class Foo
def bar
get_name #should work and print name, i.e. it uses the get_name method of Foo
Object.instance_method(:get_name).bind(self).('abc') #should work and print abc, i.e. it uses the top-level method
end
end
Foo.new.bar
# name
# abc

Getting file name in ruby where method was called

I have a method foo and it is called in a script script001.rb
how should I write the foo method so that it returns the file name of a script that called it?
To avoid needing to deal with caller style strings, you can use Kernel#caller_locations, instead. It returns you an array of Thread::Backtrace::Location objects, which has some convenient methods available for you.
To get the filename, in your case, you can use the #path method:
def foo
caller_locations.first.path
end
You can use Kernel#caller which returns the current execution stack — an array containing strings in the form file:line or file:line: in 'method':
def foo
caller[0][/[^:]+/] # OR caller[0].split(':')[0]
end
#falsetru's answer is correct, but I thought I'd add this bit of code to demonstrate the different outputs of the methods proposed.
Two files.
hosting.rb
class Hosting
def self.foo
puts "__FILE__: #{__FILE__}"
puts "__method__: #{__method__}"
puts "caller: #{caller}"
puts "caller_locations.first.path: #{caller_locations.first.path}"
end
end
calling.rb
require_relative 'hosting'
Hosting.foo
On: ruby calling.rb the output is:
__FILE__: /path/to/hosting.rb
__method__: foo
caller: ["calling.rb:2:in `<main>'"]
caller_locations.first.path: calling.rb

How do I "fake" C# style attributes in Ruby?

EDIT: I slightly changed the spec, to better match what I imagined this to do.
Well, I don't really want to fake C# attributes, I want to one-up-them and support AOP as well.
Given the program:
class Object
def Object.profile
# magic code here
end
end
class Foo
# This is the fake attribute, it profiles a single method.
profile
def bar(b)
puts b
end
def barbar(b)
puts(b)
end
comment("this really should be fixed")
def snafu(b)
end
end
Foo.new.bar("test")
Foo.new.barbar("test")
puts Foo.get_comment(:snafu)
Desired output:
Foo.bar was called with param: b = "test"
test
Foo.bar call finished, duration was 1ms
test
This really should be fixed
Is there any way to achieve this?
I have a somewhat different approach:
class Object
def self.profile(method_name)
return_value = nil
time = Benchmark.measure do
return_value = yield
end
puts "#{method_name} finished in #{time.real}"
return_value
end
end
require "benchmark"
module Profiler
def method_added(name)
profile_method(name) if #method_profiled
super
end
def profile_method(method_name)
#method_profiled = nil
alias_method "unprofiled_#{method_name}", method_name
class_eval <<-ruby_eval
def #{method_name}(*args, &blk)
name = "\#{self.class}##{method_name}"
msg = "\#{name} was called with \#{args.inspect}"
msg << " and a block" if block_given?
puts msg
Object.profile(name) { unprofiled_#{method_name}(*args, &blk) }
end
ruby_eval
end
def profile
#method_profiled = true
end
end
module Comment
def method_added(name)
comment_method(name) if #method_commented
super
end
def comment_method(method_name)
comment = #method_commented
#method_commented = nil
alias_method "uncommented_#{method_name}", method_name
class_eval <<-ruby_eval
def #{method_name}(*args, &blk)
puts #{comment.inspect}
uncommented_#{method_name}(*args, &blk)
end
ruby_eval
end
def comment(text)
#method_commented = text
end
end
class Foo
extend Profiler
extend Comment
# This is the fake attribute, it profiles a single method.
profile
def bar(b)
puts b
end
def barbar(b)
puts(b)
end
comment("this really should be fixed")
def snafu(b)
end
end
A few points about this solution:
I provided the additional methods via modules which could be extended into new classes as needed. This avoids polluting the global namespace for all modules.
I avoided using alias_method, since module includes allow AOP-style extensions (in this case, for method_added) without the need for aliasing.
I chose to use class_eval rather than define_method to define the new method in order to be able to support methods that take blocks. This also necessitated the use of alias_method.
Because I chose to support blocks, I also added a bit of text to the output in case the method takes a block.
There are ways to get the actual parameter names, which would be closer to your original output, but they don't really fit in a response here. You can check out merb-action-args, where we wrote some code that required getting the actual parameter names. It works in JRuby, Ruby 1.8.x, Ruby 1.9.1 (with a gem), and Ruby 1.9 trunk (natively).
The basic technique here is to store a class instance variable when profile or comment is called, which is then applied when a method is added. As in the previous solution, the method_added hook is used to track when the new method is added, but instead of removing the hook each time, the hook checks for an instance variable. The instance variable is removed after the AOP is applied, so it only applies once. If this same technique was used multiple time, it could be further abstracted.
In general, I tried to stick as close to your "spec" as possible, which is why I included the Object.profile snippet instead of implementing it inline.
Great question. This is my quick attempt at an implementation (I did not try to optimise the code). I took the liberty of adding the profile method to the
Module class. In this way it will be available in every class and module definition. It would be even better
to extract it into a module and mix it into the class Module whenever you need it.
I also didn't know if the point was to make the profile method behave like Ruby's public/protected/private keywords,
but I implemented it like that anyway. All methods defined after calling profile are profiled, until noprofile is called.
class Module
def profile
require "benchmark"
#profiled_methods ||= []
class << self
# Save any original method_added callback.
alias_method :__unprofiling_method_added, :method_added
# Create new callback.
def method_added(method)
# Possible infinite loop if we do not check if we already replaced this method.
unless #profiled_methods.include?(method)
#profiled_methods << method
unbound_method = instance_method(method)
define_method(method) do |*args|
puts "#{self.class}##{method} was called with params #{args.join(", ")}"
bench = Benchmark.measure do
unbound_method.bind(self).call(*args)
end
puts "#{self.class}##{method} finished in %.5fs" % bench.real
end
# Call the original callback too.
__unprofiling_method_added(method)
end
end
end
end
def noprofile # What's the opposite of profile?
class << self
# Remove profiling callback and restore previous one.
alias_method :method_added, :__unprofiling_method_added
end
end
end
You can now use it as follows:
class Foo
def self.method_added(method) # This still works.
puts "Method '#{method}' has been added to '#{self}'."
end
profile
def foo(arg1, arg2, arg3 = nil)
puts "> body of foo"
sleep 1
end
def bar(arg)
puts "> body of bar"
end
noprofile
def baz(arg)
puts "> body of baz"
end
end
Call the methods as you would normally:
foo = Foo.new
foo.foo(1, 2, 3)
foo.bar(2)
foo.baz(3)
And get benchmarked output (and the result of the original method_added callback just to show that it still works):
Method 'foo' has been added to 'Foo'.
Method 'bar' has been added to 'Foo'.
Method 'baz' has been added to 'Foo'.
Foo#foo was called with params 1, 2, 3
> body of foo
Foo#foo finished in 1.00018s
Foo#bar was called with params 2
> body of bar
Foo#bar finished in 0.00016s
> body of baz
One thing to note is that it is impossible to dynamically get the name of the arguments with Ruby meta-programming.
You'd have to parse the original Ruby file, which is certainly possible but a little more complex. See the parse_tree and ruby_parser
gems for details.
A fun improvement would be to be able to define this kind of behaviour with a class method in the Module class. It would be cool to be able to do something like:
class Module
method_wrapper :profile do |*arguments|
# Do something before calling method.
yield *arguments # Call original method.
# Do something afterwards.
end
end
I'll leave this meta-meta-programming exercise for another time. :-)

Ruby access to symbol "invoked by"

I want to (efficiently) get the symbol an aliased method is called with at runtime. A direct efficient access to a stack frame object of some sort to get it would be the fantasy.
ie:
class Foo
def generic_call(*args)
puts("generic_call() was called by using #{???}")
end
alias :specific_call1 :generic_call
alias :specific_call2 :generic_call
end
Foo.new.specific_call1
Foo.new.specific_call2
the result I'd want
generic_call() was called by using specific_call1()
generic_call() was called by using specific_call2()
class Foo
def generic_call()
puts "generic call was called by #{caller[0][/in `([^']+)'/, 1]}"
end
def specific_call1() generic_call end
def specific_call2() generic_call end
end
Foo.new.specific_call2 # Prints: generic call was called by specific_call2
This will however not work if you use alias to create specific_callN from generic_call because methods created by alias are actually a copy of the original method - they don't actually call the original method (which is why you can freely redefine the original without affecting the alias).
A code snippet to get the current method name:
module Kernel
private
# Defined in ruby 1.9
unless defined?(__method__)
def __method__
caller[0] =~ /`([^']*)'/ and $1
end
end
end
There's no built-in way to do this. You can kind of hack it like:
def current_method_name
caller[0].split('`').last.split('\'')[0]
end
Maybe, you want something like this?
class Object
def named_alias(name, generic_name)
([Class, Module].include?(self.class) ? self : self.class).class_eval do
define_method(name) { |*args| send(generic_name, name, *args) }
end
end
end
class Foo
def generic_call(f, *args)
puts("generic_call() was called by using #{f} with #{args}")
end
# def specific_call1(*args)
# generic_call(:specific_call1, *args)
# end
named_alias(:specific_call1, :generic_call)
named_alias(:specific_call2, :generic_call)
end
Foo.new.specific_call1
Foo.new.specific_call2
Disclaimer: I don't know Ruby, I've just Googled how one performs currying there, then adapted the code a bit.

Resources