web-dev-qa-db-fra.com

avantage de la méthode du robinet en rubis

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"
100
Kyle Decot

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.

92
sawa

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.

31
megas

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.

21
Rebitzele

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
14
VKatz

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
11
SystematicFrank

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
10
montrealmike

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.

Description de tap dit :

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:

  1. 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
    
  2. Initialiser un tableau et le renvoyer

    [].tap do |msg|
      msg << "EXPLAIN for: #{sql}"
      ...
      msg << connection.explain(sql, bind)
    end.join("\n")
    
  3. 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
    
  4. 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
    
  5. 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
    
10
Wand Maker

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
9
Giuseppe

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.

7
gylaz

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.

7
Pushp Raj Saurabh

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.

3
Jacob Brown

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

3
Aamir

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
1
Ashan Priyadarshana

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
1
rubyprince

Quelle est la différence?

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.

Code à travers:

user = User.new.tap do |u|
  u.username = "foobar"
  u.save!
end

Points clés: 

  • Remarquez comment la variable u est maintenant utilisée comme paramètre de bloc?
  • Une fois le bloc terminé, la variable user doit maintenant pointer sur un utilisateur (avec un nom d'utilisateur: ‘foobar’ et qui est également enregistré).
  • C'est juste agréable et facile à lire.

Documentation API

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.

0
BKSpurgeon

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!
0
Evmorov

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
0
user3936126