From: Robert Klemme Date: 2009-04-07T16:29:18+09:00 Subject: Re: how to dynamically create search term for array.find_all On 07.04.2009 03:31, Adam Akhtar wrote: > Ahh sorry Robert re: eval - categories are only half of the problem. Im > also allowing the user to filter by max and min stuff too. > > So depending on what filtering options a user has set Ill have a varying > number of conditions in my code. > > So one time it could be results.find_all{|x| x.rating <= some_parameter > && x.rating>= some_other_parameter && etc etc etc } > > Since iterating over the array only once is best for performance I > thought it would be best to call find_all once and stuff all the > required conditions into it. This was where i was stumped on how ot do > that so i thought i could use eval > > like so: > > #conditions are collected from users choices and are stored in a hash > some_conditions = {:max_rating => 10, :min_rating => 2, :categories => > [:horror, :scifi] } > > def filter (some_films, some_conditions) > > condition_string = [] > condition_string.push "x.rating <= #{some_conditions[max_rating]}" if > some_conditions[max_rating] > condition_string.push "x.rating >= #{some_conditions[min_rating}" if > some_conditions[min_rating] > > condition_string = condition_string.join(" && ") > > filtered_results = some_films.find_all{|x| eval(condition_string)} This approach is inefficient each eval is very costly: the string needs to be parsed and then executed. You could rather do this: condition_string = [] ... cond_block = eval("lambda {|x| #{condition_string.join(' && ')}}") filtered_results = some_films.find_all(&cond_block) > return filtered_results > end > > > The above code didnt filter by categories as its just for example > purposes but I was thinking of simply calling find_all again using the > code offered before rather than drafting up a complicated eval string > involving && and ||. Id love ot hear how i can better this code. See above. An alternative approach would be to implement fixed conditions and combine them, e.g. CondLess = Struct.new :cmp_val do def ===(val) val < cmp_val end end conditions = [] conditions << CondLess.new(10) << CondMore.new(1) ... filtered_results = some_films.find_all {|x| conditions.all? {|c| c === x} } Note that #all? does short circuit so evaluation continues only to the first mismatch (if there is one) much the same as && and ||. Kind regards robert