根据用户定义的输入构建动态if语句

时间:2015-07-10 08:45:59

标签: ruby ruby-on-rails-4

我有一个表,每行增加值(下面代码中的年份)。 我有一个目标,它指定了一个"阈值"。目标是用户定义的,它可以包含表的一列或多列的值。这意味着您永远不知道目标中指定了多少列。 我想匹配表中的第一行,其中行中的值大于目标中的值。我目前有这个:

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

这很有效,但现在算法被修复为使用AND匹配。我想支持OR匹配以及AND匹配。另外我想避免使用eval,必须有一个更优雅的方式。另外,我希望尽可能地降低此代码的复杂性。 我怎么能重写这段代码来满足这些要求?任何建议都表示赞赏。

1 个答案:

答案 0 :(得分:1)

为了避免使用eval:Ruby可以动态创建代码,所以不要一起添加字符串。你所要做的就是拿走琴弦!

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

现在你有一个可运行的块数组,它们以row作为参数,并返回条件的结果(return关键字不是必需的)。如果你只是在做and,那就简单了:

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

仅当所有条件返回真值时才会true(即不是falsenil)。

至于支持OR逻辑,如果您乐意支持ORing所有条件(例如替换&#34;和&#34;用&#34;或&#34;)那么这样做:

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

但是如果你想支持ORing some和ANDing其他人,那么你需要将它们组合在一起,这更复杂。