Skip to content

Instantly share code, notes, and snippets.

@alpaca-tc
Last active June 29, 2023 13:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alpaca-tc/f30a33b1d5ef28eb527caa8228b844a3 to your computer and use it in GitHub Desktop.
Save alpaca-tc/f30a33b1d5ef28eb527caa8228b844a3 to your computer and use it in GitHub Desktop.
実際のスクリプトから少し削ってる。そのままだと動かないかも :gomenne:
# ./bin/rails r script/reflection_dumper.rb
class MethodDumper
def initialize(&block)
@instance_methods = Hash.new { |h, k| h[k] = Set.new }
@class_methods = Hash.new { |h, k| h[k] = Set.new }
@block = block
end
def add_method_trace(method)
if method.include?(".")
klass, method_name = method.split(".")
@class_methods[klass.constantize].add(method_name.to_sym)
elsif method.include?("#")
klass, method_name = method.split("#")
@instance_methods[klass.constantize].add(method_name.to_sym)
else
raise NotImplementedError, "not implemented"
end
end
def run(&on_trace)
trace_point = TracePoint.new(:call) do |tp|
next unless find_definition?(trace_point)
# 呼び出し元
delimiter = class?(tp.self) ? "." : "#"
begin
on_trace.call(
klass: class?(tp.self) ? tp.self : tp.self.class, # 呼び出したクラス名
method_name: "#{trace_point.defined_class.name}#{delimiter}#{tp.method_id}",
trace_point: tp,
)
rescue StandardError => e
warn(e)
warn(e.backtrace)
exit(1)
end
end
begin
trace_point.enable
@block.call
ensure
trace_point.disable
end
end
private
def find_definition?(trace_point)
if class?(trace_point.self)
@class_methods[trace_point.defined_class].include?(trace_point.method_id)
elsif instance?(trace_point.self)
@instance_methods[trace_point.defined_class].include?(trace_point.method_id)
end
end
def class?(klass)
Class === klass
rescue StandardError
# method_missingが呼ばれるケースで失敗することがある
false
end
def instance?(obj)
!class?(obj)
end
end
dumper = MethodDumper.new do
Rails.application.reloader.reload!
Rails.application.eager_load!
end
# メソッド呼び出しをdumpするメソッド群を定義する
dumper.add_method_trace("ActiveRecord::Associations::ClassMethods.has_one")
dumper.add_method_trace("ActiveRecord::Associations::ClassMethods.has_many")
dumper.add_method_trace("ActiveRecord::Associations::ClassMethods.belongs_to")
backtrace_cleaner = ActiveSupport::BacktraceCleaner.new.tap do |cleaner|
cleaner.add_silencer { !_1.start_with?("#{Rails.root}/app") }
end
ANONYMOUS_ARGUMENT_NAMES = %i(* ** &).freeze
CSV.open("tmp/dumps/reflections.csv", "w") do |csv|
headers = %i(klass method_name path node_id parameters)
csv << headers
dumper.run do |row|
trace_point = row[:trace_point]
backtrace = backtrace_cleaner.clean(trace_point.send(:caller))
called_from = backtrace[0].to_s
path, * = called_from.split(":")
next unless path && File.exist?(path)
caller_location = trace_point.send(:caller_locations).find { _1.to_s == called_from }
node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(caller_location)
# ローカル変数(引数)
parameter_names = trace_point.self.method(trace_point.method_id).parameters.map { _2 }
parameters = (parameter_names - ANONYMOUS_ARGUMENT_NAMES).to_h { [_1, trace_point.binding.local_variable_get(_1)] }
row = {
klass: row[:klass].to_s,
method_name: trace_point.method_id, # 呼び出したメソッド名
path:,
node_id:,
parameters: JSON.dump(parameters),
}
csv << headers.map { row.fetch(_1) }
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment