Erran Morad
Erran Morad

Reputation: 4753

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

Upvotes: 3

Views: 6088

Answers (3)

Jkarayusuf
Jkarayusuf

Reputation: 361

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

Upvotes: 1

knut
knut

Reputation: 27875

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'

Upvotes: 5

Rivenfall
Rivenfall

Reputation: 1273

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

Upvotes: 1

Related Questions