ruby ruby中tap方法的优点

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17493080/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 06:03:23  来源:igfitidea点击:

advantage of tap method in ruby

ruby

提问by Kyle Decot

I was just reading a blog article and noticed that the author used tapin a snippet something like:

我只是在阅读一篇博客文章,并注意到作者tap在一个片段中使用了类似的内容:

user = User.new.tap do |u|
  u.username = "foobar"
  u.save!
end

My question is what exactly is the benefit or advantage of using tap? Couldn't I just do:

我的问题是使用 的好处或优势究竟是什么tap?我不能只做:

user = User.new
user.username = "foobar"
user.save!

or better yet:

或者更好:

user = User.create! username: "foobar"

采纳答案by sawa

When readers encounter:

当读者遇到:

user = User.new
user.username = "foobar"
user.save!

they would have to follow all the three lines and then recognize that it is just creating an instance named user.

他们必须遵循所有三行,然后才能认识到它只是在创建一个名为user.

If it were:

如果是:

user = User.new.tap do |u|
  u.username = "foobar"
  u.save!
end

then that would be immediately clear. A reader would not have to read what is inside the block to know that an instance useris created.

那么这将立即清楚。读者不必阅读块内的内容即可知道实例user已创建。

回答by megas

Another case to use tap is to make manipulation on object before returning it.

使用 tap 的另一种情况是在返回对象之前对其进行操作。

So instead of this:

所以而不是这个:

def some_method
  ...
  some_object.serialize
  some_object
end

we can save extra line:

我们可以节省额外的行:

def some_method
  ...
  some_object.tap{ |o| o.serialize }
end

In some situation this technique can save more then one line and make code more compact.

在某些情况下,这种技术可以节省多于一行并使代码更紧凑。

回答by Rebitzele

Using tap, as the blogger did, is simply a convenience method. It may have been overkill in your example, but in cases where you'd want to do a bunch of things with the user, tap can arguably provide a cleaner looking interface. So, perhaps it may be better in an example as follows:

正如博主所做的那样,使用点击只是一种方便的方法。在您的示例中,这可能有点矫枉过正,但在您想与用户一起做很多事情的情况下,点击可以说提供了一个更简洁的界面。因此,也许在以下示例中可能会更好:

user = User.new.tap do |u|
  u.build_profile
  u.process_credit_card
  u.ship_out_item
  u.send_email_confirmation
  u.blahblahyougetmypoint
end

Using the above makes it easy to quickly see that all those methods are grouped together in that they all refer to the same object (the user in this example). The alternative would be:

使用上面的方法可以很容易地快速看到所有这些方法都组合在一起,因为它们都引用同一个对象(本例中的用户)。替代方案是:

user = User.new
user.build_profile
user.process_credit_card
user.ship_out_item
user.send_email_confirmation
user.blahblahyougetmypoint

Again, this is debatable - but the case can be made that the second version looks a little messier, and takes a little more human parsing to see that all the methods are being called on the same object.

同样,这是有争议的 - 但可以证明第二个版本看起来有点混乱,并且需要更多的人工解析才能看到所有方法都在同一个对象上被调用。

回答by Gupta

This can be useful with debugging a series of ActiveRecordchained scopes.

这对于调试一系列链式作用域非常有用ActiveRecord

User
  .active                      .tap { |users| puts "Users so far: #{users.size}" } 
  .non_admin                   .tap { |users| puts "Users so far: #{users.size}" }
  .at_least_years_old(25)      .tap { |users| puts "Users so far: #{users.size}" }
  .residing_in('USA')

This makes it super easy to debug at any point in the chain without having to store anything in in a local variable nor requiring much altering of the original code.

这使得在链中的任何点进行调试变得非常容易,而无需在局部变量中存储任何内容,也不需要对原始代码进行大量更改。

And lastly, use it as a quick and unobtrusive way to debug without disrupting normal code execution:

最后,将其用作一种快速且不引人注目的调试方式,而不会中断正常的代码执行

