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