From: Michael Morin Date: 2008-08-19T15:36:31+09:00 Subject: Re: passing a variable from a block back to the method James Dinkel wrote: > I have several files that I have to read, check the contents, and then > write any changes. I overloaded the File class to help me out with > this: > > ---------------------------------------------------------- > class File > > def self.change(filename) > > # read the file, execute a block, then write the file > File.open(filename, 'r+') do |file| > lines = file.readlines > yield lines > # return if not changing anything > file.pos = 0 > file.print lines > file.truncate(file.pos) > end > end > end > ---------------------------------------------------------- > > The method is called with: > > ---------------------------------------------------------- > File.change('awesomefile') do |contents| > contents.collect! do |line| > # magic happens > # decide not to change anything > end > end > ---------------------------------------------------------- > > This is a huge waste if the files don't change (as they often don't). > The problem I'm having is with the "# decide not to change anything" in > the block and passing that back to the method so "# return if not > changing anything" knows the answer. I'm not sure if I can pass an > instance variable around to do this? I don't really know how that would > work since I don't really have an instance of file to work with. > > Maybe someone has some ideas? This works well. However, you have to be careful the last statement in your block doesn't accidentally evaluate to nil, so I added a true at the end just in case. class File def self.change(filename) # read the file, execute a block, then write the file File.open(filename, 'r+') do|file| lines = file.readlines # return if not changing anything return if yield(lines) == false file.pos = 0 file.print lines file.truncate(file.pos) end end end File.change('test.txt') do|contents| break false if contents.size < 5 contents.each do|c| c.gsub!(/[[:digit:]]/, 'X') end true end You can also use catch and throw. I think this method is a little cleaner. class File def self.change(filename) catch(:nochanges) do # read the file, execute a block, then write the file File.open(filename, 'r+') do|file| lines = file.readlines # return if not changing anything return if yield(lines) == false file.pos = 0 file.print lines file.truncate(file.pos) end end end end File.change('test.txt') do|contents| throw :nochanges if contents.size < 5 contents.each do|c| c.gsub!(/[[:digit:]]/, 'X') end end -- Michael Morin Guide to Ruby http://ruby.about.com/ Become an About.com Guide: beaguide.about.com About.com is part of the New York Times Company