Array.search

Here's an idea,

I'm making an import module for a customer and have to look up related
models all the time to link them to the imported record;

...
@item.color = Color.find_by_name(color_from_import)
...

Instead of hitting the database all the time, I'm extending the array
class with a "search" call that searches values in a preloaded list
"ActiveRecord find" style;

@colors = Color.all
...
@item.color = @colors.search(:first, :conditions => { :name =>
color_from_import })

or

@item.color = @colors.search(12)
@item.color = @colors.search(:all, :conditions => { :name => /test/})

Can someone tell me if behaviour like this is already implementent (in
core or using a plugin)? Here is my (premature) plugin code;

http://pastie.org/570795

Stijn

Uh yeah, Color.all(:conditions => { :name => color_from_import })

http://guides.rubyonrails.org/active_record_querying.html

Oh and if you want to “preload” a list I recommend using with_scope to further scope down that list.

Ryan, he is talking about a mechanism for use with already-loaded
arrays or collections, so reducing the number of queries made, as is
clear from his examples.

Regarding your next email, there is no mechanism to preload several
named_scopes on the same model with different conditions in one query,
nor would it be at all easy to do in SQL, and reducing the number of
separate queries needed is what Stinjster's interested in.

Stinjster, I'm not aware of a mechanism to do this. I think it would
sometimes be useful, but it's probably not going to get used by too
many people as most of the time people just need to load a certain set
of records, and then index them by a particular thing. For example,
it's quite common to use:

@colors_by_name = Color.all.index_by(&:name)
@item.color = @colors[color_for_import]

What you're talking about is really a more generalised mechanism for
doing Enumerable#detect calls, but I think they're already short
enough to write for the bulk of use cases, so I would say that most
people would be happy to use:

@item.color = @colors.detect {|color| color.name =~ /test}

which isn't really any more verbose than:

@item.color = @colors.search(:all, :conditions => {:name => /test/})

(And is more powerful since you can put in arbitrary ruby code.)

Could you give an example where a #search method is really a time
saver over #detect?

Hi Will,

indeed, I am looking for a way to search in already loaded sets,
instead of caching the query itself. When running an import job, I
want to prevent a hit on the database for each color lookup when I'm
importing +1000 of records.

Detect sounds great! It would indeed help me to identify all records.
So I think I'll go with that. Thanks for pointing me into that
direction.

One thing detect doesn't do (for as far as I can see/test) is return a
set that matches the detected data (e.g. when "blue" is found several
times, using a regexp, it would only return the first instance where
blue is detected). In my example above you can
specify :all, :one, :first, ... which return the appropriate result.

But again, thanks for your advice!

Stijn

Cool, glad to help.

You want Enumerable#select (aka #grep) to return an array of all the
matching results.

Making the most out of Ruby's built-in (or ActiveSupport-extended)
Enumerable functionality is a common area for improvement people see,
so I think there are some good guides out there - google should sort
you out.

Cheers,
Will

One thing detect doesn't do (for as far as I can see/test) is return a
set that matches the detected data (e.g. when "blue" is found several
times, using a regexp, it would only return the first instance where
blue is detected).

Use Enumerable#select for that: array.select { |x| x.color =~ /blue/ }

Eloy

Great!

Ruby keeps amazing me, thanks for all the tips guys.

Stijn

You can also use Enumerable#group_by to turn the array into a hash
arrays grouped by the name. That's what I usually do in this
situation. (http://api.rubyonrails.org/classes/Enumerable.html#M002570)

cheers,
steven bristol