Iterate a yaml array with ruby - ruby

I applied YAML.load_file to my example file:
---
languages:
- name: "English"
iso_639: "en"
native_name: "English"
region:
- ''
- UK
- US
- name: "Klingon"
iso_639: "tlh"
native_name: "tlhIngan Hol"
region:
- notearth
I want to iterate though these languages and the region arrays. This doesn't work:
records.each do |record|
record.region.each do |region|
self.create!
end
end
record.region gives me an unknown method error for region. How can I iterate though the languages and and their regions? Or, how can I access the region array?

There are two errors in your code:
The object you get after loading the YAML file is not an array, it's a hash, say the file is called foo.yml:
YAML.load_file('foo.yml')
# => {"languages"=>[{"name"=>"English", "iso_639"=>"en", ...
Thus you have to modify your code like the following to make it work:
records['languages'].each do |record|
# ...
region is not a method of the hash record, it is a key, you have to access the related value using record['region'].
The correct code you have to use is:
records['languages'].each do |record|
record['region'].each do |region|
# My guess is you are going to use `region` inside this block
self.create!
end
end

Yaml is loaded into a hash, hence it will be in form:
languages: [
{
name: "English"
iso_639: "en"
native_name: "English"
region: ['', 'UK', 'US']
}
{
name: "Klingon"
iso_639: "tlh"
native_name: "tlhIngan Hol"
region: ['notearth']
}]
So you need to iterate like:
results = YAML.load_file(file)
results['languages'].flat_map{|l| l['region']}.each do |region|
self.create!
end

CONFIG = YAML.load_file("file.yml")
puts CONFIG # {"languages"=>[{"name"=>"English", "iso_639"=>"en", "native_name"=>"English", "region"=>["", "UK", "US"]}, {"name"=>"Klingon", "iso_639"=>"tlh", "native_name"=>"tlhIngan Hol", "region"=>["notearth"]}]}
CONFIG['languages'].map{|l| l['region']}

Related

Ruby exclude specific data from array of hashes

I've got response which hash and array of hashes:
"id"=>67547,
"description"=>"project",
"actors"=>
[
{"id"=>123,
"displayName"=>"John Doe",
"type"=>"atlassian-user-role-actor",
"name"=>"john.doe",
"actorUser"=>{"accountId"=>"some_id"}},
{"id"=>456,
"displayName"=>"Chris Sth",
"type"=>"atlassian-user-role-actor",
"name"=>"chris.sth",
"actorUser"=>{"accountId"=>"some_id"}},
{"id"=>789,
"displayName"=>"Testing Name",
"type"=>"atlassian-user-role-actor",
"name"=>"testing.name",
"actorUser"=>{"accountId"=>"some_id"}},
]
What I need is to pull the name for each hash['actors'] and convert it to the email address. The thing is I need to skip names which are defined as EXCLUDED_NAMES
EXCLUDED_NAMES = %w[
chris.sth
removed1258986304
john.doe
other.names
].freeze
private_constant :DEFAULT_EXCLUDED_NAMES
I was trying to something like below but still get all names:
def setup_email
dev_role['actors'].map do |user|
if user.include?(EXCLUDED_NAMES)
user.delete
else
"#{user['name']}#example.com"
end
end
end
You can get an array of valid emails with:
emails = dev_role['actors'].map do |user|
"#{user['name']}#example.com" unless EXCLUDED_NAMES.include?(user['name'])
end
Array will only contain 'testing.name#example.com'
If dev_role['actors'] is this:
[
{"id"=>123,
"displayName"=>"John Doe",
"type"=>"atlassian-user-role-actor",
"name"=>"john.doe",
"actorUser"=>{"accountId"=>"some_id"}},
{"id"=>456,
"displayName"=>"Chris Sth",
"type"=>"atlassian-user-role-actor",
"name"=>"chris.sth",
"actorUser"=>{"accountId"=>"some_id"}},
{"id"=>789,
"displayName"=>"Testing Name",
"type"=>"atlassian-user-role-actor",
"name"=>"testing.name",
"actorUser"=>{"accountId"=>"some_id"}},
]
then it is certain that user in each block would be a Hash object:
{
"id"=>123,
"displayName"=>"John Doe",
"type"=>"atlassian-user-role-actor",
"name"=>"john.doe",
"actorUser"=>{"accountId"=>"some_id"}
}
So, doing user["name"], should produce: "john.doe".
Now, that we have an exclusion list EXCLUDED_NAMES we could use include? like so on it:
EXCLUDED_NAMES.include?(user["name"])
=> # true if the name is in the EXCLUDED_NAMES
So, all you need is a small change in your code to fix the condition:
def setup_email
dev_role['actors'].map do |user|
if EXCLUDED_NAMES.include?(user["name"])
user.delete
else
"#{user['name']}#example.com"
end
end
end
There is one problem though, the user.delete would not work as it expects an argument that is supposed to be a key to the hash object.
This can be fixed through by using reject or select(changing to reject as it reads better):
def setup_email
dev_role['actors'].reject do |user|
EXCLUDED_NAMES.include?(user["name"])
end.map{ |user| user["name"] }
end
The nature of the method seems to be returning an array/list, so I would insist that the name of such methods should be plural: setup_emails.
I'd create a lookup hash based upon the the actor name. Then retrieve the values that are not in EXCLUDED_NAMES.
When actors can contain duplicate names:
actors = dev_role['actors'].group_by { |actor| actor['name'] }
actors = actors.values_at(*actors.keys - EXCLUDED_NAMES).flatten(1)
When actors can't contain duplicate names:
actors = dev_role['actors'].to_h { |actor| [actor['name'], actor] }
actors = actors.values_at(*actors.keys - EXCLUDED_NAMES)
Then:
emails = actors.map { |actor| "#{actor['name']}#example.com" }
You could also solve this with an Array#reject/Array#map combination:
emails = dev_role['actors']
.reject { |actor| EXCLUDED_NAMES.include?(actor['name']) }
.map { |actor| "#{actor['name']}#example.com" }
The above might be slower when using a large EXCLUDED_NAMES array.
dev_role=dev_role.to_hash
actors=dev_role["actors"]
for each_actor in actors
if EXCLUDED_NAMES.include?(each_actor["name"])==false
p "#{each_actor['name']}#example.com"
end
end

