Ruby, writing to a YAML file, with arrays - ruby

I'm trying to save a few variables in a YAML config file.
Cool!!
However, when I try and save them, I get an error in RUBY:
undefined method `[]=' for false:FalseClass (NoMethodError)
My function should (In my head at least) be:
Does the config file exist, if not, just create a blank one.
Now that we know it exists, YAML.open it
set the new/overwriting key/value pairs
re Write the file
But, I'm getting the error above.
I'm new to Ruby (PHP bloke here), tell me where I'm being stupid please :)
def write_to_file( path_to_file, key, value, overwrite = true )
if !File.exist?(path_to_file)
File.open(path_to_file, 'a+')
end
config_file = YAML.load_file( path_to_file)
config_file[key] = value
File.open(path_to_file, 'w') { |f| YAML.dump(config_file, f) }
# I tried this commented code below too, same error..
# {|f| f.write config_file.to_yaml }
end

The problem is that you created an empty file. And the YAML parser returns false for an empty string:
YAML.load('') #=> false
Just set config_file to an empty hash when the YAML loader returned false:
config_file = YAML.load_file(path_to_file) || {}

Related

Debugging in rails Console

I have this script that i would like to test within the rails console
Gem.find_files("models/*.rb").each do |f|
filename = File.basename(f, '.*')
class_name_symbol = filename.classify.to_sym
autoload class_name_symbol, "models/#{filename}"
end
what i would like to do is print out the results in the console but can only get as far as outputting the array using
Gem.find_files("models/*.rb")
which returns this
["/home/richardlewis/.rvm/gems/ruby-1.9.3-p448#myblogView/bundler/gems/blogModels-8360dfc861ad/lib/models/portfolio_sector.rb", "/home/richardlewis/.rvm/gems/ruby-1.9.3-p448#myblogView/bundler/gems/blogModels-8360dfc861ad/lib/models/post.rb", "/home/richardlewis/.rvm/gems/ruby-1.9.3-p448#myblogView/bundler/gems/blogModels-8360dfc861ad/lib/models/image.rb", "/home/richardlewis/.rvm/gems/ruby-1.9.3-p448#myblogView/bundler/gems/blogModels-8360dfc861ad/lib/models/message.rb", "/home/richardlewis/.rvm/gems/ruby-1.9.3-p448#myblogView/bundler/gems/blogModels-8360dfc861ad/lib/models/sector.rb", "/home/richardlewis/.rvm/gems/ruby-1.9.3-p448#myblogView/bundler/gems/blogModels-8360dfc861ad/lib/models/portfolio.rb", "/home/richardlewis/.rvm/gems/ruby-1.9.3-p448#myblogView/bundler/gems/blogModels-8360dfc861ad/lib/models/category.rb"]
tips appreciated
After using answer the output is
models/portfolio_sector
models/post
models/message
models/sector
models/portfolio
models/category
=> ["/home/richardlewis/.rvm/gems/ruby-1.9.3-p448#myblogView/bundler/gems/blogModels-8360dfc861ad/lib/models/portfolio_sector.rb", "/home/richardlewis/.rvm/gems/ruby-1.9.3-p448#myblogView/bundler/gems/blogModels-8360dfc861ad/lib/models/post.rb", "/home/richardlewis/.rvm/gems/ruby-1.9.3-p448#myblogView/bundler/gems/blogModels-8360dfc861ad/lib/models/image.rb", "/home/richardlewis/.rvm/gems/ruby-1.9.3-p448#myblogView/bundler/gems/blogModels-8360dfc861ad/lib/models/message.rb", "/home/richardlewis/.rvm/gems/ruby-1.9.3-p448#myblogView/bundler/gems/blogModels-8360dfc861ad/lib/models/sector.rb", "/home/richardlewis/.rvm/gems/ruby-1.9.3-p448#myblogView/bundler/gems/blogModels-8360dfc861ad/lib/models/portfolio.rb", "/home/richardlewis/.rvm/gems/ruby-1.9.3-p448#myblogView/bundler/gems/blogModels-8360dfc861ad/lib/models/category.rb"]
not sure why the array is at the end?
Edit :
The script is supposed to take each item in the array and autoload the file contained in models/#{filename}. I would like to print out all the model/#{filename} paths in the console to ensure they are correct –
Gem.find_files("models/*.rb").each do |f|
filename = File.basename(f, '.*')
# So, instead of passing this filename to autoload, you print it. Or do both.
puts "models/#{filename}"
# class_name_symbol = filename.classify.to_sym
# autoload class_name_symbol, "models/#{filename}"
end && nil # suppress return value from `each`

Retrieve a file in Ruby

So what I am trying to do is pass a file name into a method and and check if the file is closed. What I am struggling to do is getting a file object from the file name without actually opening the file.
def file_is_closed(file_name)
file = # The method I am looking for
file.closed?
end
I have to fill in the commented part. I tried using the load_file method from the YAML module but I think that gives the content of the file instead of the actual file.
I couldn't find a method in the File module to call. Is there a method maybe that I don't know?
File#closed? returns whether that particular File object is closed, so there is no method that is going to make your current attempted solution work:
f1 = File.new("test.file")
f2 = File.new("test.file")
f1.close
f1.closed? # => true # Even though f2 still has the same file open
It would be best to retain the File object that you're using in order to ask it if it is closed, if possible.
If you really want to know if your current Ruby process has any File objects open for a particular path, something like this feels hack-ish but should mostly work:
def file_is_closed?(file_name)
ObjectSpace.each_object(File) do |f|
if File.absolute_path(f) == File.absolute_path(file_name) && !f.closed?
return false
end
end
true
end
I don't stand by that handling corner cases well, but it seems to work for me in general:
f1 = File.new("test.file")
f2 = File.new("test.file")
file_is_closed?("test.file") # => false
f1.close
file_is_closed?("test.file") # => false
f2.close
file_is_closed?("test.file") # => true
If you want to know if any process has the file open, I think you'll need to resort to something external like lsof.
For those cases where you no longer have access to the original file objects in Ruby (after fork + exec, for instance), a list of open file descriptors is available in /proc/pid/fd. Each file there is named for the file descriptor number, and is a symlink to the opened file, pipe, or socket:
# Returns hash in form fd => filename
def open_file_descriptors
Hash[
Dir.glob( File.join( '/proc', Process.pid.to_s, 'fd', '*' ) ).
map { |fn| [File.basename(fn).to_i, File.readlink(fn)] rescue [nil, nil] }.
delete_if { |fd, fn| fd.nil? or fd < 3 }
]
end
# Return IO object for the named file, or nil if it's not open
def io_for_path(path)
fd, fn = open_file_descriptors.find {|k,v| path === v}
fd.nil? ? nil : IO.for_fd(fd)
end
# close an open file
file = io_for_path('/my/open/file')
file.close unless file.nil?
The open_file_descriptors method parses the fd directory and returns a hash like {3 => '/my/open/file'}. It is then a simple matter to get the file descriptor number for the desired file, and have Ruby produce an IO object for it with for_fd.
This assumes you are on Linux, of course.

How do I check if a string is valid YAML?

I'd like to check if a string is valid YAML. I'd like to do this from within my Ruby code with a gem or library. I only have this begin/rescue clause, but it doesn't get rescued properly:
def valid_yaml_string?(config_text)
require 'open-uri'
file = open("https://github.com/TheNotary/the_notarys_linux_mint_postinstall_configuration")
hard_failing_bad_yaml = file.read
config_text = hard_failing_bad_yaml
begin
YAML.load config_text
return true
rescue
return false
end
end
I am unfortunately getting the terrible error of:
irb(main):089:0> valid_yaml_string?("b")
Psych::SyntaxError: (<unknown>): mapping values are not allowed in this context at line 6 column 19
from /home/kentos/.rvm/rubies/ruby-1.9.3-p374/lib/ruby/1.9.1/psych.rb:203:in `parse'
from /home/kentos/.rvm/rubies/ruby-1.9.3-p374/lib/ruby/1.9.1/psych.rb:203:in `parse_stream'
from /home/kentos/.rvm/rubies/ruby-1.9.3-p374/lib/ruby/1.9.1/psych.rb:151:in `parse'
from /home/kentos/.rvm/rubies/ruby-1.9.3-p374/lib/ruby/1.9.1/psych.rb:127:in `load'
from (irb):83:in `valid_yaml_string?'
from (irb):89
from /home/kentos/.rvm/rubies/ruby-1.9.3-p374/bin/irb:12:in `<main>'
Using a cleaned-up version of your code:
require 'yaml'
require 'open-uri'
URL = "https://github.com/TheNotary/the_notarys_linux_mint_postinstall_configuration"
def valid_yaml_string?(yaml)
!!YAML.load(yaml)
rescue Exception => e
STDERR.puts e.message
return false
end
puts valid_yaml_string?(open(URL).read)
I get:
(<unknown>): mapping values are not allowed in this context at line 6 column 19
false
when I run it.
The reason is, the data you are getting from that URL isn't YAML at all, it's HTML:
open('https://github.com/TheNotary/the_notarys_linux_mint_postinstall_configuration').read[0, 100]
=> " \n\n\n<!DOCTYPE html>\n<html>\n <head prefix=\"og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# githubog:"
If you only want a true/false response whether it's parsable YAML, remove this line:
STDERR.puts e.message
Unfortunately, going beyond that and determining if the string is a YAML string gets harder. You can do some sniffing, looking for some hints:
yaml[/^---/m]
will search for the YAML "document" marker, but a YAML file doesn't have to use those, nor do they have to be at the start of the file. We can add that in to tighten up the test:
!!YAML.load(yaml) && !!yaml[/^---/m]
But, even that leaves some holes, so adding in a test to see what the parser returns can help even more. YAML could return an Fixnum, String, an Array or a Hash, but if you already know what to expect, you can check to see what YAML wants to return. For instance:
YAML.load(({}).to_yaml).class
=> Hash
YAML.load(({}).to_yaml).instance_of?(Hash)
=> true
So, you could look for a Hash:
parsed_yaml = YAML.load(yaml)
!!yaml[/^---/m] && parsed_yaml.instance_of(Hash)
Replace Hash with whatever type you think you should get.
There might be even better ways to sniff it out, but those are what I'd try first.

Why does exporting data to CSV give only numbers?

I am trying to export a mongo strucuture to CSV with the following code:
file = Tempfile.new(['genreport','.csv'],file_path)
file_name = file.path()
CSV.open(file_name,"w") do |csv|
result_cursor.each do |eachdoc|
eachdoc.each do |key,value|
csv<<key.to_s
csv<<value.to_s
end
csv<<"\n"
end
end
The CSV file is created as expected, but it is full of numbers only. What am I doing wrong?
Here are the types:
result_cursor is a mongo cursor, eachdoc will be a hash, and key and value will be a String.
I'm not sure how your code differs or alters the context of whats posted, but when I try to run the code as is, I get an exception (undefined method `map' for 'value of key'). However, when I do this, it works fine.
file = Tempfile.new(['genreport','.csv'],file_path)
file_name = file.path()
CSV.open(file_name,"w") do |csv|
result_cursor.each do |eachdoc|
eachdoc.each do |key,value|
csv << [key, value]
end
end
end
That really doesn't help explain the numbers you're seeing though. Perhaps something else is overriding the contents of the temp file.

Trying to open a file in Ruby - Getting TypeError: can't convert String into Integer

Not sure whats going on here, or what could be the integer in this case. Here's the code:
def build_array_from_file(filename)
contents = []
File.read(File.expand_path('lib/project_euler/' + filename), 'r') do |file|
while line = file.get
contents << line
end
end
contents
end
filename is a string and I've checked to make sure the path comes up valid.
Any thoughts? Thanks.
File.read has no second argument for mode nor block, that's File.open:
contents_string = File.read(File.expand_path('lib/project_euler/' + filename))
Note that you can also write:
contents = File.open(path).lines # returns a lazy enumerator, keeps the file open
Or:
contents = File.readlines(path) # returns an array, the file is closed.
File.read doesn't need the mode r - you already request 'read' in File.read. The parameters fo File.read are - after the filename - the offset and length (that's why a integer was expected in the error message).
You may give the mode as File.read(filename, :mode => 'r') This may be usefull, if you need the mode rb or r:utf-8 (but there is also a encoding-option).

Resources