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
Related
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.
I want to assign a member variable to the result of a lengthy block of code that returns a 2x2 array. This would be done in the constructor. But if in the constructor I do,
def initialize
#foo = ...
...
end
Then only the first line of code in that block gets assigned to #foo. If I define a method, then I get an unknown method error during compilation, which makes sense because it doesn't know which instance to call it on. I don't think I can do 'self', because it's in the constructor so there is no instance yet.
I don't want the block of code in the class definition, I would like it neatly encapsulated somewhere instead. A class method would require making other variables available to that method that should just belong to each instance.
You can use dpassage's solution, but I think the more common way of solving this would be to just use a helper function.
class Bar
def initialize
#foo = helper
end
def helper
#do stuff
end
end
If I understand what you're trying to do, you should be able to do it like this:
def initialize
#foo = begin
...
end
end
The value of #foo should be the result of the last line of code within the begin...end block.
If you have lengthy "preparation" code before you actually define the array, just put the array assignment after the lengthy part:
def initialize
# some lengthy code
# ...
#foo = something_using_the_result_from_the_lengthy_code
end
Or if the lengthy code is inside the assignment of the array, then you should have no problem with this:
def initialize
#foo = [
# lengthy code to assign the array
]
end
Apparently, Ruby can make a code block returning an instance variable's value with a symbol. Consider:
class Person
attr_accessor :fn
end
def give(n,&b)
i=0
while(i<n)
aa = Person.new
aa.fn = "name #{i}"
i=i+1
puts b.call(aa)
end
end
Now, both give(5, &:fn) and give(5) {|x| x.fn} give
name 0
name 1
name 2
name 3
name 4
=> nil
But what does &:fn really mean? I know the ampersand can convert a Proc to a block such as
bb = Proc.new {|x| x.fn}
give(5, &bb)
So what does the symbol :fn mean? Where can I see a documentation of its use like this? Can we use a symbol to access the instance variable, like say person:new or person[:new]?
Simple. The statment
attr_accessor :fn
Doesn't define some sort of special instance variable thing. It declares two methods of (approximately) this form:
def fn
#fn
end
def fn=(v)
#fn=v
end
The Ruby "syntax" &:fn is a symbol that the operator & tries to convert to a proc. And, guess what? Symbol implements to_proc. Guess that that looks like:
def to_proc
Proc.new {|obj| obj.__send__(self)}
end
That captures self, which is the symbol :fn, which, on invocation, tells obj to execute the method fn, which returns the value of the instance variable.
EDIT: Answering the second part of the question, no and yes. With the syntax you say, no. But you can call a method from a symbol with BasicObject#__send__ and you can do the same for not attr_accessor instance variables with Object#instance_variable_get.
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.
Hi i want to do the following. I simply want to overload the [] method in order to access the instance variables... I know, it doesn't make great sense at all, but i want to do this for some strange reason :P
It will be something like this...
class Wata
attr_accessor :nombre, :edad
def initialize(n,e)
#nombre = n
#edad = e
end
def [](iv)
self.iv
end
end
juan = Wata.new('juan',123)
puts juan['nombre']
But this throw the following error:
overload.rb:11:in `[]': undefined method 'iv' for # (NoMethodError)
How can i do that?
EDIT
I have found also this solution:
def [](iv)
eval("self."+iv)
end
Variables and messages live in a different namespace. In order to send the variable as a message, you'd need to define it as either:
def [](iv)
send iv
end
(if you want to get it through an accessor)
or
def [](iv)
instance_variable_get "##{iv}"
end
(if you want to access the ivar directly)
try instance_variable_get instead:
def [](iv)
instance_variable_get("##{iv}")
end