ルビのタップ方法の利点


116

私はちょうどブログ記事を読んでいて、著者がtap次のようなスニペットで使用していることに気づきました:

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

私の質問は、使用の利点または利点は何tapですか?私だけではできませんでした:

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

またはより良い:

user = User.create! username: "foobar"

回答:


103

読者が遭遇したとき:

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

3行すべてをたどって、という名前のインスタンスを作成しているだけであることを認識する必要がありますuser

もしそうなら:

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

その後、それはすぐに明らかになります。リーダーは、インスタンスuserが作成されたことを知るためにブロック内の内容を読み取る必要はありません。


3
@マット:また、ブロックがその仕事を終えたら、プロセスで作成された変数定義を破棄します。そして、オブジェクトで呼び出さただ一つの方法があるはず、あなたは書くことができますUser.new.tap &:foobar
ボリスStitnicky

28
私はこれが非常に説得力のある使い方だとは思いません-おそらく、これ以上読みやすくはありません。それがこのページにある理由です。読みやすさを重視せずに、速度を比較しました。私のテストでは、上記の単純な実装では45%の追加ランタイムが示され、オブジェクトのセッターの数が増えるにつれて減少します-約10以上で、ランタイムの差は無視できます(YMMV)。デバッグ中に一連のメソッドを「タップ」することは成功のようですが、それ以外の場合はもっと説得する必要があります。
dinman2022 2015年

7
user = User.create!(username: 'foobar')このような場合が最も明確で最短だと思います:)-質問の最後の例。
Leeが

4
この答えはそれ自体と矛盾するため、意味がありません。「という名前のインスタンスを作成するだけ」ではありませんuser。また、「インスタンスuserが作成されたことを知るために、リーダーがブロック内にあるものを読み取る必要はない」という議論。最初のコードブロックでは、リーダーは最初の行を読み取るだけで「インスタンスuserが作成されたことを知る」ことができるため、重みはありません。
ジャクソン

5
なぜ私はここにいるのですか?なぜ私たちはここでタップとは何かを検索しているのですか?
エディ

37

タップを使用するもう1つのケースは、オブジェクトを返す前にオブジェクトを操作することです。

これの代わりに:

def some_method
  ...
  some_object.serialize
  some_object
end

追加の行を保存できます。

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

状況によっては、この手法により1行以上節約でき、コードをよりコンパクトにすることができます。


24
私はさらにもっと徹底的になるだろう:some_object.tap(&:serialize)
アメンカリーニ2015年

28

ブロガーが行ったようにタップを使用することは、単に便利な方法です。あなたの例ではやり過ぎだったかもしれませんが、ユーザーとたくさんのことをやりたい場合は、tapが間違いなくより見栄えの良いインターフェースを提供できます。したがって、おそらく次のような例の方が良いでしょう。

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

上記を使用すると、それらすべてのメソッドが同じオブジェクト(この例ではユーザー)を参照するという点で、グループ化されていることがすぐにわかります。代替案は次のとおりです。

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

繰り返しになりますが、これは議論の余地があります。ただし、2番目のバージョンは少し厄介に見え、すべてのメソッドが同じオブジェクトで呼び出されていることを確認するために、もう少し人間の解析が必要になる場合があります。


2
これはOPがすでに彼の質問に入力したもののより長い例にすぎませんuser = User.new, user.do_something, user.do_another_thing。上記のすべてをまだ実行できます... なぜこれを行うのを拡大していただけませんか?
Matt

例は基本的に同じですが、長い形式で表示すると、タップを使用する方がこのケースにとって美的に魅力的であることがわかります。デモに役立つように編集を追加します。
Rebitzele 2013

私も見ません。を使用しtapても、私の経験にメリットはありません。ローカルuser変数を作成して操作する方がはるかにきれいで、私の意見では読みやすいです。
gylaz 2013

これら2つは同等ではありません。実行してu = user = User.newからuセットアップコールに使用した場合、最初の例に沿ったものになります。
ジェリー

26

これは、一連のチェーンされたスコープのデバッグに役立ちます。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')

これにより、ローカル変数に何かを格納したり、元のコードを大幅に変更したりする必要なく、チェーンの任意のポイントで非常に簡単にデバッグできます。

そして最後に、通常のコード実行を中断せずにデバッグするための迅速で目立たない方法としてそれを使用します

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

14

関数内で例を視覚化する

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

そのアプローチには、基本的に暗黙的な戻り値という大きなメンテナンスリスクがあります

そのコードではsave!、保存したユーザーを返すことに依存しています。ただし、別のアヒルを使用している場合(または現在のアヒルが進化している場合)、完了ステータスレポートなどの他の情報が表示される可能性があります。したがって、アヒルを変更するとコードが壊れる可能性がありますuser。これは、プレーンで戻り値を確認したり、タップを使用したりしても発生しません。

