Skip to content
This repository has been archived by the owner on May 4, 2024. It is now read-only.

Low level Helpers

Stan Lo edited this page May 31, 2020 · 3 revisions

tap_init!

tap_init!(class) - tracks a class’ instance initialization

calls = []
tap_init!(Student) do |payload|
  calls << [payload[:method_name], payload[:arguments]]
end

Student.new("Stan", 18)
Student.new("Jane", 23)

puts(calls.to_s) #=> [[:initialize, {:name=>"Stan", :age=>18}], [:initialize, {:name=>"Jane", :age=>23}]]

tap_on!

tap_on!(object) - tracks any calls received by the object.

class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  def show
    tap_on!(@post).and_print(:method_name_and_location)
  end
end

And you can see these in log:

name FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
user_id FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
to_param FROM /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236

Also check the track_as_records option if you want to track ActiveRecord records.

tap_passed!

tap_passed!(target) tracks method calls that takes the target as its argument. This is particularly useful when debugging libraries. It saves your time from jumping between files and check which path the object will go.

class PostsController < ApplicationController
  # GET /posts/new
  def new
    @post = Post.new

    tap_passed!(@post) do |payload|
      puts(payload.passed_at(with_method_head: true))
    end
  end
end
Passed as 'record' in method ':polymorphic_mapping'
  > def polymorphic_mapping(record)
  at /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_dispatch/routing/polymorphic_routes.rb:131
Passed as 'klass' in method ':get_method_for_class'
  > def get_method_for_class(klass)
  at /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_dispatch/routing/polymorphic_routes.rb:269
Passed as 'record' in method ':handle_model'
  > def handle_model(record)
  at /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_dispatch/routing/polymorphic_routes.rb:227
Passed as 'record_or_hash_or_array' in method ':polymorphic_method'
  > def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options)
  at /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_dispatch/routing/polymorphic_routes.rb:139

tap_assoc!

tap_assoc!(activerecord_object) tracks association calls on a record, like post.comments

tap_assoc!(order).and_print(:method_name_and_location)
payments FROM /RUBY_PATH/gems/2.6.0/gems/jsonapi-resources-0.9.10/lib/jsonapi/resource.rb:124
line_items FROM /MY_PROJECT/app/models/line_item_container_helpers.rb:44
effective_line_items FROM /MY_PROJECT/app/models/line_item_container_helpers.rb:110
amending_orders FROM /MY_PROJECT/app/models/order.rb:385
amends_order FROM /MY_PROJECT/app/models/order.rb:432

Options

with_trace_to

It takes an integer as the number of traces we want to put into trace. Default is nil, so trace would be empty.

stan = Student.new("Stan", 18)
tap_on!(stan, with_trace_to: 5)

stan.name

puts(device.calls.first.trace) #=>
/Users/st0012/projects/tapping_device/spec/tapping_device_spec.rb:287:in `block (4 levels) in <top (required)>'
/Users/st0012/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.2/lib/rspec/core/example.rb:257:in `instance_exec'
/Users/st0012/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.2/lib/rspec/core/example.rb:257:in `block in run'
/Users/st0012/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.2/lib/rspec/core/example.rb:503:in `block in with_around_and_singleton_context_hooks'
/Users/st0012/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.2/lib/rspec/core/example.rb:460:in `block in with_around_example_hooks'
/Users/st0012/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.2/lib/rspec/core/hooks.rb:464:in `block in run'

track_as_records

It makes the device to track objects as they are ActiveRecord instances. For example:

tap_on!(@post, track_as_records: true)
post = Post.find(@post.id) # same record but a different object
post.title #=> this call will be recorded as well

exclude_by_paths

It takes an array of call path patterns that we want to skip. This could be very helpful when working on a large project like Rails applications.

tap_on!(@post, exclude_by_paths: [/active_record/]).and_print(:method_name_and_location)
_read_attribute FROM  /RUBY_PATH/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
name FROM  /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
_read_attribute FROM  /RUBY_PATH/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
user_id FROM  /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
.......

# versus

name FROM  /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
user_id FROM  /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
to_param FROM  /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236

filter_by_paths

Like exclude_by_paths, but work in the opposite way.

Payload

All tapping methods (start with tap_) takes a block and yield a Payload object as a block argument. It responds to

  • target - the target for tap_x call
  • receiver - the receiver object
  • method_name - method’s name (symbol)
    • e.g. :name
  • method_object - the method object that's being called. It might be nil in some edge cases.
  • arguments - arguments of the method call
    • e.g. {name: “Stan”, age: 25}
  • return_value - return value of the method call
  • filepath - path to the file that performs the method call
  • line_number
  • defined_class - in which class that defines the method being called
  • trace - stack trace of the call. Default is an empty array unless with_trace_to option is set
  • sql - sql that generated from the call (only present in tap_sql! payloads)
  • tp - trace point object of this call