Creating a dynamic if statement based on user input

I have a table with increasing values ​​of each row (year in the code below). I have a goal that sets a "threshold". The target is user-defined and can contain a value for one or more columns of the table This means you never know how many columns are in the target. I want to match the first row in a 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 great, but now the algorithm has been fixed to use AND matching. I want to support OR as well as AND. Also I want to avoid using eval, there must be a more elegant way. I also want to reduce the complexity of this code as much as possible. How can I rewrite this code to meet these requirements? Any suggestion is appreciated.

+3


source to share


1 answer


To avoid use eval

: Ruby can generate code dynamically, so instead of adding lines. All you have to do is remove the lines!

conditions = to_match.map do |attr|
  proc {|row| row[attr.to_sym] > attr }
end

      

You now have an array of runnable blocks that take row

as their argument and return the result of the condition (no return

keyword required). If you just do and

, it is as easy as:

does_match = conditions.all? {|c| c.call(row) }

      

which will true

only be if all conditions return true (i.e. not false

or nil

).




As far as ORing logic is concerned, if you're happy to simply support ORing for all conditions (for example, replacing "and" with "or"), this will do it:

does_match = conditions.any? {|c| c.call(row) }

      

but if you want to support ORing some and ANDing others, you need to group them somehow, which is more complicated.

+1


source







All Articles