This is my code:
require 'yaml'
class Person
attr_accessor :name, :age
end
yaml_string = <<END_OF_DATA
---
-!ruby/object:Person
age: 45
name: Jimmy
- !ruby/object:Person
age:23
name: Laura Smith
END_OF_DATA
test_data = YAML::load(yaml_string)
puts test_data[0].name
puts test_data[1].name
This is the result I get:
ruby yaml1.rb
C:/Ruby200/lib/ruby/2.0.0/psych.rb:205:in parse': (<unknown>): mapping values are not allowed in this context at line 3 column 4 (Psych::SyntaxError)
from C:/Ruby200/lib/ruby/2.0.0/psych.rb:205:inparse_stream'
from C:/Ruby200/lib/ruby/2.0.0/psych.rb:153:in parse'
from C:/Ruby200/lib/ruby/2.0.0/psych.rb:129:inload'
from yaml1.rb:17:in `'
Exit code: 1
According to the book i'm reading (Beggining Ruby by Peter Cooper). My result should be like the one below:
Jimmy
Laura Smith
Anyone know why this is happening ? What am I doing wrong ?
Your YAML is not formatted properly, I guess wrote it by hand. Here's a correct version
---
- !ruby/object:Person
age: 45
name: Jimmy
- !ruby/object:Person
age: 23
name: Laura Smith
In case you didn't spot the differences, here they are
The entries age: ... and name: ... need to be indented
A space was missing in the second line (-!ruby/object:Person) between the dash (-) and the bang (!)
A space is needed between the number 23 and the colon in the line age:23
Related
First off, I am still a novice Ruby user so this is probably a trivial question but I'm still struggling regardless.
So I have a YAML file set up like so:
userA:
{
nick: cat ,
fruit: apple ,
canDance: true ,
age: 20
}
userB:
{
nick: dog ,
fruit: orange ,
canDance: false ,
age: 23
}
Assuming that the YAML file has been loaded into Ruby, how would I be able to retrieve specific parts of this file, such as retrieving userA's fruit, or userB's canDance? Thanks in advance.
You can read the required information from your YAML like this:
require 'yaml'
people = YAML.load_file('the_filename.yaml')
puts people['userA']['fruit'] #=> 'apple'
puts people['userB']['canDance'] #=> true
Note: Your YAML file seems to be valid and can be read by the default Ruby YAML parser. But it uses a very special and uncommon syntax. I suggest writing your YAML like this:
userA:
nick: cat
fruit: apple
canDance: true
age: 20
userB:
nick: dog
fruit: orange
canDance: false
age: 23
Updated: Your sample data can be parsed as is by Ruby's standard lib YAML, however the curly braces and commas are not required.
Here is an example with some mixed types added for hobbies
test.yml
---
userA:
nick: cat
fruit: apple
canDance: true
age: 20
hobbies:
- coding
- tennis
music:
production: true
djing: true
guitar: true
userB:
nick: dog
fruit: orange
canDance: false
age: 23
hobbies:
- coding
- ruby
sports:
tennis: always
soccer: sometimes
running: rarely
Use Ruby's Yaml core lib which you can simply require.
require 'yaml'
people = File.load_file 'test.yml'
people is now an instance of Hash class which allows you to get the values of keys by calling them inside of square braces like so:
people['userA']
Now you can dig through the object by chaining keys like this:
people['userA']['hobbies']
However note that you will get an error if the chain "breaks"
people['userB']['sports']['tennis'] # this works
=>"always"
people['userA']['sports']['tennis'] # this will raise
=>NoMethodError: undefined method `[]' for nil:NilClass
Exception is raised because people['userA']['sports'] returns nil so trying to chain ['tennis'] throws the error. A useful way to avoid this when digging through a deeply nested hash is to use .dig
people.dig('userB','sports','tennis')
=>"always"
people.dig('userA','sports','tennis')
=>nil #
people.dig('userA','music','djing')
=>true
people.dig('userB','music','djing')
=>nil
With hashes who's key's are strings you can also string interpolate. Let's say we want to randomly select a user and dig through it we may do something like:
people.dig("user#{ ['A','B'].sample }",'music','djing')
Ruby version: ruby 2.0.0p576 (2014-09-19 revision 47628) [x86_64-darwin13.4.0]
I'm reading "beginning ruby" book and get stuck at translating YAML data back into working objects. (it's worth to mention that while converting working objects into YAML data works fine)
Please see code blow:
require 'yaml'
class Person
attr_accessor :name, :age
end
yaml_string = <<END_OF_DATA
---
- !ruby/object:Person
age: 45
name: Jimmy
- !ruby/object:Person
age: 23
name: Laura Smith
END_OF_DATA
error occurs
2.0.0-p576 :013"> END_OF_DATA
=> "---\n- !ruby/object:Person\nage: 45\nname: Jimmy\n- !ruby/object:Person\nage: 23\nname: Laura Smith\n"
2.0.0-p576 :014 > test_data = YAML::load(yaml_string)
Psych::SyntaxError: (<unknown>): did not find expected '-' indicator while parsing a block collection at line 2 column 1
I have done some research, intuitively,i think this link click here is saying something about this issue . However , I have no idea of what are they talking about due to my "kindergarten" level of ruby language. Wish someone can help me solve this problem and understand it completely . Thanks in advance !
You're missing some indentation. YML isn't white space agnostic. Try using this data instead:
yaml_string = <<END_OF_DATA
---
- !ruby/object:Person
age: 45
name: Jimmy
- !ruby/object:Person
age: 23
name: Laura Smith
END_OF_DATA
Given the following code (where I display some statistics about a number of bookings):
statistics = [["Germany", "EUR"], 23], [["Germany", "USD"], 42], [["Spain", "EUR"], 17]
statistics.each do |country_and_currency, number_of_bookings|
country, currency = country_and_currency # <-- Ugly.
puts "There are #{number_of_bookings} in #{currency} in #{country}"
end
The country_and_currency part is quite ugly. I tried ... do |*(country, currency), number_of_bookings|, but this did not work.
Is there an elegant way to process this nested array without using the country_and_currency variable?
Yes, it's possible:
statistics = [
[["Germany", "EUR"], 23],
[["Germany", "USD"], 42],
[["Spain", "EUR"], 17]
]
statistics.each do |(country, currency), number_of_bookings|
puts "There are #{number_of_bookings} in #{currency} in #{country}"
end
Output
There are 23 in EUR in Germany
There are 42 in USD in Germany
There are 17 in EUR in Spain
statistics.each do |(country, currency), number_of_bookings|
puts "There are #{number_of_bookings} in #{currency} in #{country}"
end
I'm trying to do the following:
open and read a file with multiple yaml docs
parse yaml docs into ruby objects
print content of each ruby object
and the code:
yml_string = Psych.dump(File.read(infile))
Psych.load_stream(yml_string) .each do |mobj|
puts "mobj:\n #{mobj}"
end
The puts prints the contents of the yml_string (multiple yaml docs) but it is one long string. How does one go about parsing each yaml doc from the yml_string and store them in to ruby objects?
The contents of infile (based on OP's comment):
---
member:
country: AF
phone1: 60 223-4564
phone2: +93 799 123-456
---
member:
country: BR
phone1: +55 55 2000 3456
phone2: 55 9000 1234
---
member:
country: CA
phone1: 604 423-4567
phone2: +1 604 423-4567
This is what I ended up with
yaml_hash = Psych.load_stream(File.read(infile))
yaml_hash.each do |member|
mem = Hash[member['member']]
end
Thank you for all your help.
require 'yaml'
require 'pp'
infile = "test.yml"
pp YAML.load_stream(File.read(infile))
# [{"member"=>
# {"country"=>"AF", "phone1"=>"60 223-4564", "phone2"=>"+93 799 123-456"}},
# {"member"=>
# {"country"=>"BR", "phone1"=>"+55 55 2000 3456", "phone2"=>"55 9000 1234"}},
# {"member"=>
# {"country"=>"CA", "phone1"=>"604 423-4567", "phone2"=>"+1 604 423-4567"}}]
On recent MRI psych is the same as yaml lib
p [RUBY_VERSION, YAML == Psych]
["2.0.0", true]
p [RUBY_VERSION, YAML == Psych]
["1.9.3", true]
I am trying to learn ruby(1.8.7). I have programmed in php for some time and I consider myself proficient in that language. I have a book which I reference, and I have looked at many introductory ruby tutorials, but I cannot figure this out.
myFiles = ['/Users/', '/bin/bash', 'Phantom.file']
myFiles.each do |cFile|
puts "File Name: #{cFile}"
if File.exists?(cFile)
puts "#{cFile} is a file"
cFileStats = File.stat(cFile)
puts cFileStats.inspect
cFileMode = cFileStats.mode.to_s()
puts "cFileMode Class: " + cFileMode.class.to_s()
puts "length of string: " + cFileMode.length.to_s()
printf("Mode: %o\n", cFileMode)
puts "User: " + cFileMode[3,1]
puts "Group: " + cFileMode[4,1]
puts "World: " + cFileMode[5,1]
else
puts "Could not find file: #{cFile}"
end
puts
puts
end
produces the following output:
File Name: /Users/
/Users/ is a file
#<File::Stat dev=0xe000004, ino=48876, mode=040755, nlink=6, uid=0, gid=80, rdev=0x0, size=204, blksize=4096, blocks=0, atime=Sun Sep 04 12:20:09 -0400 2011, mtime=Thu Sep 01 21:29:08 -0400 2011, ctime=Thu Sep 01 21:29:08 -0400 2011>
cFileMode Class: String
length of string: 5
Mode: 40755
User: 7
Group: 7
World:
File Name: /bin/bash
/bin/bash is a file
#<File::Stat dev=0xe000004, ino=8672, mode=0100555, nlink=1, uid=0, gid=0, rdev=0x0, size=1371648, blksize=4096, blocks=1272, atime=Sun Sep 04 16:24:09 -0400 2011, mtime=Mon Jul 11 14:05:45 -0400 2011, ctime=Mon Jul 11 14:05:45 -0400 2011>
cFileMode Class: String
length of string: 5
Mode: 100555
User: 3
Group: 3
World:
File Name: Phantom.file
Could not find file: Phantom.file
Wh is the string length different than expected? (Should be 5 for users, 6 for /bin/bash)? Why are the substrings not puling the correct characters. I understand World not being populated when referencing a 5 character string, but the offsets seem off, and in the case of /bin/bash 3 does not even appear in the string.
Thanks
Scott
This is a nice one.
When a number is preceeded by a 0, it is represented as octal. What you are actually getting for bin/bash:
0100755 to decimal = 33261
"33261".length = 5
And for /Users:
040755 to decimal = 16877
"16877".length = 5
Add the following line:
puts cFileMode
And you will see the error.
to_s takes an argument which is the base. If you call to_s(8) then it should work.
cFileMode = cFileStats.mode.to_s(8)
EDIT
files = ['/home/', '/bin/bash', 'filetest.rb']
files.each do |file|
puts "File Name: #{file}"
if File.exists?(file)
puts "#{file} is a file"
file_stats = File.stat(file)
puts file_stats.inspect
file_mode = file_stats.mode.to_s(8)
puts "cFileMode Class: #{file_mode.class}"
p file_mode
puts "length of string: #{file_mode.length}"
printf("Mode: #{file_mode}")
puts "User: #{file_mode[-3,1]}"
puts "Group: #{file_mode[-2,1]}"
puts "World: #{file_mode[-1,1]}"
else
puts "Could not find file: #{file}"
end
puts
puts
end
Instead of this:
puts "length of string: " + cFileMode.length.to_s()
What you want is this:
puts "length of string: #{cFile.length}"
Please do not use camel case for variables in Ruby, camel case is used on class names only, method and variable names should be written with underscores to separate multiple words.
It's also a good practice to avoid adding parameters to method calls that do not have parameters, so, instead of calling to_s() you should use to_s only.