Forum: Ruby Procs vs. nested methods and variable scope...

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Jay McGavren (Guest)
on 2007-04-10 23:46
(Received via mailing list)
I'm going to be re-using this technique at quite a few places in my
code, so I want to be certain I have it right...

I started with code like this, within a method:

   for playlist in playlists
      with_option_values('find', config) do |terms|
         if (results = playlist.Search(terms, 0))
            results.each {|track| tracks.push(track)}
         end
      end
      with_option_values('visible-find', config) do |terms|
         if (results = playlist.Search(terms, 1))
            results.each {|track| tracks.push(track)}
         end
      end
      #Repeat several more times...
   end

I of course hate repeating myself like that.  I don't want to create a
super-specialized instance or class method, though.  So I tried a
nested method:

   for playlist in playlists
      def search_playlist(playlist, option, config, field_id, tracks)
         with_option_values(option, config) do |terms|
            if (results = playlist.Search(terms, field_id))
               results.each {|track| tracks.push(track)}
            end
         end
      end
      search_playlist(playlist, 'find', config, 0, tracks)
      search_playlist(playlist, 'visible-find', config, 1, tracks)
      #Repeat several more times...
   end

...I wanted to do search_playlist(option, field_id), but I had to add
parameters for the playlist, config, and tracks as well, because they
fall out of scope when the method is called.  Yuck!

So finally, I went with a Proc, because I know a block/Proc stores all
variables that are in scope when it's defined:

   for playlist in playlists
      search_playlist = lambda {|option, field_id|
         with_option_values(option, config) do |terms|
            if (results = playlist.Search(terms, field_id))
               results.each {|track| tracks.push(track)}
            end
         end
      }
      search_playlist.call('find', 0)
      search_playlist.call('visible-find', 1)
      #Repeat several more times, with a lot less typing.
   end

search_playlist falls out of scope when the enclosing method ends, so
I think I'm safe from playlist, tracks, etc. living forever on the
stack, right?  Also, how bad is it that I'm creating the Proc inside a
(not very tight) loop?
Gary W. (Guest)
on 2007-04-11 02:52
(Received via mailing list)
On Apr 10, 2007, at 3:45 PM, Jay McGavren wrote:
>       end
>       with_option_values('visible-find', config) do |terms|
>          if (results = playlist.Search(terms, 1))
>             results.each {|track| tracks.push(track)}
>          end
>       end
>       #Repeat several more times...
>    end

What is that second argument to Search?  Is it just an index of
some sort such that 'find' is the first search (index 0) and
'visible-find' is the second search (index 1) and so on?  Also,
it is a bit strange to have a method capitalized.

If you change the definition of Search to return an empty array
instead of nil or false when it finds no matches and that second
argument to Search is just an index as I described then I think
you can wrap everything up as:

results = []
playlists.each do |playlist|
   %w{find visible-find}.each_with_index do |option, index|
     with_option_values(option, config) do |terms|
       results += playlist.Search(terms, index)
     end
   end
end

No need to create methods or procs.

I tossed the (for...in) constructs since 'each' is more Ruby-like
but you could easily use for..in if that reads better to you.

And if you like using inject:

results = playlists.inject([]) do |list, playlist|
   %w{find visible-find}.each_with_index do |option, index|
     with_option_values(option, config) do |terms|
       list += playlist.Search(terms, index)
     end
   end
end

If the second argument to Search is not strictly increasing with
the option names you could iterate over an array of values:

   [['find', 0], ['visible-find', 1], ['other-opt', 5]].each do |
option, search|
      #...
   end

If you need Search to return nil you could do something like:

       results += playlist.Search(terms, index) || []




Gary W.
Jay McGavren (Guest)
on 2007-04-11 12:11
(Received via mailing list)
On Apr 10, 3:50 pm, Gary W. <removed_email_address@domain.invalid> wrote:
> On Apr 10, 2007, at 3:45 PM, Jay McGavren wrote:
> >          if (results = playlist.Search(terms, 0))
> >             results.each {|track| tracks.push(track)}
> >          end
...
> What is that second argument to Search?  Is it just an index of
> some sort such that 'find' is the first search (index 0) and
> 'visible-find' is the second search (index 1) and so on?  Also,
> it is a bit strange to have a method capitalized.

playlist is actually a WIN32OLE object (type iTunes.Application), and
that's the form the generated methods took.  The numbers/field ID
would actually be a constant with an English name ('search all fields'
or similar in this case) if we were working in C++ and had imported a
header file.

Anyway, thanks for the pointers; I'll look 'em over and see what I can
incorporate.
This topic is locked and can not be replied to.