このような事故はかなり頻繁に見られます。特に、1つの暗いバギーコーナーを除いて、通常は戻り値が使用されない関数で発生します。

暗黙的な戻り値は、初心者が効果に気付かずに最後の行の後に新しいコードを追加することで問題が発生する傾向があるものの1つになる傾向があります。上記のコードが実際に何を意味するのかはわかりません。

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

1
2つの例に違いはまったくありません。戻るつもりでしたuserか?
ブライアンアッシュ

1
それが彼のポイントでした:例はまったく同じですが、1つは返品について明示的です。彼のポイントは、これがタップを使用して回避することができるということであった:User.new.tap{ |u| u.username = name; u.save! }
Obversity

14

必要なユーザー名を設定した後にユーザーを返却したい場合

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

ではtap、あなたはその厄介なリターンを救うことができます

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

1
これは、Object#tap私にとって最も一般的な使用例です。
Lyndsy Simon 2017

1
さて、あなたはコードのゼロ行を保存しました、そして今、それが返すもののためにメソッドの終わりを見るとき、私はブロックが#tapブロックであることを確認するために戻ってスキャンしなければなりません。これが何らかの勝利であるかどうかはわかりません。
Irongaze.com

多分しかし、これは簡単に1ライナーになる可能性があります user = User.new.tap {|u| u.username = 'foobar' }
lacostenycoder

11

変数のスコープが実際に必要な部分のみに制限されているため、コードが雑然としてなくなります。また、ブロック内のインデントは、関連するコードをまとめることにより、コードを読みやすくします。

tap言うの説明

自分自身をブロックに譲り、自分自身を返します。このメソッドの主な目的は、チェーン内の中間結果に対して操作を実行するために、メソッドチェーンを「利用する」ことです。

私たちがした場合のために、レールのソースコード検索tap使用状況を、我々はいくつかの興味深い使用法を見つけることができます。以下は、それらの使用方法に関するいくつかのアイデアを提供するいくつかのアイテム(完全なリストではない)です。

  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

9

@sawaの回答のバリエーション:

すでに述べたように、を使用tapすると、コードの意図を理解するのに役立ちます(必ずしもコンパクトにする必要はありません)。

次の2つの関数は同じくらい長いですが、最初の関数では、最初から空のハッシュを初期化した理由を理解するために、最後まで読む必要があります。

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

一方、初期化中のハッシュがブロックの出力(この場合は、関数の戻り値)になることが最初からわかります。

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

このアプリケーションはtap、より説得力のある議論になります。あなたが見たときuser = User.new、その意図はすでに明確であると私は他人に同意します。ただし、匿名データ構造は何にでも使用でき、tapメソッドは少なくともデータ構造がメソッドの焦点であることを明確にします。
volx757 2015年

この例の方が良いとはdef tapping1; {one: 1, two: 2}; end.tap
言え

9

コールチェーンのヘルパーです。オブジェクトを指定されたブロックに渡し、ブロックが終了すると、オブジェクトを返します。

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

利点は、ブロックが他の結果を返す場合でも、tapは呼び出されたオブジェクトを常に返すことです。したがって、フローを中断することなく、既存のメソッドパイプラインの途中にタップブロックを挿入できます。


8

を使用する利点はないと思いtapます。@sawaが指摘している唯一の潜在的な利点は、私が引用していることです。「インスタンスユーザーが作成されたことを知るために、読者はブロック内の内容を読み取る必要はありません。」ただし、その時点で、非単純なレコード作成ロジックを実行している場合は、そのロジックを独自のメソッドに抽出することで意図がより適切に伝達されるという議論を行うことができます。

tapは、コードを読みやすくするための不要な負担であり、Extract Methodなどのより優れた手法を使用せずに、または置き換えることもできると私は考えています。

一方でtap便利なメソッドです、それはまた、個人的な好みです。与えるtap試みを。次に、タップを使用せずにいくつかのコードを記述します。


4

使用できる場所や使用できる場所がいくつかあるかもしれませんtap。これまでのところ、以下の2つの使用法のみを見つけましたtap

1)このメソッドの主な目的は、チェーン内の中間結果に対して操作を実行するため、メソッドチェーンを利用することです。すなわち

(1..10).tap { |x| puts "original: #{x.inspect}" }.to_a.
    tap    { |x| puts "array: #{x.inspect}" }.
    select { |x| x%2 == 0 }.
    tap    { |x| puts "evens: #{x.inspect}" }.
    map    { |x| x*x }.
    tap    { |x| puts "squares: #{x.inspect}" }

2)あるオブジェクトのメソッドを呼び出して、戻り値が望んだものではないことに気づいたことがありますか?ハッシュに格納されたパラメータのセットに任意の値を追加したいと思うかもしれません。Hash。[]で更新しますが、paramsハッシュの代わりにバックバーを取得するため、明示的に返す必要があります。すなわち

def update_params(params)
  params[:foo] = 'bar'
  params
end

ここでこの状況を克服するために、tap方法が登場します。オブジェクト上で呼び出すだけで、実行したいコードを含むブロックをパスタップします。オブジェクトはブロックに委譲され、返されます。すなわち

def update_params(params)
  params.tap {|p| p[:foo] = 'bar' }
end

他にも数十のユースケースがあります。自分で見つけてみてください:)

ソース:
1)APIドックオブジェクトタップ
2)5ルビーのメソッド-使用する必要があります


3

あなたの言うとおりです。tapあなたの例でのの使用は、意味がなく、おそらく他の方法よりもクリーンではありません。

Rebitzeleが指摘するように、tapは単なる便利な方法であり、現在のオブジェクトへの短い参照を作成するためによく使用されます。

の良いユースケースの1つtapはデバッグです。オブジェクトを変更し、現在の状態を出力してから、同じブロックでオブジェクトの変更を続けることができます。例については、http//moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressionsを参照してください

私は時々、tap内部のメソッドを使用して、条件付きで早期に戻り、それ以外の場合は現在のオブジェクトを返すことが好きです。


これは、ドキュメントに記載されているアプリケーションでもあります:ruby-doc.org/core-2.1.3/Object.html#method-i-tap
Ciro Santilli郝海东冠状病六四事件法轮機能

3

メソッドの読み取りがいかに難しいかを測定するflogというツールがあります。「スコアが高いほど、コードの負担は大きくなります。」

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

def without_tap
  user = User.new
  user.username = "foobar"
  user.save!
end

def using_create
  user = User.create! username: "foobar"
end

そして、flogの結果によると、方法tapは最も読むのが難しいです(そして私はそれに同意します)

 4.5: main#with_tap                    temp.rb:1-4
 2.4:   assignment
 1.3:   save!
 1.3:   new
 1.1:   branch
 1.1:   tap

 3.1: main#without_tap                 temp.rb:8-11
 2.2:   assignment
 1.1:   new
 1.1:   save!

 1.6: main#using_create                temp.rb:14-16
 1.1:   assignment
 1.1:   create!

1

タップを使用してコードをよりモジュール化し、ローカル変数のより良い管理を実現できます。たとえば、次のコードでは、メソッドのスコープ内で、新しく作成されたオブジェクトにローカル変数を割り当てる必要はありません。ブロック変数uはブロック内でスコープされていることに注意してください。それは実際にはルビコードの美しさの一つです。

def a_method
  ...
  name = "foobar"
  ...
  return User.new.tap do |u|
    u.username = name
    u.save!
  end
end

1

Railsではtap、パラメーターを明示的にホワイトリストに登録するために使用できます。

def client_params
    params.require(:client).permit(:name).tap do |whitelist|
        whitelist[:name] = params[:client][:name]
    end
end

1

私が使用した別の例を挙げます。ユーザーに保存するために必要なパラメーターを返すメソッドuser_paramsがあります(これはRailsプロジェクトです)。

def user_params
  params.require(:user).permit(
    :first_name,
    :last_name,
    :email,
    :address_attributes
  )
end

何も返さないのがわかりますが、rubyは最後の行の出力を返します。

その後、しばらくして、条件付きで新しい属性を追加する必要がありました。だから、私はそれを次のようなものに変更しました:

def user_params 
  u_params = params.require(:user).permit(
    :first_name, 
    :last_name, 
    :email,
    :address_attributes
  )
  u_params[:time_zone] = address_timezone if u_params[:address_attributes]
  u_params
end

ここで、tapを使用してローカル変数を削除し、戻り値を削除できます。

def user_params 
  params.require(:user).permit(
    :first_name, 
    :last_name, 
    :email,
    :address_attributes
  ).tap do |u_params|
    u_params[:time_zone] = address_timezone if u_params[:address_attributes]
  end
end

1

関数型プログラミングパターンがベストプラクティスになっている世界(https://maryrosecook.com/blog/post/a-practical-introduction-to-functional-programming)では、実際にを1つの値tapとして、map、変換チェーン上のデータを変更します。

transformed_array = array.map(&:first_transformation).map(&:second_transformation)

transformed_value = item.tap(&:first_transformation).tap(&:second_transformation)

itemここで複数回宣言する必要はありません。


0

違いはなんですか?

コードの可読性の点での違いは、純粋にスタイルです。

コードウォークスルー:

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

キーポイント:

  • u変数がどのようにブロックパラメーターとして使用されているかに注意してください。
  • ブロックが完了すると、user変数はユーザーを指すようになります(ユーザー名は 'foobar'で、誰も保存されています)。
  • 楽しくて読みやすいだけです。

APIドキュメント

ソースコードの読みやすいバージョンは次のとおりです。

class Object
  def tap
    yield self
    self
  end
end

詳細については、次のリンクを参照してください。

https://apidock.com/ruby/Object/tap

http://ruby-doc.org/core-2.2.3/Object.html#method-i-tap

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.