def rockwell_retro_encabulate
  provide_inverse_reactive_current
  synchronize_cardinal_graham_meters
  @result.tap(&method(:puts))
  # Will debug `@result` just before returning it.
end

回答by montrealmike

If you wanted to return the user after setting the username you'd need to do

如果您想在设置用户名后返回用户,则需要执行以下操作

user = User.new
user.username = 'foobar'
user

With tapyou could save that awkward return

有了tap你就可以省去那个尴尬的回报

User.new.tap do |user|
  user.username = 'foobar'
end

回答by SystematicFrank

Visualize your example within a function

在函数中可视化您的示例

def make_user(name)
  user = User.new
  user.username = name
  user.save!
end

There is a big maintenance risk with that approach, basically the implicit return value.

这种方法存在很大的维护风险,基本上是隐式返回值

In that code you do depend on save!returning the saved user. But if you use a different duck (or your current one evolves) you might get other stuff like a completion status report. Therefore changes to the duck might break the code, something that would not happen if you ensure the return value with a plain useror use tap.

在该代码中,您确实依赖于save!返回保存的用户。但是如果你使用不同的鸭子(或者你当前的鸭子进化),你可能会得到其他东西,比如完成状态报告。因此,对duck 的更改可能会破坏代码,如果您user使用普通或使用tap确保返回值,则不会发生这种情况。

I have seen accidents like this quite often, specially with functions where the return value is normally not used except for one dark buggy corner.

我经常看到这样的事故,特别是那些通常不使用返回值的函数,除了一个黑暗的马车角落。

The implicit return value tends to be one of those things where newbies tend to break things adding new code after the last line without noticing the effect. They do not see what the above code really means:

隐式返回值往往是新手倾向于在最后一行之后添加新代码而没有注意到效果的事情之一。他们没有看到上述代码的真正含义:

def make_user(name)
  user = User.new
  user.username = name
  return user.save!       # notice something different now?
end

回答by Wand Maker

It results in less-cluttered code as the scope of variable is limited only to the part where it is really needed. Also, the indentation within the block makes the code more readable by keeping relevant code together.

由于变量的范围仅限于真正需要它的部分,因此它可以减少代码的混乱。此外,块内的缩进通过将相关代码放在一起使代码更具可读性。

Description of tapsays:

描述tap

Yields self to the block, and then returns self.The primary purpose of this method is to “tap into” a method chain, in order to perform operations on intermediate results within the chain.

将 self 交给块,然后返回 self。此方法的主要目的是“接入”方法链,以便对链中的中间结果执行操作。

If we search rails source code for tapusage, we can find some interesting usages. Below are few items (not exhaustive list) that will give us few ideas on how to use them:

如果我们搜索 rails 源代码的tap用法,我们可以找到一些有趣的用法。下面是一些项目(不是详尽的列表),它们会给我们一些关于如何使用它们的想法:

  1. Append an element to an array based on certain conditions

    %w(
    annotations
    ...
    routes
    tmp
    ).tap { |arr|
      arr << 'statistics' if Rake.application.current_scope.empty?
    }.each do |task|
      ...
    end
    
  2. Initializing an array and returning it

    [].tap do |msg|
      msg << "EXPLAIN for: #{sql}"
      ...
      msg << connection.explain(sql, bind)
    end.join("\n")
    
  3. As syntactic sugar to make code more readable - One can say, in below example, use of variables hashand servermakes the intent of code clearer.

    def select(*args, &block)
        dup.tap { |hash| hash.select!(*args, &block) }
    end
    
  4. Initialize/invoke methods on newly created objects.

    Rails::Server.new.tap do |server|
       require APP_PATH
       Dir.chdir(Rails.application.root)
       server.start
    end
    

    Below is an example from test file

    @pirate = Pirate.new.tap do |pirate|
      pirate.catchphrase = "Don't call me!"
      pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}]
      pirate.save!
    end
    
  5. To act on the result of a yieldcall without having to use a temporary variable.

    yield.tap do |rendered_partial|
      collection_cache.write(key, rendered_partial, cache_options)
    end
    
  1. 根据特定条件将元素追加到数组中

    %w(
    annotations
    ...
    routes
    tmp
    ).tap { |arr|
      arr << 'statistics' if Rake.application.current_scope.empty?
    }.each do |task|
      ...
    end
    
  2. 初始化一个数组并返回它

    [].tap do |msg|
      msg << "EXPLAIN for: #{sql}"
      ...
      msg << connection.explain(sql, bind)
    end.join("\n")
    
  3. 至于语法糖使代码更易读-可以说,在下面的例子,使用的变量hash,并server使得意图的代码更清晰。

    def select(*args, &block)
        dup.tap { |hash| hash.select!(*args, &block) }
    end
    
  4. 在新创建的对象上初始化/调用方法。

    Rails::Server.new.tap do |server|
       require APP_PATH
       Dir.chdir(Rails.application.root)
       server.start
    end
    

    以下是测试文件中的示例

    @pirate = Pirate.new.tap do |pirate|
      pirate.catchphrase = "Don't call me!"
      pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}]
      pirate.save!
    end
    
  5. yield调用结果采取行动而不必使用临时变量。

    yield.tap do |rendered_partial|
      collection_cache.write(key, rendered_partial, cache_options)
    end
    

回答by Giuseppe

A variation on @sawa's answer:

@sawa 答案的变体:

As already noted, using taphelps figuring out the intent of your code (while not necessarily making it more compact).

如前所述,使用tap有助于弄清代码的意图(但不一定使它更紧凑)。

The following two functions are equally long, but in the first one you have to read through the end to figure out why I initialized an empty Hash at the beginning.

以下两个函数同样长,但在第一个函数中,您必须通读结尾才能弄清楚为什么我在开头初始化了一个空 Hash。

def tapping1
  # setting up a hash
  h = {}
  # working on it
  h[:one] = 1
  h[:two] = 2
  # returning the hash
  h
end

Here, on the other hand, you know right from the start that the hash being initialized will be the block's output (and, in this case, the function's return value).

在这里,另一方面,你从一开始就知道被初始化的散列将是块的输出(在这种情况下,是函数的返回值)。

def tapping2
  # a hash will be returned at the end of this block;
  # all work will occur inside
  Hash.new.tap do |h|
    h[:one] = 1
    h[:two] = 2
  end
end

回答by Pushp Raj Saurabh

It's a helper for call chaining. It passes its object into the given block and, after the block finishes, returns the object:

它是调用链的帮手。它将其对象传递给给定的块,并在块完成后返回该对象:

an_object.tap do |o|
  # do stuff with an_object, which is in o #
end  ===> an_object

The benefit is that tap always returns the object it's called on, even if the block returns some other result. Thus you can insert a tap block into the middle of an existing method pipeline without breaking the flow.

好处是 tap 总是返回它被调用的对象,即使块返回一些其他结果。因此,您可以在现有方法管道的中间插入一个 tap 块,而不会中断流程。

回答by gylaz

I would say that there is no advantage to using tap. The only potential benefit, as @sawa points outis, and I quote: "A reader would not have to read what is inside the block to know that an instance user is created." However, at that point the argument can be made that if you're doing non-simplistic record creation logic, your intent would be better communicated by extracting that logic into its own method.

我会说使用tap. 正如@sawa 指出的,唯一潜在的好处是,我引用:“读者不必阅读块内的内容即可知道实例用户已创建。” 但是,在这一点上,可以提出这样的论点:如果您正在执行非简单的记录创建逻辑,那么通过将该逻辑提取到其自己的方法中会更好地传达您的意图。

I hold to the opinion that tapis an unnecessary burden on the readability of the code, and could be done without, or substituted with a better technique, like Extract Method.

我坚持认为这tap对代码的可读性造成了不必要的负担,可以不用或用更好的技术代替,例如Extract Method

While tapis a convenience method, it's also personal preference. Give tapa try. Then write some code without using tap, see if you like one way over another.

虽然tap是一种方便的方法,但也是个人喜好。给tap一试。然后在不使用 tap 的情况下编写一些代码,看看您是否喜欢一种方式。