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
Related
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
Consider the following code in two files:
create_object.rb
def create_object
method = 'new'
Object.send(method)
end
debug.rb
require_relative './create_object'
def foo
object = create_object
bar(object)
end
def bar(object)
# history_of_life - some method that returns object info
puts object.history_of_life
end
foo
Run ruby debug.rb. I expect history_of_life method returns something like this:
<Object:0x007f874b083430> initialized in create_object.rb:3
Are there tools like pry-stacktrace to determine the place in the code where the Object was initialized?
It’s obviously off by default due to enormous footprint, but one might achieve more-or-less desired behaviour with ObjectSpace#allocation_class_path.
In general, ObjectSpace tweaks are your friends in tasks like this one on the top (ruby) abstraction level.
ObjectSpace.allocation_sourcefile returns the source file origin from the given object. Thanks for Aleksei Matiushkin advice.
debug.rb
require 'objspace'
require_relative './create_object'
def foo
object = create_object
bar(object)
end
def bar(object)
puts ObjectSpace.allocation_sourcefile(object)
end
ObjectSpace.trace_object_allocations do
foo
end
output
/home/user/pro/test/create_object.rb
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>.
as far as I understand 'send' method, this
some_object.some_method("im an argument")
is same as this
some_object.send :some_method, "im an argument"
So what is the point using 'send' method?
It can come in handy if you don't know in advance the name of the method, when you're doing metaprogramming for example, you can have the name of the method in a variable and pass it to the send method.
It can also be used to call private methods, although this particular usage is not considered to be a good practice by most Ruby developers.
class Test
private
def my_private_method
puts "Yay"
end
end
t = Test.new
t.my_private_method # Error
t.send :my_private_method #Ok
You can use public_send though to only be able to call public methods.
In addition to Intrepidd's use cases, it is convenient when you want to route different methods on the same receiver and/or arguments. If you have some_object, and want to do different things on it depending on what foo is, then without send, you need to write like:
case foo
when blah_blah then some_object.do_this(*some_arguments)
when whatever then some_object.do_that(*some_arguments)
...
end
but if you have send, you can write
next_method =
case foo
when blah_blah then :do_this
when whatever then :do_that
....
end
some_object.send(next_method, *some_arguments)
or
some_object.send(
case foo
when blah_blah then :do_this
when whatever then :do_that
....
end,
*some_arguments
)
or by using a hash, even this:
NextMethod = {blah_blah: :do_this, whatever: :do_that, ...}
some_object.send(NextMethod[:foo], *some_arguments)
In addition to everyone else's answers, a good use case would be for iterating through methods that contain an incrementing digit.
class Something
def attribute_0
"foo"
end
def attribute_1
"bar"
end
def attribute_2
"baz"
end
end
thing = Something.new
3.times do |x|
puts thing.send("attribute_#{x}")
end
#=> foo
# bar
# baz
This may seem trivial, but it's occasionally helped me keep my Rails code and templates DRY. It's a very specific case, but I think it's a valid one.
The summing briefly up what was already said by colleagues: send method is a syntax sugar for meta-programming. The example below demonstrates the case when native calls to methods are likely impossible:
class Validator
def name
'Mozart'
end
def location
'Salzburg'
end
end
v = Validator.new
'%name% was born in %location%'.gsub (/%(?<mthd>\w+)%/) do
# v.send :"#{Regexp.last_match[:mthd]}"
v.send Regexp.last_match[:mthd].to_sym
end
=> "Mozart was born in Salzburg"
I like this costruction
Object.get_const("Foo").send(:bar)
I have two classes and I want to copy all of the methods from one class to another. Some methods will have no arguments, some will have arguments, and some will have hashes as arguments. And I never know in advance which ones will. So I created this code, until I figured out that it didn't take into account arguments. Is there any way to get a list of methods from a Class, and then clone them exactly to another class?
def partial(cls)
cls.instance_methods(false).each do |method_name|
define_method(method_name) do
cls.new.method(method_name.to_sym).call
end
end
end
The methods are created on the fly using define_method in the first class, so I can't just use an include. The code above has cls being passed in, then it finds all of the instance methods that are actually written in that Class, not ones it inherits, and then creates a new method with the same name. When that method is called, it actually calls the other Class with its method of the same name. This works wonderfully, unless I have args. I had condition check to see if it had arguments, and then had it call a method with arguments, but it did not handle hashes very well. It made the hash as an array for an argument, which is not what I wanted.
I was wondering if there was a simple way to literally say "Hey you know this method, whatever it is, literally make the same thing for this other Class."
you could also try DelegateClass:
class NamedArray < DelegateClass(Array)
def initialize n
#name = n
super(Array.new)
end
def sayName
"My name is #{#name}"
end
end
You could try SimpleDelegator: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/delegate/rdoc/SimpleDelegator.html
If all the methods are identical, why not just define them in a common module which you include in both classes? You mention not using include because the methods are dynamically defined, but that doesn't mean they won't be found when you mixin the module:
module Foo
def self.make_an_example_method(name)
define_method(name) do |*args|
puts "I am #{name} called with (#{args.inspect})"
end
end
end
class A
include Foo
end
class B
include Foo
end
Foo.make_an_example_method(:example)
Foo.make_an_example_method(:dynamic)
A.new.example # => I am example called with ([])
B.new.dynamic(1,2,3) # => I am dynamic called with ([1, 2, 3])