advantage of tap method in ruby

后端 未结 19 1003
天命终不由人
天命终不由人 2020-12-22 16:50

I was just reading a blog article and noticed that the author used tap in a snippet something like:

user = User.new.tap do |u|
  u.username = \         


        
相关标签:
19条回答
  • 2020-12-22 17:45

    It results in less-cluttered code as the scope of variable is limited only to the part where it is really needed. Also, the indentation within the block makes the code more readable by keeping relevant code together.

    Description of tap says:

    Yields self to the block, and then returns self. The primary purpose of this method is to “tap into” a method chain, in order to perform operations on intermediate results within the chain.

    If we search rails source code for tap usage, we can find some interesting usages. Below are few items (not exhaustive list) that will give us few ideas on how to use them:

    1. Append an element to an array based on certain conditions

      %w(
      annotations
      ...
      routes
      tmp
      ).tap { |arr|
        arr << 'statistics' if Rake.application.current_scope.empty?
      }.each do |task|
        ...
      end
      
    2. Initializing an array and returning it

      [].tap do |msg|
        msg << "EXPLAIN for: #{sql}"
        ...
        msg << connection.explain(sql, bind)
      end.join("\n")
      
    3. As syntactic sugar to make code more readable - One can say, in below example, use of variables hash and server makes the intent of code clearer.

      def select(*args, &block)
          dup.tap { |hash| hash.select!(*args, &block) }
      end
      
    4. Initialize/invoke methods on newly created objects.

      Rails::Server.new.tap do |server|
         require APP_PATH
         Dir.chdir(Rails.application.root)
         server.start
      end
      

      Below is an example from test file

      @pirate = Pirate.new.tap do |pirate|
        pirate.catchphrase = "Don't call me!"
        pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}]
        pirate.save!
      end
      
    5. To act on the result of a yield call without having to use a temporary variable.

      yield.tap do |rendered_partial|
        collection_cache.write(key, rendered_partial, cache_options)
      end
      
    0 讨论(0)
  • 2020-12-22 17:46

    If you wanted to return the user after setting the username you'd need to do

    user = User.new
    user.username = 'foobar'
    user
    

    With tap you could save that awkward return

    User.new.tap do |user|
      user.username = 'foobar'
    end
    
    0 讨论(0)
  • 2020-12-22 17:48

    This can be useful with debugging a series of ActiveRecord chained scopes.

    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')
    

    This makes it super easy to debug at any point in the chain without having to store anything in in a local variable nor requiring much altering of the original code.

    And lastly, use it as a quick and unobtrusive way to debug without disrupting normal code execution:

    def rockwell_retro_encabulate
      provide_inverse_reactive_current
      synchronize_cardinal_graham_meters
      @result.tap(&method(:puts))
      # Will debug `@result` just before returning it.
    end
    
    0 讨论(0)
  • 2020-12-22 17:50

    In the world where functional programming pattern is becoming a best practice (https://maryrosecook.com/blog/post/a-practical-introduction-to-functional-programming), you can see tap, as a map on a single value, indeed, to modify your data on a transformation chain.

    transformed_array = array.map(&:first_transformation).map(&:second_transformation)
    
    transformed_value = item.tap(&:first_transformation).tap(&:second_transformation)
    

    No need to declare item multiple times here.

    0 讨论(0)
  • 2020-12-22 17:52

    Using tap, as the blogger did, is simply a convenience method. It may have been overkill in your example, but in cases where you'd want to do a bunch of things with the user, tap can arguably provide a cleaner looking interface. So, perhaps it may be better in an example as follows:

    user = User.new.tap do |u|
      u.build_profile
      u.process_credit_card
      u.ship_out_item
      u.send_email_confirmation
      u.blahblahyougetmypoint
    end
    

    Using the above makes it easy to quickly see that all those methods are grouped together in that they all refer to the same object (the user in this example). The alternative would be:

    user = User.new
    user.build_profile
    user.process_credit_card
    user.ship_out_item
    user.send_email_confirmation
    user.blahblahyougetmypoint
    

    Again, this is debatable - but the case can be made that the second version looks a little messier, and takes a little more human parsing to see that all the methods are being called on the same object.

    0 讨论(0)
  • 2020-12-22 17:52

    When readers encounter:

    user = User.new
    user.username = "foobar"
    user.save!
    

    they would have to follow all the three lines and then recognize that it is just creating an instance named user.

    If it were:

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

    then that would be immediately clear. A reader would not have to read what is inside the block to know that an instance user is created.

    0 讨论(0)
提交回复
热议问题