Passing a variable from a block back to the method

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?

On Tue, Aug 19, 2008 at 7:21 AM, James D. [email protected] 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:

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?

Untested, but what about:


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
    new_lines = yield lines
    # return if not changing anything
    return unless new_lines
    file.pos = 0
    file.print new_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 → the last statement evaluates to
nil
nil

# when you want to return the new lines

end
end

Of course you should change the logic in the block to not be within a
collect! when you want
to return nil.

Hope this helps,

Jesus.

James D. wrote:

  File.open(filename, 'r+') do |file|

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 M. wrote:

You can also use catch and throw. I think this method is a little
cleaner.

ah, I think I like the catch and throw method. That’s pretty close to
what I was visualizing in my head.

Just one thing though:

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

You left in the “return if yield(lines) == false”, was that a typo? I’m
guessing it was.

James D. wrote:

class File
file.pos = 0
file.print lines
file.truncate(file.pos)
end
end
end
end

You left in the “return if yield(lines) == false”, was that a typo? I’m
guessing it was.

Oh yeah, that is a typo left over from the other example. That line
should read just “yield lines”.

Michael M. wrote:

Oh yeah, that is a typo left over from the other example. That line
should read just “yield lines”.

That’s what I figured. Thanks a bunch. My method ended up like 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
    catch :nochanges
      yield lines
      file.pos = 0
      file.print lines
      file.truncate(file.pos)
    end
  end

end
end