34

What is the best way to write a function (or something DSLish) that will allow me to write this code in Ruby. How would I construct the function write_pair?

username = "tyndall"
write_pair username
# where write_pair username outputs 
username: tyndall

Is it possible to do? Looking for the most simple way to do this.

memius
  • 4,045
  • 5
  • 26
  • 25
BuddyJoe
  • 69,735
  • 114
  • 291
  • 466

8 Answers8

25

Sure it is possible!

My solution tests the var by Object#object_id identity: http://codepad.org/V7TXRxmL
It's crippled in the binding passing style ...
Although it works just for local vars yet, it can be easily be made "universal" adding use of the other scope-variable-listing methods like instance_variables etc.

# the function must be defined in such a place 
# ... so as to "catch" the binding of the vars ... cheesy
# otherwise we're kinda stuck with the extra param on the caller
@_binding = binding
def write_pair(p, b = @_binding)
  eval("
    local_variables.each do |v| 
      if eval(v.to_s + \".object_id\") == " + p.object_id.to_s + "
        puts v.to_s + ': ' + \"" + p.to_s + "\"
      end
    end
  " , b)
end

# if the binding is an issue just do here:
# write_pair = lambda { |p| write_pair(p, binding) }

# just some test vars to make sure it works
username1 = "tyndall"
username  = "tyndall"
username3 = "tyndall"

# the result:
write_pair(username)
# username: tyndall
Assaf Shomer
  • 1,429
  • 1
  • 15
  • 21
clyfe
  • 23,695
  • 8
  • 85
  • 109
  • where can I read more on this binding (in the way that you are using the term)? this is where I get lost. – BuddyJoe Apr 15 '10 at 15:05
  • http://ruby-doc.org/core/classes/Binding.html http://onestepback.org/index.cgi/Tech/Ruby/RubyBindings.rdoc/style/print – clyfe Apr 15 '10 at 15:53
  • 13
    I would be seriously tempted to injure anyone who would actually use this code in a project. – molf Jun 28 '10 at 13:05
  • 2
    This is purely an experiment of boundaries. – clyfe Jun 28 '10 at 17:26
17

If it's possible for you to use a symbol instead of the variable name, you could do something like this:

def wp (s, &b)
  puts "#{s} = #{eval(s.to_s, b.binding)}"
end

In use:

irb(main):001:0> def wp (s, &b)
irb(main):002:1>   puts "#{s} = #{eval(s.to_s, b.binding)}"
irb(main):003:1> end
=> nil
irb(main):004:0> var = 3
=> 3
irb(main):005:0> wp(:var) {}
var = 3

Note that you must pass the empty block {} to the method or it cannot get the binding to evaluate the symbol.

Arkku
  • 41,011
  • 10
  • 62
  • 84
  • If you are going to call .to_s, why not pass a string directly? This way you can even use more complex expressions. Besides, why do you use b.send(:binding) instead of the more obvious b.binding? (I'm new to Ruby). Anyway, it works great and is far simpler than that other solution (the one called write_pair), which I can't even understand – marcus Sep 26 '10 at 02:37
  • Yes, you can pass a string instead of the symbol (no modifications required), the first line in the solution just refers to the fact that you can't type something `wp(var)` (like in the question). Don't know/remember why I picked the symbol instead of string, or why I put in `.send(:binding)` instead of `.binding` — probably due to thinking of some other solution first and evolving from that. =) – Arkku Sep 26 '10 at 04:39
6

I made a vim macro for this:

" Inspect the variable on the current line (in Ruby)
autocmd FileType ruby nmap ,i ^"oy$Iputs "<esc>A: #{(<esc>"opA).inspect}"<esc>

Put the variable you'd like to inspect on a line by itself, then type ,i (comma then i) in normal mode. It turns this:

foo

into this:

puts "foo: #{(foo).inspect}"

This is nice because it doesn't have any external dependencies (e.g. you don't have to have a library loaded up to use it).

Benjamin Oakes
  • 12,262
  • 12
  • 65
  • 83
5

You can't actually get a variable's name in Ruby. But you could do something like this:

data = {"username" => "tyndall"}

Or even,

username = "tyndall"
data = {"username", "password", "favorite_color"}
data.each { |param|
   value = eval(param)
   puts "#{param}: #{value}"
}
David
  • 7,011
  • 1
  • 42
  • 38
4

This is a simple solution:

  def write_pair(variable)
    puts variable + eval(variable)
  end

This is more readable:

 def write_pair(variable)
    puts 'A' * 100
    puts variable + ': ' + eval(variable).inspect
    puts 'Z' * 100
 end

Invocation:

write_pair "variable"
memius
  • 4,045
  • 5
  • 26
  • 25
4

Building on previous answers relating to symbols & bindings ... if passing in the variable name as a symbol works for you (who doesn't love cutting out extra keystrokes?!), try this:

def wp(var_name_as_sym)
  # gets caller binding, which contains caller's execution environment
  parent_binding = RubyVM::DebugInspector.open{|i| i.frame_binding(2) }
  # now puts the symbol as string + the symbol executed as a variable in the caller's binding
  puts %Q~#{var_name_as_sym.to_s} = #{eval("#{var_name_as_sym.to_s}.inspect", parent_binding)}~
end

aa=1
bb='some bb string'
os = OpenStruct.new(z:26, y:25)

Console output:

> wp :aa
aa = 1
=> nil
> wp :bb
bb = "some bb string"
=> nil
> wp :os
os = #<OpenStruct z=26, y=25>
=> nil

Using ruby 2.2.2p95

(Credit to banister for getting binding of calling context)

DA2128
  • 93
  • 1
  • 7
3
def write_pair var, binding
  puts "#{ var } = #{ eval(var, binding)}"
end


username = "tyndall"
write_pair "username", binding

This seems weird because binding is never defined, but it works. From Ruby: getting variable name:

The binding() method gives a Binding object which remembers the context at the point the method was called. You then pass a binding into eval(), and it evaluates the variable in that context.

Be sure to pass a string, not the variable.

Community
  • 1
  • 1
B Seven
  • 44,484
  • 66
  • 240
  • 385
0
# make use of dynamic scoping via methods and instance vars
@_binding = binding
def eval_debug(expr, binding = @_binding)
   "#{expr} => #{eval(expr, binding)}"
end

# sample invocation:
x = 10
puts eval_debug "x"
puts eval_debug "x**x"
balrog
  • 9
  • 1