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

接受对象或其id作为参数的Ruby语义,ruby,Ruby,我试图在这里以最小惊喜的原则工作 假设您有一个接受两个对象的方法。该方法需要这些对象实例,但在初始化类的地方,可能只有引用ID。例如,这在web服务中的路由器/控制器中很常见。设置可能如下所示: post "/:foo_id/add_bar/:bar_id" do AddFooToBar.call(...) end def AddFooToBar.call(foo:nil,foo_id:nil,bar:nil,bar_id:nil) @foo = foo || Foo[foo_id]

我试图在这里以最小惊喜的原则工作

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

post "/:foo_id/add_bar/:bar_id" do
  AddFooToBar.call(...)
end
def AddFooToBar.call(foo:nil,foo_id:nil,bar:nil,bar_id:nil)
  @foo = foo || Foo[foo_id]
  @bar = bar || Bar[bar_id]
  ...
end
有许多不同的方法可以解决这个问题。对我来说,这里最“idomatic”的东西是这样的:

post "/:foo_id/add_bar/:bar_id" do
  AddFooToBar.call(...)
end
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)
这创建了一个非常清晰的界面,但是实现有点冗长,特别是当有两个以上的对象并且它们的名称长于
foo
bar

你可以用一个好的老式杂烩来代替

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服务时,但我还没有找到太多关于它的文章。因此,我的问题是:

  • 以上哪种方法(或其他方法)是您认为更传统的Ruby?(波尔斯)

  • 关于上述方法中的一种,我没有列出哪些应该影响哪种方法最有效,或者你的经历导致你选择一种方法而不是其他方法,是否还有其他问题或顾虑


  • 谢谢

    我倾向于模糊地允许对象或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
    

    有趣的问题。我的直觉是(可能)使用某种惰性代理对象来创建一个Foo,这个Foo可以用一个在使用时获取的id来构造。这似乎是我在其他语言中使用过的所有数据库框架所使用的方法,而且使用起来非常轻松(尽管我确信后面的内容非常冗长)。
    Bar[foo]
    是一个打字错误。对不起!