3

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.

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for this, it achieves all requirements and more. I will give it a try.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.