简体   繁体   中英

Putting a variable name = value format in Ruby

I would like to add some debugs for my simple ruby functions and I wrote a function as below,

def debug(&block)
  varname = block.call.to_s
  puts "#{varname} = #{eval(varname,block)}"
end

debug {:x} #prints x = 5
debug {:y} #prints y = 5

I understand that eval is evil. So I have two questions.

  1. Is there any way to write that debug method without using eval? If NO is there a preferred way to do this?
  2. Is there any way to pass a list of arguments to this method? I would ideally prefer debug {:x, :y. :anynumOfvariables}. I could not quite figure out how to factor that into the debug method (ie, to take a list of arguments)

Just use arrays. You can use the Array method to ensure that you will always have an array, even if someone passes in only a single value:

def debug(&block)
  Array(block[]).each do |var| puts "#{var} = #{eval var.to_s, block}" end
end

x, y = 3, 5

debug {:x} # => "x = 3"
debug {[:x, :y]} # => "x = 3" "y = 5"

BTW: passing a block as the binding no longer works in Ruby 1.9. (Despite the fact that the documentation says it does work.) You have to explicitly call Proc#binding to get a Binding object for that Proc :

def debug(&block)
  Array(block.()).flatten.each do |var|
    puts "#{var} = #{eval var.to_s, block.binding}"
  end
end

Fortunately, this already works in Ruby 1.8, so you can futureproof your code by including it.

An alternative would be to forgo the block altogether. I mean, you already force the user of the debug to use the unfamiliar idiom of passing arguments in the block instead of in parentheses. Why not force them to just pass the binding instead?

def debug(*vars, bnd)
  vars.each do |var|
    puts "#{var} = #{eval var.to_s, bnd}"
  end
end

x, y = 3, 5

debug :x, binding # => "x = 3"
debug :x, :y, binding # => "x = 3" "y = 5"

This has the added flexibility that they can actually pass a different binding than the one at the callsite, eg if they want to actually debug a piece of code in a different piece of the application.


BTW: here's some fun with Ruby 1.9.2's parameter introspection ( Proc#parameters ):

def debug(&block)
  block.parameters.map(&:last).each do |var|
    puts "#{var} = #{eval var.to_s, block.binding}"
  end
end

x, y = 3, 5

debug {|x|} # => "x = 3"
debug {|x, y|} # => "x = 3" "y = 5"

And how I must use those methods to get names and values of variables from each loop, to place them as keys and values of nested hash?

The *attributes array is passed as an method's parameter. I want to iterate through it, and every variable's name use as a key to nested hash and variable's value as value.

I got this:

def get_varname(&block)
    varname = block.call.to_s
    return varname
end

def create_instance_hash(env_attrs_obj, *attributes)
    if not env_attrs_obj.has_key?("instances")
        env_attrs_obj["instances"] = ""
    end
    attributes.each do |attr|
        attr_name = get_varname{:attr}
        env_attrs_obj["instances"][attr_name] = attr
    end
end

instance_nat_ip_pub = "ip_pub_addr"
instance_nat_ip_prv = "ip_addr"
instance_nat_ami = "AMI_NAME"
instance_nat_aws_ssh_key_id = "AWS_SSH_KEY_ID"
instance_nat_id = "instance_id"

env_hash = {}

create_instance_hash(env_hash, *[instance_nat_ip_pub, instance_nat_ip_prv, instance_nat_ami, instance_nat_aws_ssh_key_id, instance_nat_id])

But:

def attrs_each_test(*attributes)
    attributes.each do |attr|
        puts get_varname{:attr}
    end
end

And it outputs:

attr
attr
attr
attr
attr

The error when I run this create_instance_hash :

Line 12:in `[]=': string not matched (IndexError)
    from t.rb:12:in `create_instance_hash'
    from t.rb:10:in `each'
    from t.rb:10:in `create_instance_hash'
    from t.rb:24

How I can correct this error and achieve my goal?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM