用于接受对象或其id作为参数的Ruby语义

时间:2015-01-16 20:04:49

标签: ruby

我试图在这里采用最少惊喜的原则......

让我们说你有一个接受两个对象的方法。该方法需要这些是对象实例,但在初始化类的位置,您可能只有引用ID。例如,这在Web服务中的路由器/控制器中很常见。设置可能如下所示:

post "/:foo_id/add_bar/:bar_id" do
  AddFooToBar.call(...)
end

有许多不同的方法可以解决这个问题。对我来说,最具特色的是#id;这是这样的:

def AddFooToBar.call(foo:nil,foo_id:nil,bar:nil,bar_id:nil)
  @foo = foo || Foo[foo_id]
  @bar = bar || Bar[bar_id]
  ...
end

然后当您调用该方法时,您可以将其称为:

AddFooToBar.call(foo: a_foo, bar: a_bar)
AddFooToBar.call(foo_id: 1, bar_id: 2)

这创建了一个非常清晰的界面,但实现有点冗长,特别是如果有超过2个对象且它们的名称长于foobar

你可以使用一个好的老式哈希......

def AddFooToBar.call(input={})
  @foo = input[:foo] || Foo[ input[:foo_id] ]
  @bar = input[:bar] || Bar[ input[:bar_id ]
end

方法签名现在非常简单,但与使用关键字参数相比,它失去了很多清晰度。

您可以使用单个密钥,尤其是在需要两个输入的情况下:

def AddFooToBar.call(foo:,bar:)
  @foo = foo.is_a?(Foo) ? foo : Foo[foo]
  @bar = bar.is_a?(Bar) ? bar : Bar[bar]
end

方法签名很简单,虽然使用您传递对象实例的相同参数名称只传递一个ID有点奇怪。方法定义中的查找也有点丑陋且不易阅读。

您可以决定不将其内化,并要求调用者在传递实例之前初始化实例。

post "/:foo_id/add_bar/:bar_id" do
  foo = Foo[ params[:foo_id] ]
  bar = Bar[ params[:bar_id] ]
  AddFooToBar.call(foo: foo, bar: bar)
end

这很清楚,但这意味着每个调用方法的地方都需要知道如何首先初始化所需的对象,而不是选择将该行为封装在需要对象的方法中。

最后,您可以执行反向操作,并且只允许传入对象ID,以确保在方法中查找对象。这可能会导致双重查找,如果您有时已经存在要传入的实例。由于您无法注入模拟,因此也更难测试。

我觉得这在Ruby中是一个非常普遍的问题,特别是在构建Web服务时,但我还没有找到很多关于它的文章。所以我的问题是:

  1. 您认为上述哪种方法(或其他方法)是更传统的Ruby? (POLS)

  2. 上述其中一种方法是否存在任何其他问题或疑虑,我没有列出哪些应该影响哪种方法效果最佳,或者您曾经有哪些经历导致您选择一种方案其他人?

  3. 谢谢!

1 个答案:

答案 0 :(得分:1)

我会选择性地允许对象或ID。但是,我不会像你那样做:

def AddFooToBar.call(foo:,bar:)
  @foo = foo.is_a?(Foo) ? foo : Foo[foo]
  @bar = bar.is_a?(Bar) ? bar : Bar[foo]
end

事实上,我不明白为什么你有Bar[foo]而不是Bar[bar]。但除此之外,我还会将[]方法内置的条件置于其中:

def Foo.[] arg
  case arg
  when Foo then arg
  else ...what_you_originally_had...
  end
end

然后,我会将所讨论的方法定义为:

def AddFooToBar.call foo:, bar:
  @foo, @bar = Foo[foo], Bar[bar]
end