Skip to content

Instantly share code, notes, and snippets.

@cutalion
Last active August 29, 2015 14:11
Show Gist options
  • Save cutalion/ab2451236ad135d0e7ec to your computer and use it in GitHub Desktop.
Save cutalion/ab2451236ad135d0e7ec to your computer and use it in GitHub Desktop.

Usage

require tp_logger in spec_helper and run TPLogger.start The main goal of early start is to catch class definitions. Therefore it is important to start it before loading rails environment and initializers.

require 'tp_logger'
TPLogger.start(filter: 'project_folder_name', dump_path: 'tplogger.csv')

Then run rspec spec and wait. Specs will take a bit more time.

Finally all relations between classes will be dumped into CSV file, which can be converted to the DOT file using csv2dot.rb script.

ruby ./csv2dot.rb tplogger.csv tplogger.dot
require 'csv'
csv = ARGV[0]
dot = ARGV[1]
g = File.open(dot, 'w')
g.puts 'strict digraph G{'
CSV.foreach(csv) do |from, method, to|
g.puts %Q{"#{from}"->"#{to}" [label="#{method}"];}
end
g.print '}'
class TPLogger
class << self
def start(filter: "", dump_path: "tplogger.csv")
record_app_classes(filter)
register_rspec_around_hook
register_at_exit_hook(dump_path)
end
def relations
@relations ||= Set.new
end
protected
def register_rspec_around_hook
RSpec.configure do |config|
config.around(:each) do |example|
logger = TPLogger.new
logger.enable
example.run
logger.disable
end
end
end
def dump(dump_path)
File.open(dump_path, 'w') do |csv|
relations.each do |from, method, to|
if app_class?(from) && app_class?(to)
csv << [from, method, to].to_csv
end
end
end
end
def register_at_exit_hook(dump_path)
at_exit do
TPLogger.dump(dump_path)
end
end
def app_classes
@app_classes ||= Set.new
end
def app_class?(klass)
app_classes.include? klass
end
def record_app_classes(app_path)
TracePoint.new(:end) do |tp|
app_classes << tp.self if tp.path.match(app_path)
end.enable
end
end
def initialize
@trace = TracePoint.new(:call, :return) do |tp|
log_tp_event tp
end
end
def relations
self.class.relations
end
def enable
@trace.enable
end
def disable
@trace.disable
end
def chain
@chain ||= []
end
def orig_class(tp)
if tp.defined_class == ActiveRecord::Associations::CollectionProxy
tp.defined_class
else
tp.self.class rescue tp.defined_class
end
end
def log_tp_event(tp)
tp.disable
case tp.event
when :call
chain.push tp.method_id, orig_class(tp)
if chain.size >= 4
relation = chain.last(3)
if relation.first != relation.last
relations << relation
end
end
when :return
chain.pop(2)
end
tp.enable
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment