sirve - ruby tutorial
ventaja del método de tap en ruby (14)
Estaba leyendo un artículo del blog y me di cuenta de que el autor utilizaba el tap
en un fragmento como:
user = User.new.tap do |u|
u.username = "foobar"
u.save!
end
Mi pregunta es, ¿cuál es exactamente el beneficio o la ventaja de usar tap
? No podría simplemente hacer:
user = User.new
user.username = "foobar"
user.save!
o mejor aún:
user = User.create! username: "foobar"
Cuando los lectores se encuentran:
user = User.new
user.username = "foobar"
user.save!
Tendrían que seguir todas las tres líneas y luego reconocer que solo está creando una instancia llamada user
.
Si fuera:
user = User.new.tap do |u|
u.username = "foobar"
u.save!
end
entonces eso sería inmediatamente claro. Un lector no tendría que leer lo que está dentro del bloque para saber que se creó un user
instancia.
En los rieles podemos usar los parámetros de tap
para la lista blanca explícitamente:
def client_params
params.require(:client).permit(:name).tap do |whitelist|
whitelist[:name] = params[:client][:name]
end
end
Es un ayudante para el encadenamiento de llamadas. Pasa su objeto al bloque dado y, después de que el bloque termina, devuelve el objeto:
an_object.tap do |o|
# do stuff with an_object, which is in o #
end ===> an_object
El beneficio es que el toque siempre devuelve el objeto al que se llama, incluso si el bloque arroja algún otro resultado. Por lo tanto, puede insertar un bloque de tap en el medio de una tubería de método existente sin interrumpir el flujo.
Esto puede ser útil para depurar una serie de ámbitos encadenados de 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'')
Esto hace que sea muy fácil de depurar en cualquier punto de la cadena sin tener que almacenar nada en una variable local ni requiere mucha alteración del código original.
Y, por último, úselo como una forma rápida y discreta de depurar sin alterar la ejecución normal del 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
Otro caso para usar el tap es hacer una manipulación en el objeto antes de devolverlo.
Entonces en vez de esto:
def some_method
...
some_object.serialize
some_object
end
podemos ahorrar línea adicional:
def some_method
...
some_object.tap{ |o| o.serialize }
end
En alguna situación, esta técnica puede guardar más de una línea y hacer que el código sea más compacto.
Podría haber varios usos y lugares donde podamos usar el tap
. Hasta ahora solo he encontrado los siguientes 2 usos de tap
.
1) El objetivo principal de este método es acceder a una cadena de métodos para realizar operaciones en resultados intermedios dentro de la cadena. es decir
(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) ¿Alguna vez se encontró llamando a un método sobre algún objeto, y el valor de retorno no era el que quería? Tal vez quería agregar un valor arbitrario a un conjunto de parámetros almacenados en un hash. Usted lo actualiza con Hash. [] , Pero obtiene una barra de retorno en lugar del hash de params, por lo que debe devolverlo explícitamente. es decir
def update_params(params)
params[:foo] = ''bar''
params
end
Para superar esta situación aquí, tap
método entra en juego. Solo llámalo al objeto, luego pasa tap a block con el código que querías ejecutar. El objeto será cedido al bloque, luego será devuelto. es decir
def update_params(params)
params.tap {|p| p[:foo] = ''bar'' }
end
Hay docenas de otros casos de uso, intente encontrarlos usted mismo :)
Fuente:
1) Toque del objeto API Dock
2) five-ruby-methods-you-should-be-using
Puede hacer que sus códigos sean más modulares utilizando tap, y puede lograr una mejor administración de las variables locales. Por ejemplo, en el siguiente código, no necesita asignar una variable local al objeto recién creado, en el alcance del método. Tenga en cuenta que la variable de bloque, u , tiene un ámbito dentro del bloque. En realidad, es una de las bellezas del código ruby.
def a_method
...
name = "foobar"
...
return User.new.tap do |u|
u.username = name
u.save!
end
end
Resulta en un código menos recargado ya que el alcance de la variable está limitado solo a la parte donde realmente se necesita. Además, la sangría dentro del bloque hace que el código sea más legible al mantener juntos el código relevante.
Se cede al bloque y luego regresa a sí mismo. El objetivo principal de este método es "aprovechar" una cadena de métodos para realizar operaciones en resultados intermedios dentro de la cadena.
Si buscamos el código fuente de los raíles para tap
uso del tap
, podemos encontrar algunos usos interesantes. A continuación hay algunos artículos (no una lista exhaustiva) que nos darán pocas ideas sobre cómo usarlos:
Añade un elemento a una matriz según ciertas condiciones
%w( annotations ... routes tmp ).tap { |arr| arr << ''statistics'' if Rake.application.current_scope.empty? }.each do |task| ... end
Inicializando una matriz y devolviéndola
[].tap do |msg| msg << "EXPLAIN for: #{sql}" ... msg << connection.explain(sql, bind) end.join("/n")
Como azúcar sintáctico para hacer que el código sea más legible - Se puede decir, en el ejemplo a continuación, el uso del
hash
de variables y elserver
hace que la intención del código sea más clara.def select(*args, &block) dup.tap { |hash| hash.select!(*args, &block) } end
Inicializar / invocar métodos en objetos recién creados.
Rails::Server.new.tap do |server| require APP_PATH Dir.chdir(Rails.application.root) server.start end
A continuación se muestra un ejemplo del archivo de prueba
@pirate = Pirate.new.tap do |pirate| pirate.catchphrase = "Don''t call me!" pirate.birds_attributes = [{:name => ''Bird1''},{:name => ''Bird2''}] pirate.save! end
Para actuar sobre el resultado de una llamada de
yield
sin tener que usar una variable temporal.yield.tap do |rendered_partial| collection_cache.write(key, rendered_partial, cache_options) end
Si desea devolver al usuario después de configurar el nombre de usuario, deberá hacerlo
user = User.new
user.username = ''foobar''
user
Con un tap
, podría guardar ese retorno incómodo
User.new.tap do |user|
user.username = ''foobar''
end
Tienes razón: el uso del tap
en tu ejemplo es un tanto inútil y probablemente menos limpio que tus alternativas.
Como señala Rebitzele, el tap
es solo un método de conveniencia, a menudo usado para crear una referencia más corta al objeto actual.
Un buen caso de uso para el tap
es la depuración: puede modificar el objeto, imprimir el estado actual y luego continuar modificando el objeto en el mismo bloque. Vea aquí, por ejemplo: http://moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressions .
De vez en cuando me gusta usar los métodos de tap
dentro para regresar de manera condicional temprano mientras devuelvo el objeto actual de otra manera.
Una variación de la respuesta de @sawa:
Como ya se mencionó, el uso del tap
ayuda a descifrar la intención de su código (aunque no necesariamente lo hace más compacto).
Las dos funciones siguientes son igualmente largas, pero en la primera debes leer hasta el final para descubrir por qué inicialicé una Hash vacía al principio.
def tapping1
# setting up a hash
h = {}
# working on it
h[:one] = 1
h[:two] = 2
# returning the hash
h
end
Aquí, por otro lado, sabes desde el principio que el hash que se inicializa será la salida del bloque (y, en este caso, el valor de retorno de la función).
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
Usar el tap, como lo hizo el blogger, es simplemente un método de conveniencia. Puede haber sido excesivo en su ejemplo, pero en los casos en los que le gustaría hacer muchas cosas con el usuario, puede proporcionar una interfaz más limpia. Entonces, tal vez sea mejor en un ejemplo de la siguiente manera:
user = User.new.tap do |u|
u.build_profile
u.process_credit_card
u.ship_out_item
u.send_email_confirmation
u.blahblahyougetmypoint
end
El uso de lo anterior hace que sea fácil ver rápidamente que todos esos métodos están agrupados porque todos se refieren al mismo objeto (el usuario en este ejemplo). La alternativa sería:
user = User.new
user.build_profile
user.process_credit_card
user.ship_out_item
user.send_email_confirmation
user.blahblahyougetmypoint
Una vez más, esto es discutible, pero se puede argumentar que la segunda versión parece un poco más desordenada y requiere un análisis humano más para ver que se están convocando todos los métodos al mismo objeto.
Visualice su ejemplo dentro de una función
def make_user(name)
user = User.new
user.username = name
user.save!
end
Hay un gran riesgo de mantenimiento con ese enfoque, básicamente el valor de retorno implícito .
En ese código, ¡sí dependes de save!
devolviendo el usuario guardado. Pero si utilizas un pato diferente (o evoluciona tu actual), puedes obtener otras cosas, como un informe de estado de finalización. Por lo tanto, los cambios en el pato podrían romper el código, algo que no sucedería si garantiza el valor de retorno con un user
simple o use el tap.
He visto accidentes como este bastante a menudo, especialmente con funciones en las que normalmente no se utiliza el valor de retorno, excepto en una esquina con errores oscuros.
El valor de retorno implícito tiende a ser una de esas cosas en que los novatos tienden a romper cosas agregando un nuevo código después de la última línea sin darse cuenta del efecto. No ven lo que realmente significa el código anterior:
def make_user(name)
user = User.new
user.username = name
return user.save! # notice something different now?
end
Yo diría que no hay ninguna ventaja al usar tap
. El único beneficio potencial, como señala @sawa, es citar: "Un lector no tendría que leer lo que está dentro del bloque para saber que se creó un usuario de instancia". Sin embargo, en ese punto se puede argumentar que si está haciendo lógica de creación de registros no simplista, su intención sería mejor comunicada extrayendo esa lógica en su propio método.
Sostengo la opinión de que el tap
es una carga innecesaria para la legibilidad del código, y podría hacerse sin, o sustituir con una mejor técnica, como el Método Extract .
Si bien el tap
es un método de conveniencia, también es una preferencia personal. Dar tap
una oportunidad. Luego, escribe un código sin usar el toque, mira si te gusta de una forma u otra.