Ruby. is there a way to construct a command and send it to execution?

The following code is giving me AWS API timeout error from time to time.
It transfers aws volume tags to snapshot tags one by one. I think this + other api requests overload api queue.
I wonder is there any way to construct ec2Client.create_tags command with ALL tags in it and execute it instead of looping it for each tag like below?
inst.tags.each do |tag|
puts "Tag Key : #{tag.key} , Tag Value : #{tag.value}\n"
if tag.key.downcase != "backup"
ec2Client.create_tags({
dry_run: $dry_run,
resources: [newImage.image_id],
tags: [{
key: "#{tag.key}",
value: "#{tag.value}",
},],
})
end
sleep (1.0/2.0)
end
I haven't tested this, but something like this should return an array of all the tags and pass it to the create_tags method.
def create_tags(tags)
tag_list = []
tags.each do |tag|
puts "Tag Key : #{tag.key} , Tag Value : #{tag.value}\n"
unless tag.key.casecmp('backup').zero?
tag_list << { key: tag.key.to_s, value: tag.value.to_s }
end
end
end
ec2Client.create_tags(dry_run: $dry_run,
resources: [newImage.image_id],
tags: create_tags(inst.tags))

Which is the correct way to dynamically use variating static data

Following basic principles what would be the preferred way to structure a program and its data that can variate but is used to accomplish the same "standard" task? It should easily be extendable to include more providers.
For example:
require 'mechanize'
require 'open-uri'
class AccountFetcher
##providers = {}
##providers['provider_a'] = [
'http://url_for_login_form.com/', 'name_of_form',
# the number of fields can vary between different providers
[ ['name_of_field', 'value_for_it'], ['another_field', 'another_value'] ],
['name for username field', 'name for password field']
]
##providers['provider_b'] = [
'http://another_url.com/', 'another_form_name',
[
# array of more fields that are specific to this form
],
['name for username field', 'name for password field']
]
# looks more nice using AccountFetcher.accounts opposed to ##accounts
def self.accounts
##providers
end
# example of a method that uses information that can vary
def self.fetch_form(agent, account_type)
# the first element in the array will always be the url
page = agent.get(AccountFetcher.accounts[account_type][0])
# the second element in the array will always be the name of the form
form = page.form(AccountFetcher.accounts[account_type][1])
end
def self.initialize_form(agent, account_type)
form = AccountFetcher.fetch_form(agent, account_type)
# will cycle through all fields that require 'pre-filling'
AccountFetcher.accounts[account_type][2].each do |f|
# f[0] will always be the name of the field and f[1] will
# always be the value for the field; however, the amount of
# fields may vary
form.field_with(:name => f[0]).value = f[1]
end
form
end
So the general idea is use a class variable that consists of a hash and has arrays containing the appropriate data elements. I would need to do this for every class that has similar functions.
Another idea I had would be to replace the class variables and instead turn each provider into a class database that can be accessed by the other classes. So provider_a would become:
class ProviderA
# all the possible information needed
# methods will have similar names across all the different providers
end
And the appropriate provider can be chosen by
class Providers
##p_providers = {}
##p_providers['provider a'] = ProviderA.new
##p_providers['provider b'] = ProviderB.new
##p_providers['provider c'] = ProviderC.new
##p_providers['provider d'] = ProviderD.new
def self.return_provider(name)
##p_providers[name]
end
end
Is the former or latter solution more appropriate? Or is there a more 'ruby-ish' solution?
I would store this configuration values in an external YAML file. Your configuration file could looke like this:
# config/providers.yaml
provider_a:
url: 'http://url_for_login_form.com/'
name: 'name_of_form'
fields:
- name: 'name_of_field'
value: 'value_for_it'
- name: 'another_field'
value: 'another_value'
provider_b:
url: 'http://...'
...
You could load that file with YAML.file_file that returns nested hash in this example.
require 'yaml'
def AccountFetcher
def self.accounts
#accounts ||= YAML.parse_file("config/providers.yaml")
end
#...
end
You may want to consider using a Gem like has_configuration that makes handling the data structure a bit easier.

Extracting data from yml files with ruby

I read some quick tutorials on Yaml or yml file format. I made a yaml document to represent my data. I saw some ruby tutorials which tell you how to extract yaml with ruby. Unfortunately, they just print the whole data or just keys and values. It does not meet my needs. Please help.
yaml file -
dev:
game1:
server1:
url: 'dev-game1-a-srv01.gamer.com'
log-path: '/srv/logs'
server2:
url: 'dev-game1-a-srv02.gamer.com'
log-path: '/srv/logs'
game2:
server1:
url: 'dev-game2-a-srv01.gamer.com'
log-path: '/srv/logs'
server2:
url: 'dev-game2-b-srv02.gamer.com'
log-path: '/srv/logs'
server3:
url: 'dev-game2-b-srv01.gamer.com'
log-path: '/srv/logs'
prod:
etc....
How do I select dev, game2, server 3, url using ruby code ?
Using the code below, I get an exception -
require 'yaml'
def server_info
path = 'C:\Code\demo-srv.yml'
yml = YAML::load(File.open(path))
game2 = yml['dev']['game2']
game2.each{|server|
if server['server3']
puts server['server3']['url']
end
}
end
server_info
error -
server.rb:8:in `[]': can't convert String into Integer (TypeError)
from server.rb:8:in `server_info'
from server.rb:7:in `each'
from server.rb:7:in `server_info'
from server.rb:14
Did you define the yaml-data or are you only the consumer of an existing yaml-file?
If you defined it, I would replace the array of servers with a Hash (see the missing - before the server names):
dev:
game1:
server1:
url: 'dev-game1-a-srv01.gamer.com'
log-path: '/srv/logs'
server2:
url: 'dev-game1-a-srv02.gamer.com'
log-path: '/srv/logs'
game2:
server1:
url: 'dev-game2-a-srv01.gamer.com'
log-path: '/srv/logs'
server2:
url: 'dev-game2-b-srv02.gamer.com'
log-path: '/srv/logs'
server3:
url: 'dev-game2-b-srv01.gamer.com'
log-path: '/srv/logs'
Then you can try yml['dev']['game2']['server3']['url'].
Attention: There are no checks for missing/wrong data. if the entry for game2 would miss, this code will raise an exception.
So, maybe you shoudl do something like
if yml['dev'] and yml['dev'].kind_of?(Hash)
if yml['dev']['game2'] and ....
...
else
puts "No dev-branch defined"
end
Else you can try something like:
def server_info
yml = YAML::load(DATA)
yml['dev']['game2'].each{|server|
if server['server3']
p server['server3']['url']
end
}
end
Attention (for both solutions):
There are no checks for missing/wrong data. The existence of server['server3'] is checked here. For real code, you should also check the existence of the dev and game2 data.
Answer continuation after edit:
The error convert String into Integer is often thrown if you have an array but expect a hash and you try to access an array element with a string.
You can try the following code. There are two changes:
line 8 contains the output of server - you will see it is an array, no hash.
line 9+10: The array is checked and used by its two elements (via #first and #last)
require 'yaml'
def server_info
path = 'C:\Code\demo-srv.yml'
#~ yml = YAML::load(File.open(path))
yml = YAML::load(DATA)
game2 = yml['dev']['game2']
game2.each{|server|
p server #-> you get an array
if server.first == 'server3'
puts server.last['url']
end
}
end
server_info
The file -
dev:
game1:
server1:
url: 'dev-game1-a-srv01.gamer.com'
log-path: '/srv/logs'
server2:
url: 'dev-game1-a-srv02.gamer.com'
log-path: '/srv/logs'
game2:
server1:
url: 'dev-game2-a-srv01.gamer.com'
log-path: '/srv/logs'
server2:
url: 'dev-game2-b-srv02.gamer.com'
log-path: '/srv/logs'
server3:
url: 'dev-game2-b-srv01.gamer.com'
log-path: '/srv/logs'
I don't know why but yml['dev']['game2']=>
[{"server1"=>{"url"=>"dev-game2-a-srv01.gamer.com", "log-path"=>"/srv/logs"}},
{"server2"=>{"url"=>"dev-game2-b-srv02.gamer.com", "log-path"=>"/srv/logs"}},
{"server3"=>{"url"=>"dev-game2-b-srv01.gamer.com", "log-path"=>"/srv/logs"}}]
So you have to use find on this Array to have the key.
require 'yaml'
# require 'pry'
def server3_url
yml = YAML::load(File.read('yaml.yml'))
# binding.pry
begin
yml['dev']['game2'].find{|x| x['server3']}['server3']['url']
rescue
end
end
puts server3_url
server3_url will return nil if it doesn't find a key
Changing your code the following should fix the issue.
# ...
game2.each{ |server, data|
if server == 'server3'
puts data['url']
end
}
# ...
You are encountering the type error because the yielded value server is an array, not a hash. This is happening because you are calling each on the game2 variable, which is a hash, and only yielding to a single variable.
Examples
hash = { one: 1, two: 2, three: 3 }
Yielding to a single variable
When Hash#each is called with only one variable, the current key and value are assigned to that variable as an array in the order [key, value]
hash.each do |number|
puts number.inspect
end
# Prints
# [:one, 1]
# [:two, 2]
# [:three, 3]
Yielding to multiple variables
When Hash#each is called with two variables, the current key will be assigned to the first variable, and the current value will be assigned to the second.
hash.each do |key, value|
puts "Key: #{key}; Value: #{value}"
end
# Prints:
# Key: one; Value: 1
# Key: two; Value: 2
# Key: three; Value: 3

How to get params value based on specific label?

I am parsing JSON and passing it as fields_array to render an erb template. This is a Sinatra app.
I have:
private
def fields_params
# example of parsed JSON, Company Name sometimes is Field6 but sometimes Field3
[["Company Name", "Field6"], ["Email", "Field5"]]
end
def company_name
# I want to return company name from params[company_field_id]
# Maybe something like:
id = fields_params.select{|field| field[0] == "Company Name" }.flatten[1]
params[id]
end
def fields_array
fields_params.collect do |label, param_id|
{ label: label, value: params[param_id] } if params[param_id]
end
end
How should I get company_name from params?
[["Company Name", "Field6"], ["Email", "Field5"]] is a commonly seen data pattern, and, once you recognize it you'll know it can easily be coerced into a Hash:
hash = Hash[[["Company Name", "Field6"], ["Email", "Field5"]]]
Here's what it looks like now:
{
"Company Name" => "Field6",
"Email" => "Field5"
}
At that point, it's easy to get the value:
hash['Company Name']
=> "Field6"
So, I'd modify your code to return a hash, making it a lot easier to retrieve values:
def fields_params
# example of parsed JSON, Company Name sometimes is Field6 but sometimes Field3
Hash[ [["Company Name", "Field6"], ["Email", "Field5"]] ]
end
A lot of the time the JSON I see is already going to result in a Hash of some sort after parsing. Without seeing your input JSON I can't say for sure, but it could already be in that format, and something you're doing is turning it into an array of arrays, which is what a hash looks like if run through map or collect or has had to_a applied to it.
Use the find method
fields_params.find{|x| x.first == "Company Name"}.last # => "Field6"

Resources