I have a table, with increasing values each row (year in the code below). I have a target, that specifies a "threshold". The target is user defined, it can contain a value for one or multiple columns of the table. This means you never know how many columns are specified in the target. I want to match the first row in the table, where the values in the row are greater than the values in the target. I currently have this:
class Target < ActiveRecord::Base
def loop_sheets(sheets, year_array)
result = nil
elements = self.class.column_names[1..-3].map(&:to_sym)
to_match = elements.select{|e| self.send(e) != nil }
condition = to_match.map do |attr|
"row[:#{attr}] > #{attr}"
end.join " and "
year_array.each do |year|
sheets.each do |sheet|
row = sheet.calculation.datatable.select { |r| r[:year] == year }.first
does_match = eval(condition)
if does_match
result = {
:year => row[:year],
:sheet_name => sheet.name
}
return result
end
end
end
return result
end
end
This works perfectly, but now the algorithm is fixed to use AND matching. I want to support OR matching as well as AND matching. Also I want to avoid using eval, there has to be a more elegant way. Also I want to reduce the complexity of this code as much as possible. How could I rewrite this code to meet these requirements? Any suggestion is appreciated.
To avoid using eval: Ruby can create code dynamically, so do that instead of adding strings together. All you have to do is take the strings away!
conditions = to_match.map do |attr|
proc {|row| row[attr.to_sym] > attr }
end
Now you have an array of runnable blocks that take the row as their argument and return the result of the condition (return keyword not required). If you're just doing and, it's as simple as:
does_match = conditions.all? {|c| c.call(row) }
which will be true only if all the conditions return a truthy value (i.e. not false or nil).
As for supporting OR logic, if you are happy to just support ORing all of the conditions (e.g. replacing "and" with "or") then this will do it:
does_match = conditions.any? {|c| c.call(row) }
but if you want to support ORing some and ANDing others, you'll need someway to group them together, which is more complex.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With