[ANN] Detective - a new ruby gem

Detective is a new gem build by BadrIT (www.badrit.com) to investigate
the ruby source code.

Example
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

Detective is hosted on gemcutter
[sudo] gem install detective

More examples and details on Detective homepage

On Sat, Nov 21, 2009 at 9:42 PM, Ahmed E.
[email protected]wrote:

Result

def find_by_sql(sql)
connection.select_all(sanitize_sql(sql), “#{name} Load”)ollect!
{ |record| instantiate(record) }
end

Interesting - just got a problem when have fun with it in irb:

I have a simple hello.rb:

class Foo
def bar
puts “hey”
end
end

in irb:

require ‘hello’
=> true
require ‘detective’
=> true
Detective.view_source “Foo#bar”
=> " def bar\n puts “hey”\n end\n"
class Foo
def bar
puts “changed”
end
end
=> nil
Detective.view_source “Foo#bar”
=> “Cannot find source code”
Foo.new.bar
changed

A bug or I did something wrong?

Thanks,
Jan

Detective is hosted on gemcutter

Emmm …
Actually that’s how it should work. It’s not a bug.
What the gem actually does is opening the files where you have defined a
method and extract the lines of method definition.
When you define a method in irb it’s not saved in any files. This means
we have now way to extract its code.
The same problem occurs when you define a method using eval method. The
method is defined in a string variable (memory) not in a file.
Provided that the above cases rarely happen in a real application, we
can say that this is not a problem with Detective.

Flower Born wrote:

On Sat, Nov 21, 2009 at 9:42 PM, Ahmed E.
[email protected]wrote:

Result

def find_by_sql(sql)
connection.select_all(sanitize_sql(sql), “#{name} Load”)ollect!
{ |record| instantiate(record) }
end

Interesting - just got a problem when have fun with it in irb:

I have a simple hello.rb:

class Foo
def bar
puts “hey”
end
end

in irb:

require ‘hello’
=> true
require ‘detective’
=> true
Detective.view_source “Foo#bar”
=> " def bar\n puts “hey”\n end\n"
class Foo
def bar
puts “changed”
end
end
=> nil
Detective.view_source “Foo#bar”
=> “Cannot find source code”
Foo.new.bar
changed

A bug or I did something wrong?

Thanks,
Jan

Detective is hosted on gemcutter

Actually it works correctly with dynamically defined methods; I mean
methods defined using ‘define_method’.
For example, here’s a method defined dynamically in another gem called
QuickMagick.
/usr/local/lib/ruby/gems/1.8/gems/quick_magick-0.7.1/lib/quick_magick/image.rb,
line 184
184: define_method((method).to_sym) do |*args|
185: append_to_operators(method, QuickMagick::geometry(*args) )
186: end

I tried Detective with it and here’s the result

print Detective.view_source(‘QuickMagick::Image#resize’, :rdoc)
/usr/local/lib/ruby/gems/1.8/gems/quick_magick-0.7.1/lib/quick_magick/image.rb,
line 185
185: append_to_operators(method, QuickMagick::geometry(*args) )

It showed me just the body of the method not the header. But I think
this is enough. Actually this is what ruby executes, so there’s nothing
more to tell you.

For methods working using method_missing, you are correct. Detective
will not show it to you. I’ll consider this in the next gem release.

Marnen Laibow-Koser wrote:

I like the idea behind Detective, but if it can’t cope with dynamically
defined methods, then it won’t be terribly useful in practice.
method_missing trickery and dynamic metaprogramming are fairly common in
Ruby.

Best,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

Ahmed E. wrote:

Emmm …
Actually that’s how it should work. It’s not a bug.
What the gem actually does is opening the files where you have defined a
method and extract the lines of method definition.
When you define a method in irb it’s not saved in any files. This means
we have now way to extract its code.
The same problem occurs when you define a method using eval method. The
method is defined in a string variable (memory) not in a file.
Provided that the above cases rarely happen in a real application, we
can say that this is not a problem with Detective.

I like the idea behind Detective, but if it can’t cope with dynamically
defined methods, then it won’t be terribly useful in practice.
method_missing trickery and dynamic metaprogramming are fairly common in
Ruby.

Best,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

Marnen Laibow-Koser wrote:

I like the idea behind Detective, but if it can’t cope with dynamically
defined methods, then it won’t be terribly useful in practice.
method_missing trickery and dynamic metaprogramming are fairly common in
Ruby.

Best,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

I’ve released a new version which supports method_missing like methods.
Here’s an example
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

On Nov 22, 12:39 pm, Ahmed E. [email protected] wrote:

I’ve released a new version which supports method_missing like methods.
Here’s an example
Detective is now also working with method_missing …

Detective.view_source(‘ActiveRecord::Base#find_by_id’)

The trouble is that find_by_id is a class method of AR::Base, not an
instance method (you should be looking for
ActiveRecord::Base.find_by_id). It seems you have correctly returned
the instance method_missing (ActiveRecord::Base#method_missing), but
it’s highly unlikely that this will fulfill a request for find_by_id.

I haven’t bothered to look at the Detective source. Since you
mentioned something about finding the lines in the file and then
opening the file itself to get the source code, I suggest you may want
to take a look at ruby2ruby.

Marcin R. wrote:

It’s not using introspecition (like ruby2ruby) but simply finds a
declaration of function in file!

So you get content of the function just like you written it,

That’s correct. It is not meant to be a kind of reverse-engineering. It
relies on the fact that the source code is somewhere on your machine.
It just finds it quickly and displays it to you. It needs to show the
original code with its comments and indentation. This allows the
developer to easily override a method or find bugs.

but unfrotunatelly, all metaprogramming is lost
Not exactly. Methods overrided with a plugin or gem can still be shown
correctly. There’s an example of a method overrided and it shows the new
code not the old one. Also there’s another example with a method defined
using define_method and it still gets the correct source code and its
location.
However, it cannot find methods defined in a string using eval() method.
It will tell you “cannot find source code”. It will not show you an
incorrect or false code. Though, I think methods defined using eval
scripts are very rare in the real world. Do you know famous examples for
it?

I’m thinking now of using introspecition with methods that I cannot find
its source code. This will be some kind of plan B. I’ll consider this in
a future release.

It’s not using introspecition (like ruby2ruby) but simply finds a
declaration of function in file!

So you get content of the function just like you written it, but
unfrotunatelly, all metaprogramming is lost