vantagem do método tap em ruby
estava a ler um artigo no blog e reparei que o autor usou {[[3]} num trecho como:
user = User.new.tap do |u|
u.username = "foobar"
u.save!
end
A minha pergunta é: Qual é exactamente o benefício ou a vantagem de utilizar tap
? Não podia simplesmente fazer ...
user = User.new
user.username = "foobar"
user.save!
ou melhor ainda:
user = User.create! username: "foobar"
14 answers
Quando os leitores encontrarem:
user = User.new
user.username = "foobar"
user.save!
Eles teriam que seguir todas as três linhas e então reconhecer que está apenas criando uma instância chamada user
.
Se fosse:
user = User.new.tap do |u|
u.username = "foobar"
u.save!
end
Então isso seria imediatamente claro. Um leitor não teria que ler o que está dentro do bloco para saber que uma instância user
é criada.
Outro caso para usar o tap é fazer manipulação no objecto antes de o devolver.
Então, em vez disto ...def some_method
...
some_object.serialize
some_object
end
Podemos salvar a linha extra:
def some_method
...
some_object.tap{ |o| o.serialize }
end
Em alguma situação esta técnica pode salvar mais do que uma linha e tornar o código mais compacto.
Usar o tap, como o blogueiro fez, é simplesmente um método de conveniência. Pode ter sido exagero no seu exemplo, mas nos casos em que você gostaria de fazer um monte de coisas com o usuário, tap pode sem dúvida fornecer uma interface mais limpa. Então, talvez seja melhor em um exemplo como segue:
user = User.new.tap do |u|
u.build_profile
u.process_credit_card
u.ship_out_item
u.send_email_confirmation
u.blahblahyougetmypoint
end
Usar o acima torna fácil ver rapidamente que todos esses métodos são agrupados em que todos eles se referem ao mesmo objeto (o USUÁRIO neste exemplo). A alternativa seria be:
user = User.new
user.build_profile
user.process_credit_card
user.ship_out_item
user.send_email_confirmation
user.blahblahyougetmypoint
[[3]Novamente, isso é discutível - mas o caso pode ser feito que a segunda versão parece um pouco mais confuso, e leva um pouco mais de análise humana para ver que todos os métodos estão sendo chamados sobre o mesmo objeto.
Visualize o seu exemplo dentro de uma função
def make_user(name)
user = User.new
user.username = name
user.save!
end
Há um grande risco de manutenção com essa abordagem, basicamente o valor de retorno implícito.
Nesse código, você depende de save!
devolver o utilizador gravado. Mas se você usar um pato diferente (ou o seu atual evolui) você pode obter outras coisas como um relatório de Estado de conclusão. Portanto, mudanças no pato pode quebrar o código, algo que não aconteceria se você garantir o valor de retorno com uma planície user
ou usar torneira.
O valor de retorno implícito tende a ser uma daquelas coisas em que os novatos tendem a quebrar as coisas adicionando novo código após a última linha sem notar o efeito. Eles não vêem o que o código acima realmente significa:
def make_user(name)
user = User.new
user.username = name
return user.save! # notice something different now?
end
Isto pode ser útil com a depurar uma série de ActiveRecord
âmbitos acorrentados.
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')
Isto torna super fácil depurar em qualquer ponto da cadeia sem ter de armazenar nada numa variável local nem exigir muita Alteração do código original.
E por último, use-o como uma forma rápida e discreta de depurar sem perturbar a execução normal do Código.:
def rockwell_retro_encabulate
provide_inverse_reactive_current
synchronize_cardinal_graham_meters
@result.tap(&method(:puts))
# Will debug `@result` just before returning it.
end
Uma variação na resposta de @sawa:
Como já foi referido, o uso de tap
ajuda a descobrir a intenção do seu código (embora não necessariamente tornando-o mais compacto).
As duas funções seguintes são igualmente longas, mas na primeira você tem que ler até o fim para descobrir por que inicializei um Hash vazio no início.
def tapping1
# setting up a hash
h = {}
# working on it
h[:one] = 1
h[:two] = 2
# returning the hash
h
end
Aqui, por outro lado, você sabe desde o início que o hash a ser inicializado será a saída do bloco (e, neste case, o valor de retorno da função).
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
Rende-se ao bloco, e depois devolve-se.{[21] } o objectivo principal deste método é "tocar" em uma cadeia de métodos, a fim de realizar operações sobre resultados intermédios dentro da corrente.
Se nós procurarmos o código fonte dos trilhos para tap
Uso , podemos encontrar alguns usos interessantes. Abaixo estão alguns itens (lista não exaustiva) que nos darão poucas idéias sobre como usá-los:
-
Adicione um elemento a uma matriz com base em certas condições
%w( annotations ... routes tmp ).tap { |arr| arr << 'statistics' if Rake.application.current_scope.empty? }.each do |task| ... end
-
Inicializar uma matriz e devolvê-la
[].tap do |msg| msg << "EXPLAIN for: #{sql}" ... msg << connection.explain(sql, bind) end.join("\n")
-
Como açúcar sintático para tornar o código mais legível-pode-se dizer, em baixo exemplo, use das variáveis
hash
eserver
torna a intenção do código mais clara.def select(*args, &block) dup.tap { |hash| hash.select!(*args, &block) } end
-
Inicializar / invocar métodos em objectos criados recentemente.
Rails::Server.new.tap do |server| require APP_PATH Dir.chdir(Rails.application.root) server.start end
Abaixo está um exemplo do ficheiro de teste
@pirate = Pirate.new.tap do |pirate| pirate.catchphrase = "Don't call me!" pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}] pirate.save! end
-
Para agir com base no resultado de uma chamada
yield
sem ter de usar uma variável temporária.yield.tap do |rendered_partial| collection_cache.write(key, rendered_partial, cache_options) end
Se quisesse devolver o utilizador depois de definir o nome de utilizador, teria de o fazer
user = User.new
user.username = 'foobar'
user
Com tap
podes salvar aquele regresso embaraçoso
User.new.tap do |user|
user.username = 'foobar'
end
Mantenho a opinião de que {[[0]} é desnecessário on the burden on the readability of the code, and could be done without, or substituted with a better technique, like Extract Method .
Embora {[[0]} Seja um método de conveniência, também é uma preferência pessoal. Experimenta. Em seguida, escreva algum código sem usar tap, veja se você gosta de uma maneira sobre outra.
an_object.tap do |o|
# do stuff with an_object, which is in o #
end ===> an_object
O benefício é que o tap devolve sempre o objecto em que é chamado, mesmo que o bloco devolva algum outro resultado. Assim, você pode inserir um bloco de torneira no meio de um pipeline de método existente sem quebrar o fluxo.
Tem razão: o uso de {[[0]} no seu exemplo é meio inútil e provavelmente menos limpo do que as suas alternativas.
Como observa Rebitzele, {[[0]} é apenas um método de conveniência, muitas vezes usado para criar uma referência mais curta para o objeto atual.
Um bom caso de uso para tap
é para depuração: poderá modificar o objecto, imprimir o estado actual e continuar a modificar o objecto no mesmo bloco. Veja aqui por exemplo: http://moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressions.
Eu ocasionalmente gosto de usar {[[0]} métodos internos para voltar condicionalmente cedo ao devolver o objecto actual de outra forma.
Pode haver um número de usos e lugares onde podemos ser capazes de usar tap
. Até agora só encontrei 2 Usos de tap
.
1) o objectivo principal deste método é aceder a uma cadeia de métodos, a fim de realizar operações sobre resultados intermédios dentro da cadeia. I. e
(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}" }
Alguma vez se viu a chamar método a um objecto, e o valor de retorno não era o que queria? Talvez quisesse adicionar um valor arbitrário a um conjunto de parâmetros armazenados num hash. Actualiza - o com haxixe.[] , mas você volta bar Em vez do hash dos params, então você tem que devolvê-lo explicitamente. I. e
def update_params(params)
params[:foo] = 'bar'
params
end
Para superar esta situação aqui, tap
o método entra em jogo. Liga-lhe para o objecto e passa um bloco com o código que querias executar. O objeto será entregue ao bloco, então será devolvido. I. e
def update_params(params)
params.tap {|p| p[:foo] = 'bar' }
end
Existem dezenas de outros casos de Uso, tente encontrá-los você mesmo. :)
Origem:
1) API Dock Object tap
2) five-ruby-methods-you-should-be-using
Em carris podemos usar tap
para os parâmetros da lista branca explicitamente:
def client_params
params.require(:client).permit(:name).tap do |whitelist|
whitelist[:name] = params[:client][:name]
end
end
Você pode tornar os seus códigos mais modulares usando tap, e pode alcançar uma melhor gestão das variáveis locais. Por exemplo, no seguinte código, você não precisa atribuir uma variável local ao objeto recém-criado, no escopo do método. Note que a variável do bloco, U , é escopada dentro do bloco. É na verdade uma das belezas do código ruby.
def a_method
...
name = "foobar"
...
return User.new.tap do |u|
u.username = name
u.save!
end
end