神秘的可枚举模块案例

导航至

有时会发生,问题始于更新一个 Ruby gem。

Dawn 过去一年一直在 SaaS.me 工作,开发一款针对狗狗的本地化社交网络应用,该应用大量使用了社交媒体 API。他们的 Facetwit gem 版本已在一段时间前锁定,并开始导致依赖冲突,这意味着是时候升级它了。

gem 更新起初看起来很顺利,但她很快意识到应用程序仪表板缺少附近狗狗公园用户最近推文的列表。检查来自 facetwit 客户端的响应显示令人恼火的 429: Too Many Requests。Dawn 皱着眉头,等待了几分钟让她的限制刷新,却再次收到 429。这一次有一个新的过期时间,表明她不知何故再次耗尽了她的查询。

在控制器中摸索并没有立即发现任何异常之处

class FacetwitController < ApplicationController
def search
client = Facetwit::Client.new(api_key: 'fuzzy_pickles')
@posts = client.search(params[:query])
respond_to do |format|
format.json { render json: @posts }
end
end
end

之后她用调试器逐步执行,并在检查 @posts 时注意到一些有趣的事情。她原本期望它是一个从搜索返回的响应数组,但它实际上是一个 Facetwit::SearchResults 对象。更令人惊讶的是,client.search 实际上根本没有进行 API 调用。那么她的查询限制是如何耗尽的呢?

可枚举模块

让我们退后一步。Ruby 的一大优势是其强大的集合操作方法,例如 injectsort_bymap。这些方法通过 Ruby 的 Enumerable 模块ArrayHash 中都可用。

事实上,您可以在任何类中访问所有这些方法。您只需包含该模块并创建一个 each 定义,该定义将成员 yield 给一个块。Ruby 非常智能,可以从您的 each 定义中推断出所有其他方法(尽管您还需要为排序方法定义 <=>)。

class Pack
include Enumerable
attr_accessor :dogs
def initialize
@dogs = []
end

def each(&block)
@dogs.each{ |dog| block.call(dog) }
end
end

Dog = Struct.new(:name, :breed)

> pack = Pack.new
> pack.dogs << Dog.new("Fido", "Pug")
> pack.dogs << Dog.new("Sparky", "Beagle")
> pack.map(&:breed)
=> ["Pug", "Beagle"]

那么这与 Dawn 的问题有什么关系呢?嗯,如果 each 不仅用于迭代集合,还用于请求实际集合呢?

Module Facetwit
class SearchResults
include Enumerable
def each(start = 0)
@posts.each{ |item| yield(item) }
unless finished?
start = [@posts.size, start].max
get_next
each(start, &Proc.new)
end
end
end
end

Dawn 看到了这一点,并意识到,考虑到 Facetwit 是一个多么流行的平台,迭代单个 Facetwit::SearchResults 对象将继续获取结果,直到耗尽她的可用查询。搜索不是在她的控制器中启动的,而是在她的视图中启动的

<% @posts.each do |post| %>

为了限制外部请求的数量,Dawn 使用了另一个 Enumerable 模块方法 take

<% @posts.take(10).each do |post| %>

通过这个简单的更改,她再次重新加载页面,并收到了来自 Dolores Park 的十条帖子。

不要神秘

Dawn 立即不得不调试她的代码,因为一个外部库的行为出乎意料。做出更改的程序员违反了 最少惊讶原则。这是一个重要的概念,许多 Ruby 程序员试图遵守该原则,该原则规定代码的行为方式不应不必要地令人惊讶。

当编写外部 API gem 时,使用 Enumerable 模块进行分页可能是一种优雅的模式,只要用户清楚地知道使用 Enumerable 方法可能会发出多个外部请求即可。在单线程 Rails 应用程序中,在控制器中主动等待多个 HTTP 响应可能会将应用程序锁定一段无法接受的时间。

毕竟,您应该能够在无需解开谜团的情况下欣赏好的代码。