I believe in Ruby, there is a way to access the name of all local variables within a block.
def some_method(param1, param2)
p local_variables
end
whenever 'some_method' is called, param1, and param2 will be printed out. Not the value! but the variable names.
Now, I would like to achieve the same result but within the self.method_added.
Whenever a method is defined, self.method_added is called. I want to be able to access the names of the local variables of the method being defined inside self.method_added. For example,
def self.method_added(method_name)
#prints the variables names of the argument for method method_name
end
def do_something param1, param2
#crazy stuff
end
with the code above, when do_something is created, I would like to have access to the variable name 'param1' and 'param2'
Is there a way to do this?
def self.method_added(method_name)
p self.instance_method(method_name.to_sym).parameters.collect{|g| g[1]}
end
Depending on your ruby version you might consider ruby2ruby.
See: http://www.slideshare.net/marc_chung/RubyConfRubyDosRuby
It allows you to get an AST (abstract syntax tree) of your code. But last time I checked it worked only with 1.8.
Related
I'm writing a DSL for a project and I've run into an issue involving local variables bleeding into nested procs when I don't want them to. It seems like no matter what I try, I can't overwrite a local variable's value with something from another scope once it's been set into a proc's binding.
an example of what I'm trying to get to work:
class Obj
def foo
:foo
end
end
def run_proc(context, &block)
context.instance_exec(&block)
end
def run_example(context)
# This `foo` the local var that ends up in the binding of
# the proc on the next line that I can't seem to overwrite
foo = :bar
run_proc(context) { foo }
# ^ I want to be able to eval `foo` based on the passed context obj
end
obj = Obj.new
# I want all three of these calls to return `:foo`
obj.foo #=> :foo # works as expected
obj.instance_exec { foo } #=> :foo # works as expected
run_example(obj) #=> :bar # doesn't work, since the `run_example`
# method's local `foo` var takes precedence
# over the passed object's `foo` method
I did some digging around and found answers with a similar approach to what I've been trying: Change the binding of a Proc in Ruby. I've also looked into the possibility of undefining the local variables in a proc's binding but this answer claims doing so is impossible: Undefine variable in Ruby.
So my question now is this: Should I just give up trying to nest procs and/or find a workaround that doesn't have local var/method name conflict issues, or is there actually a way to get around this?
You can use self.foo explicitely
Using it will call the bindings of the object self currently refers to rather then the closure of the state when proc/block is created.
Is there a way to bind an existing method to an existing instance of an object if both the method and the instance are passed as symbols into a method that does that if the instance is not a symbol?
For example:
def some_method
#do something
end
some_instance = Klass.new(something)
def method_that_binds(:some_method, to: :some_instance)
#how do I do that?
end
Your requirements are a little unusual, but it is possible to do this mostly as you say:
class Person; end
harry = Person.new
barry = Person.new
def test
puts 'It works!'
end
define_method :method_that_binds do |a_method, to|
eval(to[:to].to_s).singleton_class.send(:define_method, a_method, &Object.new.method(a_method))
end
method_that_binds :test, to: :harry
harry.test
# It works! will be sent to STDOUT
barry.test
# undefined method 'test'
This doesn't actually use a named parameter, but accepts a hash with a to key, but you can see you can call it in the way you want. It also assumes that the methods you are defining are defined globally on Object.
The API you want doesn't easily work, because you have to know from which scope you want to access the local variable. It's not quite clear to me why you want to pass the name of the local variable instead of passing the content of the local variable … after all, the local variable is present at the call site.
Anyway, if you pass in the scope in addition to the name, this can be accomplished rather easily:
def some_method(*args)
puts args
puts "I can access some_instance's ivar: ##private_instance_var"
end
class Foo; def initialize; #private_instance_var = :foo end end
some_instance = Foo.new
def method_that_binds(meth, to:, within:, with: [])
self.class.instance_method(meth).bind(within.local_variable_get(to)).(*with)
end
method_that_binds(:some_method, to: :some_instance, within: binding, with: ['arg1', 'arg2'])
# arg1
# arg2
# I can access some_instance's ivar: foo
As you can see, I also added a way to pass arguments to the method. Without that extension, it becomes even simpler:
def method_that_binds(meth, to:, within:)
self.class.instance_method(meth).bind(within.local_variable_get(to)).()
end
But you have to pass the scope (Binding) into the method.
If you'd like to add a method just to some_instance i.e. it's not available on other instances of Klass then this can be done using define_singleton_method (documentation here.)
some_instance.define_singleton_method(:some_method, method(:some_method))
Here the first use of the symbol :some_method is the name you'd like the method to have on some_instance and the second use as a parameter to method is creating a Method object from your existing method.
If you'd like to use the same name as the existing method you could wrap this in your own method like:
def add_method(obj, name)
obj.define_singleton_method(name, method(name))
end
Let's say we have a class A with a method a and a local variable c.
class A
def a; 10 end
end
c = '5'
And we want to add the method A#a to c.
This is how it can be done
c.singleton_class.send :define_method, :b, &A.new.method(:a)
p c.b # => 10
Explanations.
One way to add a method to an object instance and not to its class is to define it in its singleton class (which every ruby object has).
We can get the c's singleton class by calling the corresponding method c.signleton_class.
Next we need to dynamically define a method in its class and this can usually be accomplished by using the define_method which takes a method name as its first argument (in our case :b) and a block. Now, converting the method into a block might look a bit tricky but the idea is relatively simple: we first transform the method into a Method instance by calling the Object#method and then by putting the & before A.new.method(:a) we tell the interpreter to call the to_proc method on our object (as our returned object is an instance of the Method, the Method#to_proc will be called) and after that the returned proc will be translated into a block that the define_method expects as its second argument.
Is there a way to call check defined? on a dynamic instance variable name? What I want is to be able to do something like this:
dynamic_attr = 'foo'
define_method :my_method do
if defined? instance_variable_get("##{dynamic_attr}") # Obviously fails, but you see what I'm getting at...
....
end
end
Of course this fails because defined? is a special form that is looking at literals and thus returns "method" in this case because it's looking at the actual instance_variable_get method. And of course instance_variable_get("#foo") is going to return nil if I ever called #foo = nil so it's indistinguishable from having never been set. Is there any way I can see if #foo was defined with the same semantics as defined? but in a dynamic way?
Strange that nobody mentioned that. There is also Object#instance_variable_defined?.
dynamic_attr = 'foo'
define_method :my_method do
if instance_variable_defined?("##{dynamic_attr}")
....
end
end
If I did get your question, you'd need to check whether an instance variable is defined. Object#instance_variable_get retrieves a value of an existing variable or nil if not exists, not the instance variable name.
You can check the definition of dynamically created variable name by its membership to all existing like:
dynamic_attr = 'foo'
define_method :my_method do
if instance_variables.member? "##{dynamic_attr}".to_sym
....
end
end
See array returned with Object#instance_variables contains symbols so you need to convert an argument with String#to_sym to compare apples with apples.
I know that I can capture the moment of a method definition by using set_trace_func.
set_trace_func ->event, file, line, method, binding, klass{
if event == "c-call" and method == :method_added
# The moment of method definition
end
}
Is it possible to capture the name of the method being defined at such moment? I know that the class can be captured by eval("self", binding). What code can I put inside the block shown above to capture the method name?
Is it further possible to get the format of the arguments for the method being defined (the required arguments, the rest of the arguments, and their names as is in the source)?
Outside of set_trace_func, you could use Module.method_added:
class Test
def self.method_added(method_name)
puts "#{method_name} added to #{self}"
end
def foo
"foo"
end
end
$ ruby test.rb
# => foo added to Test
Check the documentation.
The Kernel.set_trace_func proc allows you catch an id parameter. This—most times—is the function name.
However, learning from your example, you can also get the current running method using eval("__method__", binding) …but I think this only gets the methods you have defined in your classes.
I am curious if as to whether or not it would be possible to loop through the instance variables of an object and dump out some basic debug information.
I know you can get a list of instance variables by doing object.instance_variables which returns an array of symbolized variables like [:#var1, :#var2, :#etc] My first guess at how to do this was:
obj.instance_variables.each do
obj.instance_variable_get(var).to_yaml
end
but i am getting the following error: "can't dump anonymous class Class". What might a better approach be?
The problem is you have some anonymous proc or function in your instance variables that doesn't respond to to_yaml. Because it can't be converted to yaml you are getting this error. Try using inspect instead, all objects should respond to inspect:
obj.instance_variables.each do |var|
p obj.instance_variable_get(var).inspect
end
You have to take into account that in ruby just declaring the attr_accessor will not create the variable, you need to assign it:
class A
attr_accessor :x, :y
def initialize(z)
#x=z
end
end
def inspect_object(o)
o.instance_variables.each do |var|
var.slice!(0)
p var
p o.send(var)
end
end
a = A.new(5)
inspect_object(a)
This outputs
"x"
5