回答:
読者が遭遇したとき:
user = User.new
user.username = "foobar"
user.save!
3行すべてをたどって、という名前のインスタンスを作成しているだけであることを認識する必要がありますuser
。
もしそうなら:
user = User.new.tap do |u|
u.username = "foobar"
u.save!
end
その後、それはすぐに明らかになります。リーダーは、インスタンスuser
が作成されたことを知るためにブロック内の内容を読み取る必要はありません。
user = User.create!(username: 'foobar')
このような場合が最も明確で最短だと思います:)-質問の最後の例。
user
。また、「インスタンスuser
が作成されたことを知るために、リーダーがブロック内にあるものを読み取る必要はない」という議論。最初のコードブロックでは、リーダーは最初の行を読み取るだけで「インスタンスuser
が作成されたことを知る」ことができるため、重みはありません。
タップを使用するもう1つのケースは、オブジェクトを返す前にオブジェクトを操作することです。
これの代わりに:
def some_method
...
some_object.serialize
some_object
end
追加の行を保存できます。
def some_method
...
some_object.tap{ |o| o.serialize }
end
状況によっては、この手法により1行以上節約でき、コードをよりコンパクトにすることができます。
some_object.tap(&:serialize)
ブロガーが行ったようにタップを使用することは、単に便利な方法です。あなたの例ではやり過ぎだったかもしれませんが、ユーザーとたくさんのことをやりたい場合は、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番目のバージョンは少し厄介に見え、すべてのメソッドが同じオブジェクトで呼び出されていることを確認するために、もう少し人間の解析が必要になる場合があります。
user = User.new, user.do_something, user.do_another_thing
。上記のすべてをまだ実行できます... なぜこれを行うのかを拡大していただけませんか?
tap
ても、私の経験にメリットはありません。ローカルuser
変数を作成して操作する方がはるかにきれいで、私の意見では読みやすいです。
u = user = User.new
からu
セットアップコールに使用した場合、最初の例に沿ったものになります。
これは、一連のチェーンされたスコープのデバッグに役立ちます。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
関数内で例を視覚化する
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
user
か?
User.new.tap{ |u| u.username = name; u.save! }
必要なユーザー名を設定した後にユーザーを返却したい場合
user = User.new
user.username = 'foobar'
user
ではtap
、あなたはその厄介なリターンを救うことができます
User.new.tap do |user|
user.username = 'foobar'
end
Object#tap
私にとって最も一般的な使用例です。
user = User.new.tap {|u| u.username = 'foobar' }
変数のスコープが実際に必要な部分のみに制限されているため、コードが雑然としてなくなります。また、ブロック内のインデントは、関連するコードをまとめることにより、コードを読みやすくします。
自分自身をブロックに譲り、自分自身を返します。このメソッドの主な目的は、チェーン内の中間結果に対して操作を実行するために、メソッドチェーンを「利用する」ことです。
私たちがした場合のために、レールのソースコード検索tap
使用状況を、我々はいくつかの興味深い使用法を見つけることができます。以下は、それらの使用方法に関するいくつかのアイデアを提供するいくつかのアイテム(完全なリストではない)です。
特定の条件に基づいて要素を配列に追加する
%w(
annotations
...
routes
tmp
).tap { |arr|
arr << 'statistics' if Rake.application.current_scope.empty?
}.each do |task|
...
end
配列を初期化して返す
[].tap do |msg|
msg << "EXPLAIN for: #{sql}"
...
msg << connection.explain(sql, bind)
end.join("\n")
コードを読みやすくするための構文糖として-以下の例では、変数hash
を使用してserver
コードの意図をより明確にすることができます。
def select(*args, &block)
dup.tap { |hash| hash.select!(*args, &block) }
end
新しく作成されたオブジェクトのメソッドを初期化/呼び出します。
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
yield
一時変数を使用せずに呼び出しの結果を処理する。
yield.tap do |rendered_partial|
collection_cache.write(key, rendered_partial, cache_options)
end
@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
メソッドは少なくともデータ構造がメソッドの焦点であることを明確にします。
def tapping1; {one: 1, two: 2}; end
.tap
コールチェーンのヘルパーです。オブジェクトを指定されたブロックに渡し、ブロックが終了すると、オブジェクトを返します。
an_object.tap do |o|
# do stuff with an_object, which is in o #
end ===> an_object
利点は、ブロックが他の結果を返す場合でも、tapは呼び出されたオブジェクトを常に返すことです。したがって、フローを中断することなく、既存のメソッドパイプラインの途中にタップブロックを挿入できます。
を使用する利点はないと思いtap
ます。@sawaが指摘している唯一の潜在的な利点は、私が引用していることです。「インスタンスユーザーが作成されたことを知るために、読者はブロック内の内容を読み取る必要はありません。」ただし、その時点で、非単純なレコード作成ロジックを実行している場合は、そのロジックを独自のメソッドに抽出することで意図がより適切に伝達されるという議論を行うことができます。
私tap
は、コードを読みやすくするための不要な負担であり、Extract Methodなどのより優れた手法を使用せずに、または置き換えることもできると私は考えています。
一方でtap
便利なメソッドです、それはまた、個人的な好みです。与えるtap
試みを。次に、タップを使用せずにいくつかのコードを記述します。
使用できる場所や使用できる場所がいくつかあるかもしれません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ルビーのメソッド-使用する必要があります
あなたの言うとおりです。tap
あなたの例でのの使用は、意味がなく、おそらく他の方法よりもクリーンではありません。
Rebitzeleが指摘するように、tap
は単なる便利な方法であり、現在のオブジェクトへの短い参照を作成するためによく使用されます。
の良いユースケースの1つtap
はデバッグです。オブジェクトを変更し、現在の状態を出力してから、同じブロックでオブジェクトの変更を続けることができます。例については、http://moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressionsを参照してください。
私は時々、tap
内部のメソッドを使用して、条件付きで早期に戻り、それ以外の場合は現在のオブジェクトを返すことが好きです。
メソッドの読み取りがいかに難しいかを測定する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!
タップを使用してコードをよりモジュール化し、ローカル変数のより良い管理を実現できます。たとえば、次のコードでは、メソッドのスコープ内で、新しく作成されたオブジェクトにローカル変数を割り当てる必要はありません。ブロック変数uはブロック内でスコープされていることに注意してください。それは実際にはルビコードの美しさの一つです。
def a_method
...
name = "foobar"
...
return User.new.tap do |u|
u.username = name
u.save!
end
end
Railsではtap
、パラメーターを明示的にホワイトリストに登録するために使用できます。
def client_params
params.require(:client).permit(:name).tap do |whitelist|
whitelist[:name] = params[:client][:name]
end
end
私が使用した別の例を挙げます。ユーザーに保存するために必要なパラメーターを返すメソッド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
関数型プログラミングパターンがベストプラクティスになっている世界(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
ここで複数回宣言する必要はありません。
コードの可読性の点での違いは、純粋にスタイルです。
user = User.new.tap do |u|
u.username = "foobar"
u.save!
end
キーポイント:
u
変数がどのようにブロックパラメーターとして使用されているかに注意してください。user
変数はユーザーを指すようになります(ユーザー名は 'foobar'で、誰も保存されています)。ソースコードの読みやすいバージョンは次のとおりです。
class Object
def tap
yield self
self
end
end
詳細については、次のリンクを参照してください。
User.new.tap &:foobar