Return Method local_variables as Hash - ruby

I'm generating some local variables in a method. I would like to find a way of returning them in a hash with keys and values, but the generation of that very hash ends up in the return itself. How can I avoid this?
def get_map_libs()
libjq = JSON.parse(File.read(URI.open('https://api.cdnjs.com/libraries/jquery', 'r')))['latest']
liblf = JSON.parse(File.read(URI.open('https://api.cdnjs.com/libraries/leaflet', 'r')))['latest']
libbs = JSON.parse(File.read(URI.open('https://api.cdnjs.com/libraries/twitter-bootstrap', 'r')))['latest']
libfa = JSON.parse(File.read(URI.open('https://api.cdnjs.com/libraries/font-awesome', 'r')))['latest']
return {libjq: libjq, liblf: liblf, libbs: libbs, libfa: libfa}
# return local_variables
end
This method works as expected. There should be a way of grabbing local_variables and returning it. However, when I use that commented-out return (return local_variables), it only returns their keys:
[
[0] :libjq,
[1] :liblf,
[2] :libbs,
[3] :libfa
]
I tried building a return hash r = {} and populating it, however that very hash also shows up in the return. I tried deleting it, but that throws an error when I try to delete itself in itself.
Can this be done or do I have to hard code it like above?

The documentation of local_variables tells that it only returns an array with the names of the local variables.
But you could use that method to generate the hash:
local_variables.map { |name| [name, eval(name.to_s)] }.to_h
I think that is a bit error-prone because you might return unexpected variables and their values.
Perhaps it would be better to refector your method to something like this:
LIBRARIES = {
libjq: 'jquery',
liblf: 'leaflet',
libbs: 'twitter-bootstrap',
libfa: 'font-awesome'
}
def library_urls
LIBRARIES.map { |k, v|
[k, JSON.parse(URI.open("https://api.cdnjs.com/libraries/#{v}").read)['latest']]
}.to_h
end

Well, the problem is that you are assigning the result hash to a local variable, so if you want to return a list of local variables, then of course that variable will be included.
The two simplest solutions I can think of would be:
Filter out the name of that local variable.
Just don't assign it. I.e. where you have r = something, just return something without assigning it to r in the first place. Something like this:
def get_map_libs
libjq = :jq
liblf = :lf
libbs = :bs
libfa = :fa
local_variables.map {|var| [var, binding.local_variable_get(var)] }.to_h
end
get_map_libs
#=> { :libjq => :jq, :liblf => :lf, :libbs => :bs, :libfa => :fa }

Related

how to pass variable from a class to another class in ruby

I'm trying to extract data from mongodb to Elasticsearch, getMongodoc = coll.find().limit(10)
will find the first 10 entries in mongo.
As you can see , result = ec.mongoConn should get result from method mongoConn() in class MongoConnector. when I use p hsh(to examine the output is correct), it will print 10 entires, while p result = ec.mongoConn will print #<Enumerator: #<Mongo::Cursor:0x70284070232580 #view=#<Mongo::Collection::View:0x70284066032180 namespace='mydatabase.mycollection' #filter={} #options={"limit"=>10}>>:each>
I changed p hsh to return hsh, p result = ec.mongoConn will get the correct result, but it just prints the first entry not all 10 entries. it seems that the value of hsh did not pass to result = ec.mongoConn correctly, Can anyone tell me what am I doing wrong? is this because I did something wrong with method calling?
class MongoConncetor
def mongoConn()
BSON::OrderedHash.new
client = Mongo::Client.new([ 'xx.xx.xx.xx:27017' ], :database => 'mydatabase')
coll = client[:mycollection]
getMongodoc = coll.find().limit(10)
getMongodoc.each do |document|
hsh = symbolize_keys(document.to_hash).select { |hsh| hsh != :_id }
return hsh
# p hsh
end
end
class ElasticConnector < MongoConncetor
include Elasticsearch::API
CONNECTION = ::Faraday::Connection.new url: 'http://localhost:9200'
def perform_request(method, path, params, body)
puts "--> #{method.upcase} #{path} #{params} #{body}"
CONNECTION.run_request \
method.downcase.to_sym,
path,
((
body ? MultiJson.dump(body) : nil)),
{'Content-Type' => 'application/json'}
end
ec = ElasticConnector.new
p result = ec.mongoConn
client = ElasticConnector.new
client.bulk index: 'myindex',
type:'test' ,
body: result
end
You are calling return inside a loop (each). This will stop the loop and return the first result. Try something like:
getMongodoc.map do |document|
symbolize_keys(document.to_hash).select { |hsh| hsh != :_id }
end
Notes:
In ruby you usually don't need the return keyword as the last value is returned automatically. Usually you'd use return to prevent some code from being executed
in ruby snake_case is used for variable and method names (as opposed to CamelCase or camelCase)
map enumerates a collection (by calling the block for every item in the collection) and returns a new collection of the same size with the return values from the block.
you don't need empty parens () on method definitions
UPDATE:
The data structure returned by MongoDB is a Hash (BSON is a special kind of serialization). A Hash is a collection of keys ("_id", "response") that point to values. The difference you point out in your comment is the class of the hash key: string vs. symbol
In your case a document in Mongo is represented as Hash, one hash per document
If you want to return multiple documents, then an array is required. More specifically an array of hashes: [{}, {}, ...]
If your target (ES) does only accept one hash at a time, then you will need to loop over the results from mongo and add them one by one:
list_of_results = get_mongo_data
list_of_results.each do |result|
add_result_to_es(result)
end

