class TappingDevice
Constants
- CALLER_START_POINT
- C_CALLER_START_POINT
- VERSION
Attributes
calls[R]
options[R]
target[R]
trace_point[R]
Public Class Methods
config()
click to toggle source
# File lib/tapping_device/configuration.rb, line 31 def self.config @config ||= Configuration.new end
new(options = {}, &block)
click to toggle source
# File lib/tapping_device.rb, line 31 def initialize(options = {}, &block) @block = block @output_block = nil @options = process_options(options.dup) @calls = [] @disabled = false @with_condition = nil TappingDevice.devices << self end
Public Instance Methods
create_child_device()
click to toggle source
# File lib/tapping_device.rb, line 58 def create_child_device new_device = self.class.new(@options.merge(root_device: root_device), &@block) new_device.stop_when(&@stop_when) new_device.instance_variable_set(:@target, @target) self.descendants << new_device new_device end
descendants()
click to toggle source
# File lib/tapping_device.rb, line 70 def descendants options[:descendants] end
root_device()
click to toggle source
# File lib/tapping_device.rb, line 66 def root_device options[:root_device] end
set_block(&block)
click to toggle source
# File lib/tapping_device.rb, line 45 def set_block(&block) @block = block end
stop!()
click to toggle source
# File lib/tapping_device.rb, line 49 def stop! @disabled = true TappingDevice.delete_device(self) end
stop_when(&block)
click to toggle source
# File lib/tapping_device.rb, line 54 def stop_when(&block) @stop_when = block end
track(object)
click to toggle source
# File lib/tapping_device.rb, line 74 def track(object) @target = object validate_target! MethodHijacker.new(@target).hijack_methods! if options[:hijack_attr_methods] @trace_point = build_minimum_trace_point(event_type: options[:event_type]) do |payload| record_call!(payload) stop_if_condition_fulfilled!(payload) end @trace_point.enable unless TappingDevice.suspend_new self end
with(&block)
click to toggle source
# File lib/tapping_device.rb, line 41 def with(&block) @with_condition = block end
Private Instance Methods
build_minimum_trace_point(event_type:) { |payload| ... }
click to toggle source
# File lib/tapping_device.rb, line 93 def build_minimum_trace_point(event_type:) TracePoint.new(*event_type) do |tp| next unless filter_condition_satisfied?(tp) filepath, line_number = get_call_location(tp) payload = build_payload(tp: tp, filepath: filepath, line_number: line_number) unless @options[:force_recording] next if is_tapping_device_call?(tp) next if should_be_skipped_by_paths?(filepath) next unless with_condition_satisfied?(payload) next if payload.is_private_call? && @options[:ignore_private] next if !payload.is_private_call? && @options[:only_private] end yield(payload) end end
build_payload(tp:, filepath:, line_number:)
click to toggle source
# File lib/tapping_device.rb, line 142 def build_payload(tp:, filepath:, line_number:) Payload.new( target: @target, receiver: tp.self, method_name: tp.callee_id, method_object: get_method_object_from(tp.self, tp.callee_id), arguments: collect_arguments(tp), return_value: (tp.return_value rescue nil), filepath: filepath, line_number: line_number, defined_class: tp.defined_class, trace: get_traces(tp), is_private_call: tp.defined_class.private_method_defined?(tp.callee_id), tag: options[:tag], tp: tp ) end
collect_arguments(tp)
click to toggle source
# File lib/tapping_device.rb, line 189 def collect_arguments(tp) parameters = if RUBY_VERSION.to_f >= 2.6 tp.parameters else get_method_object_from(tp.self, tp.callee_id)&.parameters || [] end.map { |parameter| parameter[1] } tp.binding.local_variables.each_with_object({}) do |name, args| args[name] = tp.binding.local_variable_get(name) if parameters.include?(name) end end
config()
click to toggle source
# File lib/tapping_device.rb, line 255 def config TappingDevice.config end
filter_condition_satisfied?(tp)
click to toggle source
# File lib/tapping_device.rb, line 114 def filter_condition_satisfied?(tp) false end
get_call_location(tp, padding: 0)
click to toggle source
# File lib/tapping_device.rb, line 168 def get_call_location(tp, padding: 0) caller(get_trace_index(tp) + padding).first.split(":")[0..1] end
get_method_object_from(target, method_name)
click to toggle source
# File lib/tapping_device.rb, line 160 def get_method_object_from(target, method_name) Object.instance_method(:method).bind(target).call(method_name) rescue NameError # if any part of the program uses Refinement to extend its methods # we might still get NoMethodError when trying to get that method outside the scope nil end
get_trace_index(tp)
click to toggle source
# File lib/tapping_device.rb, line 172 def get_trace_index(tp) if tp.event == :c_call C_CALLER_START_POINT else CALLER_START_POINT end end
get_traces(tp)
click to toggle source
# File lib/tapping_device.rb, line 180 def get_traces(tp) if with_trace_to = options[:with_trace_to] trace_index = get_trace_index(tp) caller[trace_index..(trace_index + with_trace_to)] else [] end end
is_from_target?(tp)
click to toggle source
# File lib/tapping_device.rb, line 219 def is_from_target?(tp) comparsion = tp.self is_the_same_record?(comparsion) || target.__id__ == comparsion.__id__ end
is_tapping_device_call?(tp)
click to toggle source
# File lib/tapping_device.rb, line 126 def is_tapping_device_call?(tp) if tp.defined_class == TappingDevice::Trackable || tp.defined_class == TappingDevice return true end if Module.respond_to?(:module_parents) tp.defined_class.module_parents.include?(TappingDevice) elsif Module.respond_to?(:parents) tp.defined_class.parents.include?(TappingDevice) end end
is_the_same_record?(comparsion)
click to toggle source
# File lib/tapping_device.rb, line 224 def is_the_same_record?(comparsion) return false unless options[:track_as_records] if target.is_a?(ActiveRecord::Base) && comparsion.is_a?(target.class) primary_key = target.class.primary_key target.send(primary_key) && target.send(primary_key) == comparsion.send(primary_key) end end
process_options(options)
click to toggle source
# File lib/tapping_device.rb, line 202 def process_options(options) options[:filter_by_paths] ||= config[:filter_by_paths] options[:exclude_by_paths] ||= config[:exclude_by_paths] options[:with_trace_to] ||= config[:with_trace_to] options[:event_type] ||= config[:event_type] options[:hijack_attr_methods] ||= config[:hijack_attr_methods] options[:track_as_records] ||= config[:track_as_records] options[:ignore_private] ||= config[:ignore_private] options[:only_private] ||= config[:only_private] # for debugging the gem more easily options[:force_recording] ||= false options[:descendants] ||= [] options[:root_device] ||= self options end
record_call!(payload)
click to toggle source
# File lib/tapping_device.rb, line 232 def record_call!(payload) return if @disabled write_output!(payload) if @output_writer if @block root_device.calls << @block.call(payload) else root_device.calls << payload end end
should_be_skipped_by_paths?(filepath)
click to toggle source
this needs to be placed upfront so we can exclude noise before doing more work
# File lib/tapping_device.rb, line 119 def should_be_skipped_by_paths?(filepath) exclude_by_paths = options[:exclude_by_paths] filter_by_paths = options[:filter_by_paths] exclude_by_paths.any? { |pattern| pattern.match?(filepath) } || (filter_by_paths && !filter_by_paths.empty? && !filter_by_paths.any? { |pattern| pattern.match?(filepath) }) end
stop_if_condition_fulfilled!(payload)
click to toggle source
# File lib/tapping_device.rb, line 248 def stop_if_condition_fulfilled!(payload) if @stop_when&.call(payload) stop! root_device.stop! end end
validate_target!()
click to toggle source
# File lib/tapping_device.rb, line 112 def validate_target!; end
with_condition_satisfied?(payload)
click to toggle source
# File lib/tapping_device.rb, line 138 def with_condition_satisfied?(payload) @with_condition.nil? || @with_condition.call(payload) end
write_output!(payload)
click to toggle source
# File lib/tapping_device.rb, line 244 def write_output!(payload) @output_writer.write!(payload) end