Ruby smart variables [duplicate] - ruby

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.

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

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.

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}"
}

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).

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)

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"

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.

# 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"

Related

How to pass method arguments use as Hash path?

E.G.
def do_the_thing(file_to_load, hash_path)
file = File.read(file)
data = JSON.parse(file, { symbolize_names: true })
data[sections.to_sym]
end
do_the_thing(file_I_want, '[:foo][:bar][0]')
Tried a few methods but failed so far.
Thanks for any help in advance :)
Assuming you missed the parameters names...
Lets assume our file is:
// test.json
{
"foo": {
"bar": ["foobar"]
}
}
Recomended solution
Does your param really need to be a string??
If your code can be more flexible, and pass arguments as they are on ruby, you can use the Hash dig method:
require 'json'
def do_the_thing(file, *hash_path)
file = File.read(file)
data = JSON.parse(file, symbolize_names: true)
data.dig(*hash_path)
end
do_the_thing('test.json', :foo, :bar, 0)
You should get
"foobar"
It should work fine !!
Read the rest of the answer if that doesn't satisfy your question
Alternative solution (using the same argument)
If you REALLY need to use that argument as string, you can;
Treat your params to adapt to the first solution, it won't be a small or fancy code, but it will work:
require 'json'
BRACKET_REGEX = /(\[[^\[]*\])/.freeze
# Treats the literal string to it's correspondent value
def treat_type(param)
# Remove the remaining brackets from the string
# You could do this step directly on the regex if you want to
param = param[1..-2]
case param[0]
# Checks if it is a string
when '\''
param[1..-2]
# Checks if it is a symbol
when ':'
param[1..-1].to_sym
else
begin
Integer(param)
rescue ArgumentError
param
end
end
end
# Converts your param to the accepted pattern of 'dig' method
def string_to_args(param)
# Scan method will break the match results of the regex into an array
param.scan(BRACKET_REGEX).flatten.map { |match| treat_type(match) }
end
def do_the_thing(file, hash_path)
hash_path = string_to_args(hash_path)
file = File.read(file)
data = JSON.parse(file, symbolize_names: true)
data.dig(*hash_path)
end
so:
do_the_thing('test.json', '[:foo][:bar][0]')
returns
"foobar"
This solution though is open to bugs when the "hash_path" is not on an acceptable pattern, and treating it's bugs might make the code even longer
Shortest solution (Not safe)
You can use Kernel eval method which I EXTREMELY discourage to use for security reasons, read the documentation and understand its danger before using it
require 'json'
def do_the_thing(file, hash_path)
file = File.read(file)
data = JSON.parse(file, symbolize_names: true)
eval("data#{hash_path}")
end
do_the_thing('test.json', '[:foo][:bar][0]')
If the procedure you were trying to work with was just extracting the JSON data to an object, you might find yourself using either of the following scenarios:
def do_the_thing(file_to_load)
file = File.read(file)
data = JSON.parse(file, { symbolize_names: true })
data[sections.to_sym]
end
do_the_thing(file_I_want)[:foo][:bar][0]
or use the dig function of Hash :
def do_the_thing(file_to_load, sections)
file = File.read(file)
data = JSON.parse(file, { symbolize_names: true })
data.dig(*sections)
end
do_the_thing(file_I_want, [:foo, :bar, 0])

Using variable declared in one method to open webpage in another method

I am working on a CLI Project and trying to open up a web page by using url variable declared in another method.
def self.open_deal_page(input)
index = input.to_i - 1
#deals = PopularDeals::NewDeals.new_deals
#deals.each do |info|
d = info[index]
#product_url = "#{d.url}"
end
#product_url.to_s
puts "They got me!"
end
def self.deal_page(product_url)
#self.open_deal_page(input)
deal = {}
html = Nokogiri::HTML(open(#product_url))
doc = Nokogiri::HTML(html)
deal[:name] = doc.css(".dealTitle h1").text.strip
deal[:discription] = doc.css(".textDescription").text.strip
deal[:purchase] = doc.css("div a.button").attribute("href")
deal
#binding.pry
end
but I am receiving this error.
`open': no implicit conversion of nil into String (TypeError)
any possible solution? Thank you so much in advance.
Try returning your #product_url within your open_deal_page method, because now you're returning puts "They got me!", and also note that your product_url is being created inside your each block, so, it won't be accessible then, try creating it before as an empty string and then you can return it.
def open_deal_page(input)
...
# Create the variable
product_url = ''
# Assign it the value
deals.each do |info|
product_url = "#{info[index].url}"
end
# And return it
product_url
end
In your deal_page method tell to Nokogiri to open the product_url that you're passing as argument.
def deal_page(product_url)
...
html = Nokogiri::HTML(open(product_url))
...
end

ruby parameters returning themselves

I'm running Ruby 2.3.1 x64 on Windows 10 x64.
My code:
class Credentials
attr_reader :username, :password
def initialize(username = nil, password = nil)
#username = username
#password = password
get_credentials if !#username || !#password #Gets credentials if none are specified
end
def get_credentials
#username = ask("Username: ") { |q| q.echo = true }
#password = ask("Password: ") { |q| q.echo = "*" }
end
end
Ignore the get_credentials wackyness, it's a gem called Highline that I'm using to hide input for security reasons.
When I do the following:
$user = Credentials.new(username: "foo", password: "bar")
I get this return:
#<Credentials:0x000000038ecf30 #password=nil, #username={:username=>"foo", :password=>"bar"}>
Likewise, calling $user.username returns the following:
{:username=>"foo", :password=>"bar"}
when it should be returning:
"foo"
and calling $user.password returns nil.
Can someone tell me why in the name of Henry Hamilton this is happening?! I've used hashed parameters many times, and it always works just fine. Why is it stuffing every parameter setting into a single parameter?
$user = Credentials.new(username: "foo", password: "bar")
You are passing just one parameter to the initialize method, a hash. The hash for the username attribute and nil for the password attribute. Try
$user = Credentials.new("foo", "bar")
Or, if you really want keyword arguments then
def initialize(username: nil, password: nil)
When you define a method/constructor you don't pass arguments by name but by value just like any other programming language, So :
$user=Credentials.new("foo","bar")
Will do what you want.
This is the default in almost every programming language, your question should have been "How did this work", it worked because ruby is dynamically typed and the syntax key1: val1,key2: val2,... is the new hash syntax(since ruby 1.9), a hash is a key-value data structure , so your :
$user=Credentials.new(username: 'foo',password: 'bar')
Is actually calling the constructor with one argument only which is username with the value {username: 'foo',password: 'bar'} and because initialize is defined with default arguments , password got a value of nil.
Now if you do want to pass arguments by name, you have to define the constructor like so :
def initialize(username: nil,password: nil)
//code
end
After that you can do :
$user=Credentials.new(username: 'foo',password: 'bar')
And expect it to behave like you want.
Notice that keyword arguments(that is passing arguments by name) are introduced in ruby 2, also notice that you can achieve the same with a constructor that accepts one parameter which is a hash like this :
def initialize(params={})
//code
end
But this way doesn't limit the number of arguments nor their names(you can call Credentials.new(fooprop: 'foovalue') and no error will be thrown), also it needs some change in code.
The Keyword arguments feature is found in some programming languages and it's useful when the function have many parameters or to make it clear for the programmer what is the parameter for.
def initialize(params={})
#username = params[:username]
#password = params[:password]
#username || #password || get_credentials #simply
end
And then:
$user = Credentials.new(username: "foo", password: "bar")

Dynamically check if a field in JSON is nil without using eval

Here's an extract of the code that I am using:
def retrieve(user_token, quote_id, check="quotes")
end_time = Time.now + 15
match = false
until Time.now > end_time || match
#response = http_request.get(quote_get_url(quote_id, user_token))
eval("match = !JSON.parse(#response.body)#{field(check)}.nil?")
end
match.eql?(false) ? nil : #response
end
private
def field (check)
hash = {"quotes" => '["quotes"][0]',
"transaction-items" => '["quotes"][0]["links"]["transactionItems"]'
}
hash[check]
end
I was informed that using eval in this manner is not good practice. Could anyone suggest a better way of dynamically checking the existence of a JSON node (field?). I want this to do:
psudo: match = !JSON.parse(#response.body) + dynamic-path + .nil?
Store paths as arrays of path elements (['quotes', 0]). With a little helper function you'll be able to avoid eval. It is, indeed, completely inappropriate here.
Something along these lines:
class Hash
def deep_get(path)
path.reduce(self) do |memo, path_element|
return unless memo
memo[path_element]
end
end
end
path = ['quotes', 0]
hash = JSON.parse(response.body)
match = !hash.deep_get(path).nil?

Ruby method calls without defining variables

I am beyond confused on where the :find is coming from line 17, as well as :findcity... is that how you call a fucntion within a predefined method call from ruby???
cities = {'CA' => 'San Francisco',
'MI' => 'Detroit',
'FL' => 'Jacksonville'}
cities['NY'] = 'New York'
cities['OR'] = 'Portland'
def find_city(map, state)
if map.include? state
return map[state]
else
return "Not found."
end
end
# ok pay attention!
cities[:find] = method(:find_city)
while true
print "State? (ENTER to quit) "
state = gets.chomp
break if state.empty?
# this line is the most important ever! study!
puts cities[:find].call(cities, state)
end
For starters if you are a beginner in Ruby just don't bother trying to understand it. This is not the usual way of doing things in Ruby.
But here are some explanations:
:find is a Symbol and it could be :search or something else in this example.
You could actually use a different variable to store the method instead of storing inside the cities Hash. Like so:
# Instead of doing this
hash = {} # => {}
hash[:puts_method] = method(:puts)
hash[:puts_method].call("Foo")
# Foo
# You can just
puts_method = method(:puts)
puts_method.call("Foo")
# Foo
The find_city is the method defined in your code. Passing the symbol :find_city to the method method returns you an object representing that method (very meta uh?) of the class Method.
So like in the example above we can have an object representing the method puts with which we can send the method call to call it.
the_puts = method(:puts)
# => #<Method: Object(Kernel)#puts>
the_puts.call("Hey!")
# Hey!
# => nil
# Which is the same as simply doing
puts("Hey!")
# Hey!
# => nil

Categories

Resources