detective
Detective is a gem built by BadrIT (www.badrit.com/) to investigate the ruby source code. Check the examples below.
Motivation
Tired of opening files of installed gems to know how the code is working? Not able to know who and where that function has been overrided? It’s time to get help from a private Detective. Detective allows you show the source and find the location of some ruby method.
Installation
gem install detective
Examples
View the source of a class method …
require 'detective'
Detective.view_source('ActiveRecord::Base.find_by_sql')
Result
def find_by_sql(sql)
connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
end
View the source of an instance method …
Detective.view_source('ActiveRecord::Base#update_attributes')
Result
def update_attributes(attributes)
self.attributes = attributes
save
end
View the source of an overrided method …
ActiveRecord::Base.class_eval do
def update_attributes(attributes)
puts "updating attributes ..."
self.attributes = attributes
save
end
end
Detective.view_source('ActiveRecord::Base#update_attributes')
Result
def update_attributes(attributes)
puts "updating attributes ..."
self.attributes = attributes
save
end
Find the location of some source …
Detective.get_location('ActiveRecord::Base#attributes')
Result
location
/home/aseldawy/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb
2752
(new) You can also find source code for modules …
Detective.view_source('AuthenticatedSystem#current_user')
Result
def current_user
@current_user ||= (login_from_session || login_from_basic_auth || ) unless @current_user == false
end
(new) You have an alternative output …
Detective.view_source('AuthenticatedSystem#current_user', :rdoc)
Result
/home/aseldawy/aptana_studio/archiving_system/lib/authenticated_system.rb, line 11
11: def current_user
12: @current_user ||= (login_from_session || login_from_basic_auth || login_from_cookie) unless @current_user == false
13: end
(new) Detective is now also working with method_missing …
Detective.view_source('ActiveRecord::Base#find_by_id')
Result
def method_missing(method_id, *args, &block)
method_name = method_id.to_s
if self.class.private_method_defined?(method_name)
raise NoMethodError.new("Attempt to call private method", method_name, args)
end
# If we haven't generated any methods yet, generate them, then
# see if we've created the method we're looking for.
if !self.class.generated_methods?
self.class.define_attribute_methods
if self.class.generated_methods.include?(method_name)
return self.send(method_id, *args, &block)
end
end
if self.class.primary_key.to_s == method_name
id
elsif md = self.class.match_attribute_method?(method_name)
attribute_name, method_type = md.pre_match, md.to_s
if @attributes.include?(attribute_name)
__send__("attribute#{method_type}", attribute_name, *args, &block)
else
super
end
elsif @attributes.include?(method_name)
read_attribute(method_name)
else
super
end
end
No luck with native methods …
Detective.view_source('String#length')
Result
native method
How it works (advanced)
The idea is to invoke the given method and trace the execution of the program. This allows us to know where is the definition of the method. Then with the help of RubyParser, we can extract its code from the file. The invoke of this method is made in a separate process so that it doesn’t conflict with your program. This child process is killed before the method starts its execution so the method is not really invoked.
For systems not supporting fork (like windows), the child process is replaced with a thread. This might make some problems because it is running in the same space of your own ruby program. We make our best to decrease the effect of this call by killing the thread before the method starts execution. However, given that Detective will be used while developing only, we can ignore this effect.
Copyright
Copyright © 2009 BadrIT. See LICENSE for details.