I have this in my yml file
members:
- id: 1
name: Sam
- id: 18
name: tom
After retrieving data from this file in a rails application I want to convert it to an array.
for example
id=[1,18]
name=[sam,tom]
how can I achieve this?
Currently This is how I am retrieving the data.
yml = YAML.load_file("mem.yml")
And this is how i get my data
[{"id":1, "name":"Sam"},{"id":18, "name":"tom"}]
if I use yml["members"][1]["id"] I get the first id.
I also tried writing id and name separately like below. This does give me what I want when I use yml["id"]but I don't want to use it because of its readability. BTW my data is static.
id:
- 1
- 18
name:
- Sam
- tom
Try the below:
yml = [{"id":1, "name":"Sam"},{"id":18, "name":"tom"}]
result = {}.tap do |result|
yml.each do |hash| # Iterate over array of hashes on parsed input from YAML file
hash.each do |key, value| # Iterate over each keys in the hash
result[key] ||= []
result[key] << value # Append element in the array
end
end
end
This will return the result as a hash:
{:id=>[1, 18], :name=>["Sam", "tom"]}
You can access the ids and names as
result[:id] # [1, 18]
result[:name] # ["Sam", "tom"]
Related
I am creating JSON output and want to check the value before I create the key value pair. Is there a way to do it while creating the JSON rather than adding it later?
I am creating this JSON output:
sample_json = {
abc: "abc",
xyz: "xyz",
pqr: some_value unless some_value.blank?
}
In this example I cannot check for unless some_value.blank? while creating the JSON string, so I first create the JSON and then add pqr: some_value separately after checking for a blank value, something like this:
sample_json = {
abc: "abc",
xyz: "xyz"
}
sample_json[:pqr] = some_value unless some_value.blank?
Is there is a way to add the check while creating the JSON itself like in the first example?
I don't think you can.
But, some other ways to deal with it, assuming you have active_support, you could use a hash compact (sample_json.compact), which will remove key-value pairs with nil values.
But If you do need !v.blank?, you could do:
sample_json.select { |_, value| !value.blank? }
I'd go about it like this:
require 'active_support/core_ext/object/blank'
require 'json'
some_value = 'foo'
hash = {
abc: "abc",
xyz: "xyz"
}
hash.merge!(pqr: some_value) unless some_value.blank?
puts hash.to_json
# >> {"abc":"abc","xyz":"xyz","pqr":"foo"}
If some_value = '' it'll result in:
# >> {"abc":"abc","xyz":"xyz"}
Don't create the JSON then try to change it. Instead create the base object, make the test then merge in the change, then serialize to JSON.
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 #.
Sorry for the confusing title, not sure how to describe this issue.
Inside a Ruby on Rails controller I'm creating a list named #commits, where each item in #commits should contain a hash table whose elements are the values of various properties for each commit. These property values are stored in a Redis database.
Below, I iterate through a list of properties whose values should be grabbed from Redis, and then grab those values for each of 8 different commits. Then I place the values from redis into a different hash table for each commit, using the commit property name as the key for the hash.
# Initialize #commits as a list of eight empty hash tables
#commits = Array.new(8, {})
# Iterate over the attributes that need hashed for each item in #commits
[:username, :comment, :rev, :repo].each do |attrib|
# 8 items in #commits
8.times do |i|
# Get a value from redis and store it in #commits[i]'s hash table
#commits[i][attrib] = $redis.lindex(attrib, i)
# Print the value stored in the hash
# Outputs 7, 6, .., 0 for #commits[i][:rev]
puts #commits[i][attrib].to_s
end
end
# Print the value of every item that was stored in the hash tables above,
# but only for the :rev key
# Outputs 0 eight times
8.times do |i|
puts #commits[i][:rev]
end
However, per the comments above, #commits[0..7] all seem to have the same values in their hashes, despite them being seemingly stored correctly a few lines above. Using the hash key :rev as an example, the first puts outputs 7..0, which is correct, but the second puts outputs the number 0 eight times.
Anyone know why?
It would help if you show how #commits is initialized, but it looks like you've created a structure with multiple references to the same object.
Incorrect, same object recycled for all keys:
#commits = Hash.new([ ])
Correct, new object created for each key:
#commits = Hash.new { |h, k| h[k] = [ ] }
You could be using an Array with the same mistake:
#commits = Array.new(8, [ ])
This will lead to the following behaviour:
a = Array.new(4, [ ])
a[0]
# => []
a[0] << 'x'
# => ["x"]
a
# => [["x"], ["x"], ["x"], ["x"]]
It can be fixed by passing in a block:
a = Array.new(4) { [ ] }
a[0]
# => []
a[0] << 'x'
# => ["x"]
a
# => [["x"], [], [], []]
It is highly unusual to see an array pre-initialized with values, though. Normally these are just lazy-initialized, or a Hash is used in place of an Array.
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 have such a YAML file:
Company1:
name: Something1
established: 2000
#
Company2:
name: Something2
established: 1932
reading the YAML file: (** UPDATE **)
config = YAML.load_file('file.yaml')
config.each do |key, value|
if(key == 'name')
company_name = value
#year = config['Company1']['established']
year = config.fetch(key)['established']
end
end
** UPDATE **
Now the above code is working, but it shows the result as:
company1 => {"name" => "something1"} => {"established year" => 2000"}
how can I remove the the {} and "" ?
Okay, so this is your YAML file right?
Company1:
name: Something1
established: 2000
Company2:
name: Something2
established: 1932
Okay now this YAML file actually represents a Hash. The has has two keys i.e Company1, Company2 (because they are the leading entries and the sub entries (name and established) are indented under them). The value of these two keys is again a Hash. This Hash also has 2 keys namely name and established. And they have values like Something1 and 2000 respectively etc.
So when you do,
config=YAML.load_file('file.yml')
And print config (which is a Hash representing the YAML file contents) using,
puts config
you get following output:
{"Company1"=>{"name"=>"Something1", "established"=>2000}, "Company2"=>{"name"=>"Something2", "established"=>1932}}
So we have a Hash object as described by the YAML file.
Using this Hash is pretty straight forward.
Since each company's name and year come in a separate hash held by the outer hash (company1, company2), we can iterate through the companies. The following Code prints the Hash.
config.each do |company,details|
puts company
puts "-------"
puts "Name: " + details["name"]
puts "Established: " + details["established"].to_s
puts "\n\n"
end
So in Each iteration we get access to each (key,value) of the Hash. This in first iteration we have company(key) as Company1 and details(value) as {"name"=>"Something1", "established"=>2000}
Hope this helped.
YAML uses indentation for scoping, so try, e.g.:
Company1:
name: Something1
established: 2000
Company2:
name: Something2
established: 1932