简体   繁体   English

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

[英]Ruby semantics for accepting an object or its id as an argument

I'm trying to work on the principle of least surprise here... 我正在努力研究这里最不惊讶的原则......

Let's say you've got a method that accepts two objects. 假设您有一个接受两个对象的方法。 The method needs these to be object instances, but in the place where you initialize the class you may only have reference IDs. 该方法需要这些是对象实例,但在初始化类的位置,您可能只有引用ID。 This would be common in a router / controller in a web service, for example. 例如,这在Web服务中的路由器/控制器中很常见。 The setup might look something like this: 设置可能如下所示:

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

There are many different ways that this could be solved. 有许多不同的方法可以解决这个问题。 To me the most 'idomatic' here is something like this: 对我来说,最像'idomatic'的是这样的:

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

Then when you call the method, you could call it like: 然后,当您调用该方法时,您可以将其称为:

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

This creates a pretty clear interface, but the implementation is a little verbose, particularly if there are more than 2 objects and their names are longer than foo and bar . 这创建了一个非常清晰的界面,但实现有点冗长,特别是如果有超过2个对象且它们的名称比foobar

You could use a good old fashioned hash instead... 你可以用一个好的老式哈希代替......

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

The method signature is super simple now, but it loses a lot of clarity compared to what you get using keyword arguments. 方法签名现在非常简单,但与使用关键字参数获得的方法相比,它失去了很多清晰度。

You could just use a single key instead, especially if both inputs are required: 您可以只使用一个键,特别是如果需要两个输入:

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

The method signature is simple, though it's a little weird to pass just an ID using the same argument name you'd pass an object instance to. 方法签名很简单,虽然使用您传递对象实例的相同参数名称只传递一个ID有点奇怪。 The lookup in the method definition is also a little uglier and less easy to read. 方法定义中的查找也有点丑陋且不易阅读。

You could just decide not to internalize this at all and require the caller to initialize instances before passing them in. 您可以决定不将其内化,并要求调用者在传递实例之前初始化实例。

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

This is quite clear, but it means that every place that calls the method needs to know how to initialize the required objects first, rather than having the option to encapsulate that behavior in the method that needs the objects. 这很清楚,但这意味着调用方法的每个地方都需要知道如何首先初始化所需的对象,而不是选择将该行为封装在需要对象的方法中。

Lastly, you could do the inverse, and only allow object ids to be passed in, ensuring the objects will be looked up in the method. 最后,您可以执行反向操作,并且只允许传入对象ID,以确保在方法中查找对象。 This may cause double lookups though, in case you sometimes have instances already existing that you want to pass in. It's also harder to test since you can't just inject a mock. 这可能会导致双重查找,以防您有时已经存在要传入的实例。由于您不能只注入模拟,因此也更难测试。

I feel like this is a pretty common issue in Ruby, particularly when building web services, but I haven't been able to find much writing about it. 我觉得这在Ruby中是一个非常普遍的问题,特别是在构建Web服务时,但是我还没有找到很多关于它的文章。 So my questions are: 所以我的问题是:

  1. Which of the above approaches (or something else) would you expect as more conventional Ruby? 您认为上述哪种方法(或其他方法)是更传统的Ruby? (POLS) (POLS)

  2. Are there any other gotchas or concerns around one of the approaches above that I didn't list which should influence which one works best, or experiences you've had that led you to choose one option over the others? 对于上面列出的其中一种方法,我没有列出哪些应该影响哪种方法效果最好,或者你曾经有过的经历导致你选择其中一种选择?

Thanks! 谢谢!

I would go with allowing either the objects or the ids indistinctively. 我会选择性地允许对象或ID。 However, I would not do like you did: 但是,我不会像你那样做:

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

In fact, I do not understand why you have Bar[foo] and not Bar[bar] . 事实上,我不明白为什么你有Bar[foo]而不是Bar[bar] But besides this, I would put the conditions built-in within the [] method: 但除此之外,我会在[]方法中内置条件:

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

Then, I would have the method in question to be defined like: 然后,我会将有问题的方法定义为:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM