是否有更惯用的 ruby 来执行此迭代?

Is there more idiomatic ruby to perform this iteration?

我正在遍历一组 id,并希望 return 第一个对象 return 在其上调用谓词方法时 true。几行代码顶一千字:

def applicable_question(question_ids)
  ordered_ids(question_ids, order).detect do |question_id|
    question = Question.find_by(id: question_id)
    return question if question.applicable_for?(self)
  end
end

剥离域术语:

def desired_thing(ids)
  ids.detect do |id|
    thing = Thing.new(id)
    return thing if thing.true?
  end
end

这里有更惯用的方法吗?具体来说,我感觉自己在虐detect。我立即找到了 eachbreak,但并没有用这种方法走得太远。

此代码的一个要求是它不需要实例化大量对象(例如 ActiveRecord 子类型)来找到所需的东西。

您有:

desired_thing = ids.detect do |id|
  thing = Thing.new(id)
  return thing if thing.true?
end

如果找到 thing,其中 thing.true?truedetect 永远不会 returns ids 的元素(要分配到 desired_thing) 因为它被 return 抢占了。另一方面,如果块在没有调用 return 的情况下完成,detect returns nil 并将该值分配给 desired_thing,但是 nil 值在后面的代码中没有用。因此最好只写:

ids.each do |id|
  thing = Thing.new(id)
  return thing if thing.true?
end

我想你快到了。

根据 find_by_id 方法,我假设 Question 是具有某种 ActiveRecord 功能的 class,所以我希望 where方法也存在。

在那种情况下:

applicable_question = Question.where(id: ids).order(...).detect do |question|
  question.applicable_for?(self)
end

会做的很好。

感觉不像是在滥用detect,你只需要拥有正确的可枚举对象集即可。

更新

如果实例化太多对象不是一个可接受的解决方案,那么 是可行的方法。请记住,这两种方法都有其缺点。

  • Question.where(id: ids).order(...).detect {|q| q.condition? } 将对数据库执行单个查询但实例化太多对象。
  • ids.each {|id| q = Question.find(id); return q if q.condition? } 将对数据库执行太多查询但一次只实例化一个对象

第一种方法总是很繁重(内存方面),而第二种方法的性能取决于您检索记录的顺序,并且也可能变得非常昂贵。

最佳选择也取决于您的数据集。如果你有成千上万的问题,第一种方法是不可能的。

也许最好的选择是尝试以 condition? 更可能为真的方式对记录进行排序

你所拥有的与你想要做的相差不远。我会做类似的事情:

desired_thing = ids.detect{ |id| Thing.new(id) }

我不确定您是否想远离 SQL 方法,例如 find_by_id 或其他方法,但我相信您也可以使用这些方法,除非您需要使用检测(或查找)。