Assign different variable names (which are nil array) through a loop - ruby

I have a loop which should assign different variable names depending on filesname which are contained in an array.
Each variable is set as an empty array.
filenames = ['file1', 'file2']
filenames.each do |filename|
"#data_" + "#{filename}" = [] # used # as I need these variables externaly from the loop
end
This gives me the following error
syntax error, unexpected '=', expecting keyword_end
What I don't understand is if I use
filenames.each do |filename|
p "#data_" + "#{filename}"
end
it renders correctly
#data_file1
#data_file2
And if I set an empty array through the loop
filenames.each do |filename|
#data = []
end
p #data
#=> []
it works too...
Thanks for your help!

I would recommend using a data structure rather than synthesizing instance variables1 with meta-programming.
Why not just:
#data = filenames.inject({}) { |h, v| h[v] = []; h }
# => { "file1" => [], "file2" => [] }
Now you can just reference #data['file1'] where you want the 'file1' Array object.
1. But for the record, here is one way:
class A
def f x, y
self.class.send :attr_accessor, x
send "#{x}=", y
end
end
a = A.new
a.f 'new_instance_variable', 12345
p a.new_instance_variable

Simplest solution, if you're sure about the sanitsed-ness of your filenames array, would be:
filenames = ['file1', 'file2']
filenames.each do |filename|
eval "#data_#{filename} = []"
end

What you're doing in "#data_" + "#{filename}" = [] is assigning empty Array instance ([]) to a String instance ("#data_file1"), hence syntax error, unexpected '=', expecting keyword_end
You should want to do is to assign [] to instance variable: #data_file, not string: "#data_file".
You can achieve that with instance_variable_set method of class Object:
filenames = ['file1', 'file2']
filenames.each do |filename|
instance_variable_set("#data_#{filename}".to_sym, [])
end

Why are you using string concatenation and interpolation in the same string...
something like
p "#data_#{filename}"
is the same as your
p "#data_" + "#{filename}"
Now this won't work because you are trying to say string = something else you need to evaluate the string
eval("#data_#{filename} = []")

Related

Converting Ruby Hash into string with escapes

I have a Hash which needs to be converted in a String with escaped characters.
{name: "fakename"}
and should end up like this:
'name:\'fakename\'
I don't know how this type of string is called. Maybe there is an already existing method, which I simply don't know...
At the end I would do something like this:
name = {name: "fakename"}
metadata = {}
metadata['foo'] = 'bar'
"#{name} AND #{metadata}"
which ends up in that:
'name:\'fakename\' AND metadata[\'foo\']:\'bar\''
Context: This query a requirement to search Stripe API: https://stripe.com/docs/api/customers/search
If possible I would use Stripe's gem.
In case you can't use it, this piece of code extracted from the gem should help you encode the query parameters.
require 'cgi'
# Copied from here: https://github.com/stripe/stripe-ruby/blob/a06b1477e7c28f299222de454fa387e53bfd2c66/lib/stripe/util.rb
class Util
def self.flatten_params(params, parent_key = nil)
result = []
# do not sort the final output because arrays (and arrays of hashes
# especially) can be order sensitive, but do sort incoming parameters
params.each do |key, value|
calculated_key = parent_key ? "#{parent_key}[#{key}]" : key.to_s
if value.is_a?(Hash)
result += flatten_params(value, calculated_key)
elsif value.is_a?(Array)
result += flatten_params_array(value, calculated_key)
else
result << [calculated_key, value]
end
end
result
end
def self.flatten_params_array(value, calculated_key)
result = []
value.each_with_index do |elem, i|
if elem.is_a?(Hash)
result += flatten_params(elem, "#{calculated_key}[#{i}]")
elsif elem.is_a?(Array)
result += flatten_params_array(elem, calculated_key)
else
result << ["#{calculated_key}[#{i}]", elem]
end
end
result
end
def self.url_encode(key)
CGI.escape(key.to_s).
# Don't use strict form encoding by changing the square bracket control
# characters back to their literals. This is fine by the server, and
# makes these parameter strings easier to read.
gsub("%5B", "[").gsub("%5D", "]")
end
end
params = { name: 'fakename', metadata: { foo: 'bar' } }
Util.flatten_params(params).map { |k, v| "#{Util.url_encode(k)}=#{Util.url_encode(v)}" }.join("&")
I use it now with that string, which works... Quite straigt forward:
"email:\'#{email}\'"
email = "test#test.com"
key = "foo"
value = "bar"
["email:\'#{email}\'", "metadata[\'#{key}\']:\'#{value}\'"].join(" AND ")
=> "email:'test#test.com' AND metadata['foo']:'bar'"
which is accepted by Stripe API

How Ruby Method Modify in Place

How does one write Ruby methods for modification in place?
I want to accomplish the following:
def fulljoin(ruby_array)
r = ''
ruby_array.each {|item| r += "'#{ item }', "}
r.chop!.chop!
end
a = ['Alex', 'Bert', 'Charlie']
a = fulljoin(a) # => 'Alex', 'Bert', 'Charlie'
But I want to modify the array a in place:
a.fulljoin!
What is the syntax to accomplish this?
Initially a is an Array. If you could write method a.fulljoin! with desirable result, a would become a String, but it's not possible in Ruby.
But a.fulljoin! can convert a to Array with single member a[0] - a
String you need. And it will be as close to your goal as possible:
class Array
def fulljoin!
r = "'#{self.join("', '")}'"
self.clear
self[0] = r
end
end
a = ["Alex", "Bert", "Charlie"]
a.fulljoin!
p a
=> ["'Alex', 'Bert', 'Charlie'"]
P.S.: As suggested by #engineersmnky, method fulljoin! can be simplified to:
class Array
def fulljoin!
self.replace(["'#{self.join("', '")}'"])
end
end

Mutating an array of symbols

I want to mutate an array of symbols by adding an e or an s to the end of the symbols depending on the last letter of each symbol. For example, the array:
[:alpha, :beta, :kappa, :phi]
will be modified to:
[:alphae, :betae, :kappae, :phis]
I can do it using an if ... else condition and a regex with an array of strings, but not with symbols. I tried to convert my symbols to strings, mutate them, then convert back, but I get an error
s = [:aplha, :beta, :kappa, :phi]
def pluralSym(sym, out = [])
sym.each do |s|
s.to_s
if s.match(/a$/)
out = s.sub(/a$/, "ae")
elsif s.match(/i$/)
out = s.sub(/i$/, "is")
else
out = s
end
out.to_sym
end
end
p pluralSym(s)
block in pluralSym': undefined method `sub' for :aplha:Symbol
You can create a method that receives the symbol, the if that matches with /a$/ or /i$/, interpolate the suffix, and converts that to a symbol in each case, otherwise just return sym
def plural_sym(sym)
return "#{sym}ae".to_sym if sym =~ /a$/
return "#{sym}is".to_sym if sym =~ /i$/
sym
end
p [:aplha, :beta, :kappa, :phi].map(&method(:plural_sym))
# [:aplhaae, :betaae, :kappaae, :phiis]
The (&method(:plural_sym)) is just a way to call your function passing as argument each element within the block.
Notice here, you're not mutating an array, you're returning a new one.
You convert symbol to string, but you don't assign it and you keep using symbol. Also use map instead of each. A quickfix would be:
s = [:aplha, :beta, :kappa, :phi]
def pluralSym(sym, out = [])
sym.map! do |s|
str = s.to_s
if str.match(/a$/)
out = str.sub(/a$/, "ae")
elsif s.match(/i$/)
out = str.sub(/i$/, "is")
else
out = str
end
out.to_sym
end
end
H = { 'a'=>'e', 'i'=>'s' }
def plural_sym(arr)
arr.map! { |sym| (sym.to_s + H.fetch(sym[-1], '')).to_sym }
end
arr = [:aplha, :beta, :phi, :rho]
plural_sym arr
#=> [:aplhae, :betae, :phis, :rho]
arr
#=> [:aplhae, :betae, :phis, :rho]
See Hash#fetch.
A variant of this follows.
H = Hash.new { |h,k| '' }.merge('a'=>'e', 'i'=>'s')
def plural_sym(arr)
arr.map! { |sym| (sym.to_s + H[sym[-1]]).to_sym }
end
arr = [:aplha, :beta, :phi, :rho]
plural_sym arr
#=> [:aplhae, :betae, :phis, :rho]
arr
#=> [:aplhae, :betae, :phis, :rho]
See Hash::new.
Symbols are immutable in ruby so you need convert them to string first
s = s.to_s

How to get parts of JSON object

