views:

155

answers:

4

Given any object in Ruby (on Rails), how can I write a method so that it will display that object's instance variable names and its values, like this:

@x: 1
@y: 2
@link_to_point: #<Point:0x10031b298 @y=20, @x=38>

(Update: inspect will do except for large object it is difficult to break down the variables from the 200 lines of output, like in Rails, when you request.inspect or self.inspect in the ActionView object)

I also want to be able to print <br> to the end of each instance variable's value so as to print them out nicely on a webpage.

the difficulty now seems to be that not every instance variable has an accessor, so it can't be called with obj.send(var_name)

(the var_name has the "@" removed, so "@x" becomes "x")

Update: I suppose using recursion, it can print out a more advanced version:

#<Point:0x10031b462>
    @x: 1
    @y: 2
    @link_to_point: #<Point:0x10031b298>
        @x=38
        @y=20
A: 

Like this?

# Get the instance variables of an object
d = Date.new
d.instance_variables.each{|i| puts i + "<br />"}

Ruby Documentation on instance_variables.

The concept is commonly called "introspection", (to look into oneself).

rlb.usa
this only prints out the variable names, not the values
動靜能量
`eval(i)` should do the rest
Justin L.
@Justin, or avoid `eval` and use `instance_variable_get`
Marc-André Lafortune
thanks; learning more new stuff every day :)
Justin L.
+2  A: 

I would probably write it like this:

class Object
  def all_variables(root=true)
    vars = {}
    self.instance_variables.each do |var|
      ivar = self.instance_variable_get(var)
      vars[var] = [ivar, ivar.all_variables(false)]
    end
    root ? [self, vars] : vars
  end
end

def string_variables(vars, lb="\n", indent="\t", current_indent="")
  out             = "#{vars[0].inspect}#{lb}"
  current_indent += indent
  out            += vars[1].map do |var, ivar|
                      ivstr = string_variables(ivar, lb, indent, current_indent)
                      "#{current_indent}#{var}: #{ivstr}"
                    end.join
  return out
end

def inspect_variables(obj, lb="\n", indent="\t", current_indent="")
  string_variables(obj.all_variables, lb, indent, current_indent)
end

The Object#all_variables method produces an array containing (0) the given object and (1) a hash mapping instance variable names to arrays containing (0) the instance variable and (1) a hash mapping…. Thus, it gives you a nice recursive structure. The string_variables function prints out that hash nicely; inspect_variables is just a convenience wrapper. Thus, print inspect_variables(foo) gives you a newline-separated option, and print inspect_variables(foo, "<br />\n") gives you the version with HTML line breaks. If you want to specify the indent, you can do that too: print inspect_variables(foo, "\n", "|---") produces a (useless) faux-tree format instead of tab-based indenting.

There ought to be a sensible way to write an each_variable function to which you provide a callback (which wouldn't have to allocate the intermediate storage); I'll edit this answer to include it if I think of something. Edit 1: I thought of something.

Here's another way to write it, which I think is slightly nicer:

class Object
  def each_variable(name=nil, depth=0, parent=nil, &block)
    yield name, self, depth, parent
    self.instance_variables.each do |var|
      self.instance_variable_get(var).each_variable(var, depth+1, self, &block)
    end
  end
end

def inspect_variables(obj, nl="\n", indent="\t", sep=': ')
  out = ''
  obj.each_variable do |name, var, depth, _parent|
    out += [indent*depth, name, name ? sep : '', var.inspect, nl].join
  end
  return out
end

The Object#each_variable method takes a number of optional arguments, which are not designed to be specified by the user; instead, they are used by the recursion to maintain state. The given block is passed (a) the name of the instance variable, or nil if the variable is the root of the recursion; (b) the variable; (c) the depth to which the recursion has descended; and (d), the parent of the current variable, or nil if said variable is the root of the recursion. The recursion is depth-first. The inspect_variables function uses this to build up a string. The obj argument is the object to iterate through; nl is the line separator; indent is the indentation to be applied at each level; and sep separates the name and the value.

Edit 2: This doesn't really add anything to the answer to your question, but: just to prove that we haven't lost anything in the reimplementation, here's a reimplementation of all_variables in terms of each_variables.

def all_variables(obj)
  cur_depth = 0
  root      = [obj, {}]

  tree      = root
  parents   = []
  prev      = root

  obj.each_variable do |name, var, depth, _parent|
    next unless name

    case depth <=> cur_depth
      when -1 # We've gone back up
        tree = parents.pop(cur_depth - depth)[0]
      when +1 # We've gone down
        parents << tree
        tree = prev
      else # We're at the same level
        # Do nothing
    end

    cur_depth            = depth
    prev = tree[1][name] = [var, {}]
  end

  return root
end

I feel like it ought to be shorter, but that may not be possible; because we don't have the recursion now, we have to maintain the stack explicitly (in parents). But it is possible, so the each_variable method works just as well (and I think it's a little nicer).

Antal S-Z
+2  A: 

I see... Antal must be giving the advanced version here...

the short version then probably is:

def p_each(obj)
  obj.instance_variables.each do |v|
    puts "#{v}: #{obj.instance_variable_get(v)}\n"
  end
  nil
end

or to return it as a string:

def sp_each(obj)
  s = ""
  obj.instance_variables.each do |v|
    s += "#{v}: #{obj.instance_variable_get(v)}\n"
  end
  s
end

or shorter:

def sp_each(obj)
  obj.instance_variables.map {|v| "#{v}: #{obj.instance_variable_get(v)}\n"}.join
end
動靜能量
That's the bare-bones simplest form, yeah. My solution also (a) recurses, so you get the tree structure, and (b) allows you to add the `<br />`s without rewriting the rest of your code.
Antal S-Z
+1  A: 

This is a quick adaptation of a simple JSON emitter I wrote for another question:

class Object
  def inspect!(indent=0)
    return inspect if instance_variables.empty?
    "#<#{self.class}:0x#{object_id.to_s(16)}\n#{'  ' * indent+=1}#{
      instance_variables.map {|var|
        "#{var}: #{instance_variable_get(var).inspect!(indent)}"
      }.join("\n#{'  ' * indent}")
    }\n#{'  ' * indent-=1}>"
  end
end

class Array
  def inspect!(indent=0)
    return '[]' if empty?
    "[\n#{'  ' * indent+=1}#{
      map {|el| el.inspect!(indent) }.join(",\n#{'  ' * indent}")
    }\n#{'  ' * indent-=1}]"
  end
end

class Hash
  def inspect!(indent=0)
    return '{}' if empty?
    "{\n#{'  ' * indent+=1}#{
      map {|k, v|
        "#{k.inspect!(indent)} => #{v.inspect!(indent)}"
      }.join(",\n#{'  ' * indent}")
    }\n#{'  ' * indent-=1}}"
  end
end

That's all the magic, really. Now we only need some simple defaults for some types where a full-on inspect doesn't really make sense (nil, false, true, numbers, etc.):

module InspectBang
  def inspect!(indent=0)
    inspect
  end
end

[Numeric, Symbol, NilClass, TrueClass, FalseClass, String].each do |klass|
  klass.send :include, InspectBang
end
Jörg W Mittag