Ruby Forum Ruby > passing a variable from a block back to the method

Posted by James Dinkel (jdinkel)
on 19.08.2008 07:25
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?
Posted by Jesús Gabriel y Galán (Guest)
on 19.08.2008 08:17
(Received via mailing list)
On Tue, Aug 19, 2008 at 7:21 AM, James Dinkel <jdinkel@gmail.com> 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.
Posted by Michael Morin (Guest)
on 19.08.2008 08:40
(Received via mailing list)
James Dinkel 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
Posted by James Dinkel (jdinkel)
on 19.08.2008 15:40
Michael Morin 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.
Posted by Michael Morin (Guest)
on 20.08.2008 01:54
(Received via mailing list)
James Dinkel 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".
Posted by James Dinkel (jdinkel)
on 20.08.2008 17:04
Michael Morin 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
----------------------------------------------------------