Je venais de lire un article de blog et j'ai remarqué que l'auteur utilisait tap
dans un extrait, par exemple:
user = User.new.tap do |u|
u.username = "foobar"
u.save!
end
Ma question est la suivante: quel est exactement l’avantage d’utiliser tap
? Ne pourrais-je pas simplement faire:
user = User.new
user.username = "foobar"
user.save!
ou mieux encore:
user = User.create! username: "foobar"
Quand les lecteurs rencontrent:
user = User.new
user.username = "foobar"
user.save!
ils devraient suivre les trois lignes, puis reconnaître qu'il s'agit simplement de créer une instance nommée user
.
Si c'était:
user = User.new.tap do |u|
u.username = "foobar"
u.save!
end
alors ce serait immédiatement clair. Un lecteur n'aurait pas à lire ce qu'il y a à l'intérieur du bloc pour savoir qu'une instance user
est créée.
Un autre cas d'utilisation de tap consiste à manipuler un objet avant de le renvoyer.
Donc au lieu de cela:
def some_method
...
some_object.serialize
some_object
end
nous pouvons économiser une ligne supplémentaire:
def some_method
...
some_object.tap{ |o| o.serialize }
end
Dans certaines situations, cette technique peut économiser plus d'une ligne et rendre le code plus compact.
Taper sur, comme l'a fait le blogueur, est simplement une méthode pratique. Dans votre exemple, cela aurait peut-être été excessif, mais dans les cas où vous voudriez faire beaucoup de choses avec l'utilisateur, tap peut sans doute fournir une interface plus propre. Alors, peut-être serait-il préférable dans un exemple comme suit:
user = User.new.tap do |u|
u.build_profile
u.process_credit_card
u.ship_out_item
u.send_email_confirmation
u.blahblahyougetmypoint
end
L'utilisation de ce qui précède permet de voir rapidement que toutes ces méthodes sont regroupées en ce sens qu'elles se réfèrent toutes au même objet (l'utilisateur dans cet exemple). L'alternative serait:
user = User.new
user.build_profile
user.process_credit_card
user.ship_out_item
user.send_email_confirmation
user.blahblahyougetmypoint
Encore une fois, ceci est discutable - mais on peut affirmer que la deuxième version semble un peu plus compliquée et prend un peu plus humain à analyser pour voir que toutes les méthodes sont appelées sur le même objet.
Cela peut être utile avec debugging une série d'étendues 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')
Cela rend le débogage très facile à n’importe quel point de la chaîne sans rien stocker dans une variable locale ni une modification importante du code original.
Enfin, utilisez-le comme un moyen rapide et discret de déboguer sans perturber l’exécution de code normale:
def rockwell_retro_encabulate
provide_inverse_reactive_current
synchronize_cardinal_graham_meters
@result.tap(&method(:puts))
# Will debug `@result` just before returning it.
end
Visualisez votre exemple dans une fonction
def make_user(name)
user = User.new
user.username = name
user.save!
end
Cette approche présente un gros risque de maintenance, essentiellement/ la valeur de retour implicite .
Dans ce code, vous dépendez du fait que save!
renvoie l'utilisateur enregistré. Mais si vous utilisez un autre canard (ou que votre actuel évolue), vous obtiendrez peut-être d'autres éléments, tels qu'un rapport d'état d'achèvement. Par conséquent, les modifications apportées au canard pourraient casser le code, ce qui ne se produirait pas si vous garantissez la valeur de retour avec un user
simple ou si vous utilisez tap.
J'ai souvent vu des accidents comme celui-ci, spécialement avec des fonctions où la valeur de retour n'est normalement pas utilisée, à l'exception d'un coin sombre.
La valeur de retour implicite a tendance à être une des choses où les débutants ont tendance à casser des choses en ajoutant un nouveau code après la dernière ligne sans remarquer l’effet. Ils ne voient pas ce que le code ci-dessus signifie vraiment:
def make_user(name)
user = User.new
user.username = name
return user.save! # notice something different now?
end
Si vous souhaitez renvoyer l'utilisateur après avoir défini le nom d'utilisateur, vous devez le faire.
user = User.new
user.username = 'foobar'
user
Avec tap
, vous pourriez économiser ce retour difficile
User.new.tap do |user|
user.username = 'foobar'
end
Il en résulte un code moins encombré car la portée de la variable est limitée à la partie où elle est réellement nécessaire. De plus, l'indentation dans le bloc rend le code plus lisible en gardant le code pertinent ensemble.
Rend soi-même au bloc, puis retourne soi-même. L'objectif principal Cette méthode consiste à «exploiter» une chaîne de méthodes pour pouvoir exécuter opérations sur les résultats intermédiaires au sein de la chaîne.
Si nous recherchons le code source de Rails pour tap
usage , nous pouvons trouver des utilisations intéressantes. Voici quelques éléments (liste non exhaustive) qui nous donneront quelques idées sur la façon de les utiliser:
Ajouter un élément à un tableau en fonction de certaines conditions
%w(
annotations
...
routes
tmp
).tap { |arr|
arr << 'statistics' if Rake.application.current_scope.empty?
}.each do |task|
...
end
Initialiser un tableau et le renvoyer
[].tap do |msg|
msg << "EXPLAIN for: #{sql}"
...
msg << connection.explain(sql, bind)
end.join("\n")
Comme sucre syntaxique pour rendre le code plus lisible - On peut dire, dans l'exemple ci-dessous, que l'utilisation des variables hash
et server
clarifie l'intention du code.
def select(*args, &block)
dup.tap { |hash| hash.select!(*args, &block) }
end
Initialiser/invoquer des méthodes sur des objets nouvellement créés.
Rails::Server.new.tap do |server|
require APP_PATH
Dir.chdir(Rails.application.root)
server.start
end
Ci-dessous un exemple de fichier de test
@pirate = Pirate.new.tap do |pirate|
pirate.catchphrase = "Don't call me!"
pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}]
pirate.save!
end
Agir sur le résultat d'un appel yield
sans avoir à utiliser de variable temporaire.
yield.tap do |rendered_partial|
collection_cache.write(key, rendered_partial, cache_options)
end
Une variation de la réponse de @ sawa:
Comme indiqué précédemment, l'utilisation de tap
permet de déterminer l'intention de votre code (sans le rendre nécessairement plus compact).
Les deux fonctions suivantes sont également longues, mais dans la première vous devez lire la fin pour comprendre pourquoi j'ai initialisé un hachage vide au début.
def tapping1
# setting up a hash
h = {}
# working on it
h[:one] = 1
h[:two] = 2
# returning the hash
h
end
Ici, par contre, vous savez dès le départ que le hachage en cours d'initialisation sera la sortie du bloc (et, dans ce cas, la valeur de retour de la fonction).
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
Je dirais qu'il n'y a aucun avantage à utiliser tap
. Le seul avantage potentiel, comme le souligne @sawa est, et je cite: "Un lecteur n'aurait pas à lire ce qui est à l'intérieur du bloc pour savoir qu'un utilisateur d'instance est créé." Cependant, à ce stade, on peut soutenir que, si vous utilisez une logique de création d’enregistrement non simpliste, votre intention serait mieux communiquée en extrayant cette logique dans sa propre méthode.
Je suis d’avis que tap
est un fardeau inutile pour la lisibilité du code, et pourrait être supprimé ou remplacé par une technique plus performante, telle que Extract Method .
Alors que tap
est une méthode pratique, c'est aussi une préférence personnelle. Essayez tap
. Ensuite, écrivez du code sans utiliser tap, voyez si vous aimez un chemin sur un autre.
C’est un assistant pour le chaînage d’appels. Il passe son objet dans le bloc donné et, une fois le bloc terminé, renvoie l'objet:
an_object.tap do |o|
# do stuff with an_object, which is in o #
end ===> an_object
L’avantage est que tap renvoie toujours l’objet sur lequel il a été appelé, même si le bloc renvoie un autre résultat. Ainsi, vous pouvez insérer un bloc de prises au milieu d'un pipeline de méthode existant sans interrompre le flux.
Vous avez raison: l'utilisation de tap
dans votre exemple est en quelque sorte inutile et probablement moins propre que vos alternatives.
Comme le note Rebitzele, tap
est simplement une méthode pratique, souvent utilisée pour créer une référence plus courte à l'objet actuel.
Un bon cas d'utilisation de tap
concerne le débogage: vous pouvez modifier l'objet, imprimer l'état actuel, puis continuer à modifier l'objet dans le même bloc. Voir par exemple ici: http://moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressions .
J'aime parfois utiliser tap
inside méthodes pour retourner conditionnellement tôt tout en retournant l'objet actuel autrement.
Il pourrait y avoir un certain nombre d'utilisations et d'emplacements où nous pourrons peut-être utiliser tap
. Jusqu'à présent, je n'ai trouvé que les 2 utilisations suivantes de tap
.
1) Le but principal de cette méthode est de puiser dans une chaîne de méthodes afin d'effectuer des opérations sur des résultats intermédiaires dans la chaîne. c'est à dire
(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) Vous est-il déjà arrivé d'appeler une méthode sur un objet et la valeur de retour n'était pas celle que vous souhaitiez? Peut-être que vous vouliez ajouter une valeur arbitraire à un ensemble de paramètres stockés dans un hachage. Vous le mettez à jour avec Hash. [], Mais vous récupérez bar à la place du paramètre hash, vous devez donc le retourner explicitement. c'est à dire
def update_params(params)
params[:foo] = 'bar'
params
end
Afin de surmonter cette situation ici, la méthode tap
entre en jeu. Appelez-le simplement sur l'objet, puis passez un bloc avec le code que vous voulez exécuter. L'objet sera cédé au bloc, puis retourné. c'est à dire
def update_params(params)
params.tap {|p| p[:foo] = 'bar' }
end
Il y a des dizaines d'autres cas d'utilisation, essayez de les trouver vous-même :)
La source:
1) API Dock Object Tap
2) méthodes à cinq rubis que vous devriez utiliser
Dans Rails, nous pouvons utiliser tap
pour ajouter explicitement des paramètres à la liste blanche:
def client_params
params.require(:client).permit(:name).tap do |whitelist|
whitelist[:name] = params[:client][:name]
end
end
Je vais donner un autre exemple que j'ai utilisé. J'ai une méthode user_params qui retourne les paramètres nécessaires pour sauvegarder pour l'utilisateur (c'est un projet Rails)
def user_params
params.require(:user).permit(
:first_name,
:last_name,
:email,
:address_attributes
)
end
Vous pouvez voir que je ne renvoie rien mais Ruby renvoie le résultat de la dernière ligne.
Ensuite, après un certain temps, je devais ajouter un nouvel attribut de manière conditionnelle. Donc, je l'ai changé en quelque chose comme ceci:
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
Ici, nous pouvons utiliser tap pour supprimer la variable locale et supprimer le retour:
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
Il n'y a pas de différence. Que vous manipuliez un objet dans un bloc ou à l'extérieur de celui-ci, cela n'a pas d'importance. La différence est purement stylistique et profite au programmeur lors de la lecture du code.
user = User.new.tap do |u|
u.username = "foobar"
u.save!
end
Points clés:
u
est maintenant utilisée comme paramètre de bloc?user
doit maintenant pointer sur un utilisateur (avec un nom d'utilisateur: ‘foobar’ et qui est également enregistré).Voici une version facile à lire du code source:
class Object
def tap
yield self
self
end
end
Pour plus d'informations, voir ces liens:
https://apidock.com/Ruby/Object/tap
http://Ruby-doc.org/core-2.2.3/Object.html#method-i-tap
HTH - toutes les questions poster un commentaire et je vais essayer d'améliorer la réponse.
Il existe un outil appelé flog qui mesure combien il est difficile de lire une méthode. "Plus le score est élevé, plus le code est pénible."
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
et selon le résultat de flog, la méthode avec tap
est la plus difficile à lire (et je suis d'accord avec elle)
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!
Vous pouvez rendre vos codes plus modulaires à l’aide de tap, et obtenir une meilleure gestion des variables locales. Par exemple, dans le code suivant, il n'est pas nécessaire d'affecter une variable locale au nouvel objet créé, dans la portée de la méthode. Notez que la variable de bloc, u , est définie dans le bloc. C'est en fait l'une des beautés du code Ruby.
def a_method
...
name = "foobar"
...
return User.new.tap do |u|
u.username = name
u.save!
end
end