like the following line of code
sites = YAML::load(File.open(SITESPATH))
is it necessary to change to
File.open(SITESPATH) do |file|
sites = YAML::load(file)
end
just in order to make it sure that file got closed?
Yes, you should close the file, so your second example is the correct one.
Just as a side-note, remember that the sites variable will not be visible outside the block, unless you already created it before the block.
Because IO.open, when called with block, returns the value of the block, you may use:
sites = File.open(SITESPATH) {|file| YAML::load(file) }
You could use YAML.load_file(filename) instead.
This isn't really about YAML::load so much as it is about File/IO streams generally.
Called without a block, File.open is exactly the same as File.new. This doesn't close a file on its own, so you would need to close it yourself.
From the documentation:
With no associated block, [File.]open is a synonym for IO.new. If the
optional code block is given, it will be passed io as an argument,
and the IO object will automatically be closed when the block
terminates. In this instance, IO.open returns the value of the block.
Related
Suppose two kinds of ruby File operations.
Firstly,
file = File.open("xxx")
file.close
Secondly,
file = File.read("xxx")
file.close
It's known to all that we should close file after we finish using it. But, in the second block of code, Ruby interpreter throw an error message shown below:
in `<main>': undefined method `close' for #<String:0x000000022a3a08> (NoMethodError)
I need not to use file.close in the second case? I wonder why?
It's because File.read method returns string with content of the file, not the File object. And yes, you don't need to use close explicitly if you use File.read method, because ruby does it for you automatically.
Marek Lipka answered correctly, I just wanted you to point to the documentation sentences again.
I need not to use file.close in the second case?
You don't need to do so.
Read the doc IO::read carefully :
Opens the file, optionally seeks to the given offset, then returns length bytes (defaulting to the rest of the file). read ensures the file is closed before returning.
Sometimes I found myself need to open a file, read it content and do some functional manipulation and store the data to an variable. This would end up with the following line of code:
#some_vars = File.open("items.txt").read.chomp!.split(',')
I have two questions here:
Does the File instance File.open() closed after this line?
How to close such a File instance without sacrificing the readability?
No, File.open leaves the file handle open. You should use IO.read instead, which returns the entire contents of the file and closes it when it's done:
IO.read("items.txt").chomp!.split(',')
This is bit shorter for one-liners than passing a block to File.open.
The example you posted will not close the file descriptor automatically. You would have to manually call File#close on the descriptor, or let Ruby close the file automatically when the interpreter exits.
If you want to automatically close a file, you need the File#open block syntax:
File.open('items.txt') { |f| f.read.chomp!.split(',') }
Ruby will then close the file whenever the block terminates.
Even using block in File.open doesn't make sure that file will be always closed, for example, when the application is suspended.
There's a trick to protect from this kind of situation.
f = File.open('items.txt', 'w')
at_exit { f.flush; f.close }
Then at_exit block will be executed at the end of application or when the program exits.
I hope I am not repeating anyone here, but I have been searching google and here and not coming up with anything. This question is really more a matter of "sexifying" my code.
What I am specifically trying to do is this:
Dir.new('some_directory').each do |file|
# is there a way to refer to the string 'some_directory' via a method or variable?
end
Thanks!
Not in general; it's totally up to the method itself what arguments the block gets called with, and by the time each has been called (which calls your block), the fact that the string 'some_directory' was passed to Dir.new has been long forgotten, i.e. they're quite separate things.
You can do something like this, though:
Dir.new(my_dir = 'some_directory').each do |file|
puts "#{my_dir} contains #{file}"
end
The reason it won't work is that new and each are two different methods so they don't have access to each others' parameters. To 'sexify' your code, you could consider creating a new method to contain the two method calls and pass the repeated parameter to that:
def do_something(dir)
Dir.new(dir).each do |file|
# use dir in some way
end
end
The fact that creating a new method has such a low overhead means it's entirely reasonable to create one for as small a chunk of code as this - and is one of the many reasons that make Ruby such a pleasure of a language to work with.
Just break it out into a variable. Ruby blocks are closures so they will have access -
dir = 'some_directory'
Dir.new(dir).each do |file|
# use dir here as expected.
end
I am new to Ruby and am learning from reading an already written code.
I encounter this code:
label = TkLabel.new(#root) do
text 'Current Score: '
background 'lightblue'
end
What is the semantics of the syntax "do" above?
I played around with it and it seems like creating a TkLabel object then set its class variable text and background to be what specified in quote. However when I tried to do the same thing to a class I created, that didn't work.
Oh yeah, also about passing hash into function, such as
object.function('argument1'=>123, 'argument2'=>321)
How do I make a function that accepts that kind of argument?
Thanks in advance
What you're looking at is commonly referred to as a DSL, or Domain Specific Language.
At first glance it may not be clear why the code you see works, as text and background are seemingly undefined, but the trick here is that that code is actually evaluated in a scope in which they are. At it's simplest, the code driving it might look something like this:
class TkLabel
def initialize(root, &block)
#root = root
if block
# the code inside the block in your app is actually
# evaluated in the scope of the new instance of TkLabel
instance_eval(&block)
end
end
def text(value)
# set the text
end
def background(value)
# set the background
end
end
Second question first: that's just a hash. Create a function that accepts a single argument, and treat it like a hash.
The "semantics" are that initialize accepts a block (the do...end bit), and some methods accepting string parameters to set specific attributes.
Without knowing how you tried to do it, it's difficult to go much beyond that. Here are a few, possible, references that might help you over some initial hurdles.
Ruby is pretty decent at making miniature, internal DSLs because of its ability to accepts blocks and its forgiving (if arcane at times) syntax.
What does 'a' and |f| mean below ?
open('myfile.out', 'a') { |f|
f.puts "Hello, world."
}
From the ruby IO doc:
"a" | Write-only, starts at end of file if file exists,
| otherwise creates a new file for writing.
The |f| is a variable that holds the IO object in the block (everything in the {}). So when you f.puts "Hello World" you're calling puts on the IO object which then writes to the file.
The 'a' is just a file open mode, like you'd see in C / C++. It means append, and is relatively uncommon - you're more likely to be familiar with 'r' (read), 'w' (write), etc.
The {|f| ... } bit is the exciting part. It's called a a block - they're everywhere, and they're probably my favourite part of Ruby - I've gone back to C++ recently, and I find myself cursing the language for not supporting them all the time.
Think of code like foo(bar) {|baz| ... } as creating a nameless function, and passing that function as another (hidden) argument to foo (kinda like this is a hidden argument to member functions in C++) - it's just not as hidden, 'cause you specify it right there.
Now, when you pass the block to foo, it will eventually call your block (using the yield statement), and it will supply the argument baz. If my foo behaved like your File.open function, its definition would look something like this:
def foo(filename, &block)
file = File.open(filename)
yield(file)
file.close
end
You can see how it opens the file, passes it to your block with yield, and then closes the file once your block returns. Very convenient - blocks are your friends!
Another good place to start wrapping your head around them is the each function - one of the simplest and most common block functions in Ruby:
[holt#Michaela ~]$ irb
irb(main):001:0> ['Welcome', 'to', 'Ruby!'].each {|word| puts word}
Welcome
to
Ruby!
=> ["Welcome", "to", "Ruby!"]
irb(main):002:0>
This time, your block gets called three times, and each time a different array element gets yielded to your block as word - it's a super-simple way to call a function for every element of an array.
Hope this helps, and welcome to Ruby!
'a' -> Mode in which to open the file ('append' mode)
f is a parameter to the block. A block is a piece of code that can be executed (it is a Proc object underneath).
Here, f will be the file descriptor, I think.
1) You call the open method, passing in the two arguments:
myfile.out <-- This is your file that you want to access
a <-- you are stating that you want to write to a file, starting at the end of the file(aka append)
2) The method open that exists in Kernel, yields an IO stream object aka |f|, in which you can access throughout your block.
3) You are appending "hello world" to myfile.out
4) Once the block ends, the IO stream closes.
The 'a', which stands for append, opens the file in write-only mode and starts writing at the end of the file. If no file exists, a new file is created. Please see the Ruby Docs for more information.
The |f| is a block parameter, which is being passed within the {}. For more information on blocks, please see The Pragmatic Programmer's Guide.
I would highly suggest reading through the help file for the File class for starters.
You can see there the documentation for the open method.
The method signature is File.open(filename, mode)
So, in your example, a, is the mode which in this case is append. Here's a list of valid values for the mode argument:
'r' - Open a file for reading. The file must exist.
'w' - Create an empty file for writing. If a file with the same name already exists its content is erased and the file is treated as a new empty file.
'a' - Append to a file. Writing operations append data at the end of the file. The file is created if it does not exist.
'r+' - Open a file for update both reading and writing. The file must exist.
'w+' - Create an empty file for both reading and writing. If a file with the same name already exists its content is erased and the file is treated as a new empty file.
'a+' - Open a file for reading and appending. All writing operations are performed at the end of the file, protecting the previous content to be overwritten. You can reposition (fseek, rewind) the internal pointer to anywhere in the file for reading, but writing operations will move it back to the end of file. The file is created if it does not exist.
If File.open is used in a block, such as in your example, then f becomes the block variable that points to the newly-opened file, which allows you to both read and write to the file just using f as the reference, while within the block. Using this form of File.open is nice because it handles closing the file automatically when the block ends.
open('myfile.out', 'a') -> Here 'a' means Write only access. Pointer is positioned at end of file.
|f| is the file descriptor, it does puts of "Hello, World."
Instead of |f|, you can write anything, say |abc| or |line|, it doesn't matter.