How do I access the elements in a hash which is itself a value in a hash?

I have this hash $chicken_parts, which consists of symbol/hash pairs (many more than shown here):
$chicken_parts = { :beak = > {"name"=>"Beak", "color"=>"Yellowish orange", "function"=>"Pecking"}, :claws => {"name"=>"Claws", "color"=>"Dirty", function"=>"Scratching"} }
Then I have a class Embryo which has two class-specific hashes:
class Embryo
#parts_grown = Hash.new
#currently_developing = Hash.new
Over time, new pairs from $chicken_parts will be .merge!ed into #parts_grown. At various times, #currently developing will be declared equal to one of the symbol/hash pairs from #parts_grown.
I'm creating Embryo class functions and I want to be able to access the "name", "color", and "function" values in #currently_developing, but I don't seem to be able to do it.
def grow_part(part)
#parts_grown.merge!($chicken_parts[part])
end
def develop_part(part)
#currently_developing = #parts_grown[part]
seems to populate the hashes as expected, but
puts #currently_developing["name"]
does not work. Is this whole scheme a bad idea? Should I just make the Embryo hashes into arrays of symbols from $chicken_parts, and refer to it whenever needed? That seemed like cheating to me for some reason...
There's a little bit of confusion here. When you merge! in grow_part, you aren't adding a :beak => {etc...} pair to #parts_grown. Rather, you are merging the hash that is pointed too by the part name, and adding all of the fields of that hash directly to #parts_grown. So after one grow_part, #parts_grown might look like this:
{"name"=>"Beak", "color"=>"Yellowish orange", "function"=>"Pecking"}
I don't think that's what you want. Instead, try this for grow_part:
def grow_part(part)
#parts_grown[part] = $chicken_parts[part]
end
class Embryo
#parts_grown = {a: 1, b: 2}
def show
p #parts_grown
end
def self.show
p #parts_grown
end
end
embryo = Embryo.new
embryo.show
Embryo.show
--output:--
nil
{:a=>1, :b=>2}

Issues iterating over a hash in Ruby

What I'd like to do is pass in a hash of hashes that looks something like this:
input = {
"configVersion" => "someVers",
"box" =>
{
"primary" => {
"ip" => "192.168.1.1",
"host" => "something"
},
"api" => {
"live" => "livekey",
"test" => "testkey"
}
}
}
then iterate over it, continuing if the value is another hash, and generating output with it. The result should be something like this:
configVersion = "someVers"
box.primary.ip = "192.168.1.1"
box.primary.host = "something"
and so on...
I know how to crawl through and continue if the value is a hash, but I'm unsure how to concatenate the whole thing together and pass the value back up. Here is my code:
def crawl(input)
input.each do |k,v|
case v
when Hash
out < "#{k}."
crawl(v)
else
out < " = '#{v}';"
end
end
end
My problem is: where to define out and how to return it all back. I'm very new to Ruby.
You can pass strings between multiple calls of the recursive method and use them like accumulators.
This method uses an ancestors string to build up your dot-notation string of keys, and an output str that collects the output and returns it at the end of the method. The str is passed through every call; the chain variable is a modified version of the ancestor string that changes from call to call:
def hash_to_string(hash, ancestors = "", str = "")
hash.each do |key, value|
chain = ancestors.empty? ? key : "#{ancestors}.#{key}"
if value.is_a? Hash
hash_to_string(value, chain, str)
else
str << "#{chain} = \"#{value}\"\n"
end
end
str
end
hash_to_string input
(This assumes you want your output to be a string formatted as you've shown above)
This blog post has a decent solution for the recursion and offers a slightly better alternative using the method_missing method available in Ruby.
In general, your recursion is correct, you just want to be doing something different instead of concatenating the output to out.

This is wrong, but why (and how can I get it to flow better)?

So I am figuring out how to set up some options for a class. 'options' is a hash. I want to
1) filter out options I don't want or need
2) set some instance variables to use elsewhere
3) and set up another hash with the processed options as #current_options.
def initialize_options(options)
#whitelisted_options, #current_options = [:timestamps_offset, :destructive, :minimal_author], {}
n_options = options.select { |k,v| #whitelisted_options.include?(k) }
#current_options[:timestamps_offset] = #timestamp_offset = n_options.fetch(:timestamps_offset, 0)*(60*60*24)
#current_options[:destructive] = #destructive = n_options.fetch(:destructive, false)
#current_options[:minimal_author] = #minimal_author = n_options.fetch(:minimal_author, false)
end
I'm guessing this is a bit much, no matter what I pass in I get:
{:timestamps_offset=>0, :destructive=>false, :minimal_author=>false}
When I do this line by line from the command line, it works as I want it to but not in my class. So what is going on and how do I clean this up?
EDIT: this actually works disembodied from the class I'm using it in, but inside it doesn't so the reality is something is going on I'm not aware of right now.
attr_reader :current_options is how this is set on the class, perhaps that needs some revision.
EDIT2: line 2 of the method is supposed to select from #whitelisted_options
EDIT3: Actually turned out to be something I wasn't thinking of..."options" comes in parsed from a yaml file as strings....and I was fetching symbols, changing that around makes a difference where before the method was looking for symbols and finding none, e.g. "destructive" vs :destructive, so always defaulting to the defaults. In short, I just needed to symbolize the hash keys when options are imported.
Your #current_options is initialized as an empty hash. When you filter the options passed as params, none of the keys will be present in #current_options so n_options will end up empty.
Then when you set up #current_options in the following lines, it will always grab the default values (0, false, false), and that's why your output's always the same.
You solve this problem by conditionally initializing #current_options so that it's only set to {} once:
#current_options ||= {}
Post-OP edit:
Your issue's with options.select -- in Ruby 1.8, it doesn't return a Hash, but rather an Array. Your calls to fetch are then always failing (as symbols can't be array indexes), so always returning defaults.
Instead, try:
n_options = options.inject({}) {|h, p| h[p[0]] = p[1] if #whitelisted_options.include? p[0]; h }
where p is an array containing each key/value pair.
In Ruby 1.9.2, Hash.select behaves the way you expected it to.
Edit 2: Here's how I'd approach it:
class Foo
##whitelisted_options= {:timestamps_offset => 0, :destructive => false, :minimal_author =>false}
##whitelisted_options.keys.each do |option|
define_method(option) { return #current_options[option] rescue nil}
end
def initialize_options(options)
#current_options = {}
##whitelisted_options.each {|k, v| #current_options[k] = options[k] || v}
#current_options
end
end
In use:
f = Foo.new
f.destructive #=> nil
f.initialize_options(:minimal_author => true, :ignore => :lol)
f.destructive #=> false
f.minimal_author #=> true
f.timestamps_offset #=> 0
What is #whitelisted_options for?
What do you want to happen if :destructive is not a key in options? Do you want to have :destructive => false, or do you want #current_options to not mention :destructive at all?

how to name an object reference (handle) dynamically in ruby

So I have a class like this:
def Word
end
and im looping thru an array like this
array.each do |value|
end
And inside that loop I want to instantiate an object, with a handle of the var
value = Word.new
Im sure there is an easy way to do this - I just dont know what it is!
Thanks!
To assign things to a dynamic variable name, you need to use something like eval:
array.each do |value|
eval "#{value} = Word.new"
end
but check this is what you want - you should avoid using eval to solve things that really require different data structures, since it's hard to debug errors created with eval, and can easily cause undesired behaviour. For example, what you might really want is a hash of words and associated objects, for example
words = {}
array.each do |value|
words[value] = Word.new
end
which won't pollute your namespace with tons of Word objects.
Depending on the data structure you want to work with, you could also do this:
# will give you an array:
words = array.map { |value| Word.new(value) }
# will give you a hash (as in Peter's example)
words = array.inject({}) { |hash, value| hash.merge value => Word.new }
# same as above, but more efficient, using monkey-lib (gem install monkey-lib)
words = array.construct_hash { |value| [value, Word.new ] }

Resources