This question already has answers here:
How to dynamically create a local variable?
(4 answers)
Closed 6 years ago.
What I'm trying to do in Ruby is to create an object with a name that comes from a string (in an array for instance). Then I want to use that object in further code.
So, for example:
array = ["a", "b", "c"]
array.each do |x|
send x + '_data'
x + '_data' = []
x + '_data' << "foo"
end
The above, of course, does not work.
I've racked my brain, the docs, and SO all morning on this. Your help is appreciated!
Any ideas?
Thanks!
Cheers,
Kyle
EDIT for clarity:
Ok, my understanding of send was incorrect.
For each string in the array, I want to create an array.
So the loop above would create three arrays: a_data,b_data,c_data
Then, I want to populate each array with "foo".
So a_data[0] => "foo"
Thanks!
Double edit:
Here's my slightly altered actual code with fuller explanation of what I'm doing:
I have a big json file of thousands of tweets (not just the text, but the full json from twitter api).
I then have an array of hashes based with topics and associated keywords -- e.g. "cooking" -> "utensils", "oven", "microwave".
I want to loop through the array of topic hashes and see if any of the topic keywords match words in the tweet text.
If there's a match, I want to add that tweet to a new array.
# topics is an array of hashes. Each hash contains title and list of keywords
topics.each do |topic|
# create an array with the topic's name to store matches
(topic[:title] + '_tweets') = []
topic[:keywords].each do |kw|
# loop through array of hashes (parsed json) to check for keyword matches in strings
tweets.each do |tweet|
text = tweet["text"]
# if string contains keyword, add to the topic's array
if text.include? kw
(topic[:title] + '_tweets') << tweet
end
end
end
Thanks for y'all's help guys!
Why not create a Hash to keep the data you need?
array = ["a", "b", "c"]
data = {}
array.each do |x|
key = x + '_data'
data[key] ||= []
data[key] << "foo"
end
Also, note data[key] ||= [] trick. It means "look into data[key]. If it is nil, initialize it with empty array". It is idiomatic way to initialize something once.
You can declare data as Hash.new([]). Then you won't need data[key] ||= [] at all, because Hash.new([]) will create a hash that returns an empty array if the value associated with the given key has not been set.
This is much more flexible than using variable variables from PHP
But if you REALLY need something like this, you can do the following:
array = ["a", "b", "c"]
array.each do |x|
instance_variable_set '#' + x + '_data', []
instance_variable_get('#' + x + '_data') << "foo"
end
p #a_data # ["foo"]
Here we create an instance variable in the context of current object instance. Its name MUST begin with #.
Related
I am using a GET API, currently passing an array as a string:
def fetch_details ids
url = "#{url}/api/v1/get-info?ids=#{ids.join(',')}"
response = Net::HTTP.get_response(URI.parse(URI.encode(url)))
if response.code.to_i == 200
return Oj.load(response.body)
else
return {}
end
end
On the server-side I am extracting id from this method:
def self.get_details(ids)
ids = ids.split(",").map {|x| x.gsub( " ", "")}
end
For each id, I want to send an array of UUIDs:
ids = [100,21,301]
uuids= {["abc","bca"],["Xyz"],["pqr","345"]}
Something like this
hash=[
100=>[abc,bca],
21=>[xyz],
301=>[pqr,345]
]
The endpoint uses the id and corresponding UUIDs to join two tables in database query so I should be able to extract the id and corresponding UUID at the end.
How do I pass both these values?
To pass an array in the parameters in Rails/Rack you need to add brackets to the name and repeat the parameter:
/api/v1/get-info?ids[]=1&ids[]=2&ids[]=3
You can use Hash#to_query from ActiveSupport to generate the query string:
irb(main):001:0> { ids: [1,2,3] }.to_query
=> "ids%5B%5D=1&ids%5B%5D=2&ids%5B%5D=3"
As pointed out by #3limin4t0r you should only use this for one-dimensional arrays of simple values like strings and numbers.
To pass a hash you use brackets but with keys in the brackets:
/api/v1/get-info?foo[bar]=1&foo[baz]=2
Again you can generate the query string with #to_query:
irb(main):002:0> { foo: { bar: 1, baz: 2 } }.to_query
=> "foo%5Bbar%5D=1&foo%5Bbaz%5D=2"
The keys can actually be numbers as well and that should be used to pass complex structures like multidimensional arrays or an array of hashes.
I have a program where a user is able to receive popular "vacation spots". Al they have to do is enter the continent (Which will bring them to that dictionary) and then enter a country/state (which is a key in a hash) and then it will find the corresponding value.
I have a required file (dict.rb) which is basically a hash module using arrays.
But the issue I have is fairly small. I assigned the user input to two variables, continent_select and country_select
Here's the code:
require './dict.rb'
#create a new dictionary called northamerica
northamerica = Dict.new
Dict.set(northamerica, "new york", "New York City")
Dict.set(northamerica, "new jersey", "Belmar")
puts "Welcome to The Vacation Hub"
puts "What continent are you interested in?"
print '> '
continent_select = $stdin.gets.chomp.downcase
continent_select.gsub!(/\A"|"\Z/, '')
puts "Which state would you like to go to in #{continent_select}"
print '> '
country_select = $stdin.gets.chomp.downcase
#puts "You should go to #{Dict.get(northamerica, "#{country_select}")}"
#=> You should go to Belmar
puts "You should go to #{Dict.get(continent_select, "#{country_select}")}"
#=> error
Ignore the get and set methods, they're in the included dict.rb
Anyway look carefully at the last few lines. The Dict.get method has two arguments. The first finds which dictionary to use. If I just put northamerica as an argument it works. But if I put continent_select instead (assuming the user enters 'northamerica') it doesn't work. I think the program is looking for a Dictionary named continent_select, rather than looking for the variable continent_select.
UPDATE
Here's the whole dict.rb for those who asked.
module Dict
#creates a new dictionary for the user
def Dict.new(num_buckets=256)
#initializes a Dict with given num of buckets
#creates aDict variable which is an empty array
#that will hold our values later
aDict = []
#loop through 0 to the number of buckets
(0...num_buckets).each do |i|
#keeps adding arrays to aDict using push method
aDict.push([])
end
return aDict
#returns [[],[],[]] => array of empty arrays reading to go.
end
def Dict.hash_key(aDict, key)
# Given a key this will create a number and then convert
# it to an index for the aDict's buckets.
return key.hash % aDict.length
#key.hash makes the key a number
# % aDict.length makes the number between 1 and 256
end
def Dict.get_bucket(aDict, key)
#given a key, find where the bucket would go
#sets the key to a number and it's put in bucket_id variable
bucket_id = Dict.hash_key(aDict, key)
#finds the key number in the dict, and returns the key
return aDict[bucket_id]
end
def Dict.get_slot(aDict, key, default=nil)
#returns the index, key, and value of a slot found in a bucket
#assigns the key name to the bucket variable
bucket = Dict.get_bucket(aDict, key)
bucket.each_with_index do |kv, i|
k, v = kv
if key == k
return i, k, v
#returns index key was found in, key, and value
end
end
return -1, key, default
end
def Dict.get(aDict, key, default=nil)
#Gets the value in a bucket for the given key, or the default
i, k, v = Dict.get_slot(aDict, key, default=default)
return v
end
def Dict.set(aDict, key, value)
#sets the key to the value, replacing any existing value
bucket = Dict.get_bucket(aDict, key)
i, k, v = Dict.get_slot(aDict, key)
if i >= 0
bucket[i] = [key, value]
else
bucket.push([key, value])
end
end
def Dict.delete(aDict, key)
#deletes. the given key from the Dict
bucket = Dict.get_bucket(aDict, key)
(0...bucket.length).each do |i|
k, v = bucket[i]
if key == k
bucket.delete_at(i)
break
end
end
end
def Dict.list(aDict)
#prints out what's in the dict
aDict.each do |bucket|
if bucket
bucket.each {|k, v| puts k, v}
end
end
end
end
Now there's some weird stuff going on.
In the first case, which seems to be okay, you pass the correct arguments:
Dict.get(northamerica, "#{country_select}")
That is: Dict instance as the first argument, and a String as the second. But then in the second case:
Dict.get(continent_select, "#{country_select}")
You pass a String instance instead of an obviously expected Dict, and this results in an error.
As far as I understand your intention, you want user input to become a variable name to be used as the first argument, but there is no way way it is magically happening, and you end you up passing just a string.
What you need to do is explicitly map a user input to a corresponding Dict object, and then use it. It can look like this:
# fetch a Dict object that corresponds to "northamerica" string from a hash
# NOTE: it will raise an exception if a user enters something that's not present
# in a hash, i.e. something other than "northamerica"
selected_continent_dict = { "northamerica" => northamerica }.fetch(continent_select)
puts "You should go to #{Dict.get(selected_continent_dict, country_select)}"
If you're prohibited to use Ruby hashes, you can easily get away with a case statement:
selected_continent_dict = case continent_select
when "northamerica"
northamerica
else
raise "Invalid continent"
end
puts "You should go to #{Dict.get(selected_continent_dict, country_select)}"
Hope this helps!
P.S. Two more advice, if you don't mind:
There's no real need for string interpolation in the second argument, and something like Dict.get(northamerica, country_select) could be a cleaner way.
Better variable naming could save you from headaches. I.e. if you renamed a (quite misleading) country_select to a user_state_selection_string it would remind you that it is a string, and of what it holds. The example is arbitrary though. There's a wonderful book called "Code Complete" by Steve McConnell which covers this and other issues much better than I do.
I have the following controller action that builds two arrays.
current_event = Event.find(params[:event_id])
campaign_titles = Relationship::CampaignTitleRelationship.where(campaign_id: current_event.campaign_id)
campaign_title_ids = Array.new
campaign_titles.each do |title|
campaign_title_ids << [title.title_id]
end
event_title_ids = Array.new
params[:title_ids].each do |title|
event_title_ids << [title]
end
The two arrays output like this
[["6556"], ["9359"], ["11319"], ["12952"], ["14389"], ["14955"], ["16823"]]
[[6556], [9359], [11319], [12952], [14389], [14955], [16823]]
I'm trying to compare these two arrays using the - symbol, but am only getting an output of each id, instead of what I expect (nothing) since both arrays contain the same items.
I can see that the first array has quotations around each key inside the bracket. The second does not. How do I compare these two arrays?
Just add params as integers to your array
params[:title_ids].each do |title|
event_title_ids << [title.to_i]
end
I'm attempting to convert MySQL timestamps in an ActiveRecord object to another timestamp format. My method takes an array of ActiveRecord records and returns an array of hashes with the timestamped fields with the formatted timestamp:
def convert_mysql_timestamps(records)
ary = []
hash = {}
records.each_with_index do |record, i|
record.attributes.each do |field, value|
if time_columns.include?(field) and value then
hash[field] = value.strftime("%Y-%m-%dT%H:%M:%S%z")
else
hash[field] = value
end
end
ary[i] = {}
ary[i] = hash
end
ary
end
However, when in the ary[i] = hash assignment, all ary elements get set to hash.
Is there a better way to convert a record's timestamp fields? (I don't need to save the records back to the database.) Also, how can I get the array to capture each individual hash representation of the record?
Input:
[#<Vehicle id: 15001, approved_at: "2011-03-28 10:16:31", entry_date: "2011-03-28 10:16:31">, #<Vehicle id: 15002, approved_at: "2011-03-28 10:16:31", entry_date: "2011-03-28 10:16:31">]
Desired output:
[{"id"=>15001, "approved_at"=>"2011-03-28T10:16:31-0700", "entry_date"=>"2011-03-28T10:16:31-0700"}, {"id"=>15002, "approved_at"=>"2011-03-28T10:16:31-0700", "entry_date"=>"2011-03-28T10:16:31-0700"}]
The problem is that you're creating one Hash:
def convert_mysql_timestamps(records)
ary = []
hash = {}
#...
and then trying to re-use for each record. You probably want a fresh Hash for each each_with_index iteration:
def convert_mysql_timestamps(records)
ary = []
records.each_with_index do |record, i|
hash = { }
record.attributes.each do |field, value|
#...
end
ary[i] = hash
end
end
You can use map for this - it's always a good option when you want to take an array in one format and produce a same-sized array in another. Here's how:
def convert_mysql_timestamps(records)
records.map do |record|
Hash[records.attributes.map{|k, v| [k, convert_mysql_timestamp(v)] }]
end
end
def convert_mysql_timestamp(field, value)
return value unless time_columns.include?(field) && value
value.strftime("%Y-%m-%dT%H:%M:%S%z")
end
It works like so:
Hash[array_of_pairs] turns an array of key-value pairs - like [["foo", 2], ["bar", 3], ...] - into a hash like {"foo" => 2, "bar" => 3, ...}.
map calls its block for each item in the collection, and collects each return value of the block into a new array, which it returns. The attributes.map inside the Hash[...] creates the array of key-value pairs, and the outer records.map collects up all the hashes into the returned array.
I'd suggest reading up on the methods in Enumerable because there are so many neat things like map in there. You will find that you almost never have to use indices in your loops, although if you're coming from another language with for loops everywhere it's a hard habit to break!
I am not sure what your time_columns are, but assuming they are Time class, you can simplify the part like value.is_a?(Time).
def convert_mysql_timestamps(records)
records.collect do |record|
# assuming records are from a Rails model, I am using #attributes
# to loop through all fields in a record
# then inject values in this hash -> ({}),
# which is in the block, named attributes
record.attributes.inject({}) do |attributes, (column_name, value)|
# if it is Time, convert it to iso8601 (slightly different from your format,
# but if this is also acceptable, your code can be simpler)
attributes[column_name] = (value.is_a?(Time) ? value.iso8601 : value)
attributes
end
end
end
I've got around 15 parameters being submitted (using tag helpers as no data related to a model). Is there a more efficient way of accessing and maybe storing the params into a hash/array as apposed to having to push all the params into a new array using something like the below:
array = []
array << param["a"]
array << param["b"]
array << param["c"]
array << param["d"]
etc..
If all you want are the values then you can do:
array = hash.values
or more specifically to your question:
param_array = param.values
values returns an array of all the values in the hash.