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"
 94
Author: Kyle Decot, 2013-07-05

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.
 83
Author: sawa, 2016-05-13 14:11:18

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.

 29
Author: megas, 2013-07-05 17:12:56

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.
 21
Author: Rebitzele, 2013-07-05 17:21:47

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.

Já vi acidentes como este muitas vezes, especialmente com funções em que o valor de retorno não é normalmente utilizado, excepto num canto escuro.

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
 11
Author: SystematicFrank, 2015-02-20 14:03:12

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
 11
Author: VKatz, 2016-11-02 07:27:23

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
 9
Author: Giuseppe, 2015-05-13 17:38:40
Resulta em código menos confuso, uma vez que o âmbito da variável é limitado apenas à parte em que é realmente necessário. Além disso, a indentação dentro do bloco torna o código mais legível mantendo o código relevante junto.

A descrição de tap diz:

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:

  1. 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
    
  2. Inicializar uma matriz e devolvê-la
    [].tap do |msg|
      msg << "EXPLAIN for: #{sql}"
      ...
      msg << connection.explain(sql, bind)
    end.join("\n")
    
  3. Como açúcar sintático para tornar o código mais legível-pode-se dizer, em baixo exemplo, use das variáveis hash e server torna a intenção do código mais clara.

    def select(*args, &block)
        dup.tap { |hash| hash.select!(*args, &block) }
    end
    
  4. 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
    
  5. 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
    
 9
Author: Wand Maker, 2016-05-09 05:06:53

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
 7
Author: montrealmike, 2017-04-02 00:40:09
Eu diria que não há vantagem em usar {[[0]}. O único benefício potencial, como @sawa aponta é, e cito: "um leitor não teria que ler o que está dentro do bloco para saber que um usuário de instância é criado."No entanto, nesse ponto o argumento pode ser feito de que se você estiver fazendo lógica de criação de registros não simplista, sua intenção seria melhor comunicada extraindo essa lógica em seu próprio método.

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.

 6
Author: gylaz, 2017-05-23 12:34:45
É um ajudante para a call chaining. Passa o seu objecto para o bloco dado e, após o bloco terminar, devolve o objecto:
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.

 6
Author: Pushp Raj Saurabh, 2015-07-31 14:35:49

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.

 3
Author: Jacob Brown, 2013-07-05 16:55:56

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

 3
Author: Aamir, 2017-01-25 05:55:33

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
 1
Author: Ashan Priyadarshana, 2017-01-25 05:52:26

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
 0
Author: user3936126, 2014-08-13 06:13:27