I get TypeError: no implicit conversion of String into Integer Couldn't figure out what is wrong here.
require 'json'
h = '{"name":[{"first":"first ", "last":"last"}], "age":2}'
h = JSON.parse(h)
class C
def fullname(p)
first(p["name"]) + last(p["name"])
end
def age(p)
p["age"]
end
private
def first(name)
name["first"]
end
def last(name)
name["last"]
end
end
C.new.age(h) #=> 2
C.new.fullname(h) #=> TypeError: no implicit conversion of String into Integer
Name is an array, you have two options:
Option A:
Give fullname an element of the array:
def fullname(elem)
first(elem) + last(elem)
end
And call it with
C.fullname(p.first)
for instance
Option B:
Assume that it's always the first element of the array in fullname
def fullname(p)
name=p["name"].first
first(name) + last(name)
end
Don't be confused by Array.first which is Array[0] and your 'first' function
The result of h["name"] is name = [{"first" => "first ", "last" => "last"}], which is an array. You cannot apply name["first"] or name["last"]. The argument passed to an array has to be an integer.
"name" is an Array. fullname(p) should read
first(p["name"][0]) + last(p["name"][0])

Can't convert symbol to integer from hash table

Edit: The issue is being unable to get the quantity of arrays within the hash, so it can be, x = amount of arrays. so it can be used as function.each_index{|x| code }
Trying to use the index of the amount of rows as a way of repeating an action X amount of times depending on how much data is pulled from a CSV file.
Terminal issued
=> Can't convert symbol to integer (TypeError)
Complete error:
=> ~/home/tests/Product.rb:30:in '[]' can't convert symbol into integer (TypeError) from ~home/tests/Product.rub:30:in 'getNumbRel'
from test.rb:36:in '<main>'
the function is that is performing the action is:
def getNumRel
if defined? #releaseHashTable
return #releaseHashTable[:releasename].length
else
#releaseHashTable = readReleaseCSV()
return #releaseHashTable[:releasename].length
end
end
The csv data pull is just a hash of arrays, nothing snazzy.
def readReleaseCSV()
$log.info("Method "+"#{self.class.name}"+"."+"#{__method__}"+" has started")
$log.debug("reading product csv file")
# Create a Hash where the default is an empty Array
result = Array.new
csvPath = "#{File.dirname(__FILE__)}"+"/../../data/addingProdRelProjIterTestSuite/releaseCSVdata.csv"
CSV.foreach(csvPath, :headers => true, :header_converters => :symbol) do |row|
row.each do |column, value|
if "#{column}" == "prodid"
proHash = Hash.new { |h, k| h[k] = [ ] }
proHash['relid'] << row[:relid]
proHash['releasename'] << row[:releasename]
proHash['inheritcomponents'] << row[:inheritcomponents]
productId = Integer(value)
if result[productId] == nil
result[productId] = Array.new
end
result[productId][result[productId].length] = proHash
end
end
end
$log.info("Method "+"#{self.class.name}"+"."+"#{__method__}"+" has finished")
#productReleaseArr = result
end
Sorry, couldn't resist, cleaned up your method.
# empty brackets unnecessary, no uppercase in method names
def read_release_csv
# you don't need + here
$log.info("Method #{self.class.name}.#{__method__} has started")
$log.debug("reading product csv file")
# you're returning this array. It is not a hash. [] is preferred over Array.new
result = []
csvPath = "#{File.dirname(__FILE__)}/../../data/addingProdRelProjIterTestSuite/releaseCSVdata.csv"
CSV.foreach(csvPath, :headers => true, :header_converters => :symbol) do |row|
row.each do |column, value|
# to_s is preferred
if column.to_s == "prodid"
proHash = Hash.new { |h, k| h[k] = [ ] }
proHash['relid'] << row[:relid]
proHash['releasename'] << row[:releasename]
proHash['inheritcomponents'] << row[:inheritcomponents]
# to_i is preferred
productId = value.to_i
# this notation is preferred
result[productId] ||= []
# this is identical to what you did and more readable
result[productId] << proHash
end
end
end
$log.info("Method #{self.class.name}.#{__method__} has finished")
#productReleaseArr = result
end
You haven't given much to go on, but it appears that #releaseHashTable contains an Array, not a Hash.
Update: Based on the implementation you posted, you can see that productId is an integer and that the return value of readReleaseCSV() is an array.
In order to get the releasename you want, you have to do this:
#releaseHashTable[productId][n][:releasename]
where productId and n are integers. Either you'll have to specify them specifically, or (if you don't know n) you'll have to introduce a loop to collect all the releasenames for all the products of a particular productId.
This is what Mark Thomas meant:
> a = [1,2,3] # => [1, 2, 3]
> a[:sym]
TypeError: can't convert Symbol into Integer
# here starts the backstrace
from (irb):2:in `[]'
from (irb):2
An Array is only accessible by an index like so a[1] this fetches the second element from the array
Your return a an array and thats why your code fails:
#....
result = Array.new
#....
#productReleaseArr = result
# and then later on you call
#releaseHashTable = readReleaseCSV()
#releaseHashTable[:releasename] # which gives you TypeError: can't convert Symbol into Integer

Resources