A Better ri

Thu Jun 12 2008

ri is the local, command-line viewer for Ruby documentation. I vastly prefer looking up documentation with ri over mousing up and down and in and out of HTML frames, scrolling through PDFs, and flipping through books, which have unfortunately become much more common ways to search Ruby documentation.

ri rocks. But one thing ri does not handle well is ambiguous method lookups.

For example, if I type

ri detect

I get this output:

More than one method matched your request. You can refine
your search by asking for information on one of:

     Enumerable#detect, YAML::detect_implicit, EnumerablePass#detect,
     HTML5::HTMLInputStream#detect_bom,
     HTML5::HTMLInputStream#detect_encoding,
     HTML5::HTMLInputStream#detect_encoding_meta,
     Mocha::ExpectationList#detect

And now I would have to type this to get the documentation for Enumerable#detect:

ri Enumerable#detect

That’s a lot of typing. Not good.

There’s also this problem. If I have multiple versions of a gem installed, ri can make it impossible to get documentation for a method, because it won’t be able to disambiguate the method you specify. If I type

ri ActiveRecord::Base.find

ri gives me this output:

More than one method matched your request. You can refine
your search by asking for information on one of:

     ActiveRecord::Base::find, ActiveRecord::Base::find_by_sql,
     ActiveRecord::Base::find, ActiveRecord::Base::find_by_sql,
     ActiveRecord::Base::find, ActiveRecord::Base::find_by_sql

Now you’re stuck. There’s no way forward.

To make ri better, I wrote a patch for the ri utility. It adds two simple enhancements.

Interactive Method Lookups

With the patch, ri will handle ambiguous methods more sanely. If you give ri an ambiguous method, it will go into interactive mode and let you pick from a menu of numbered alternatives. Enter a number, and it will give you the documentation you want.

So if I type

ri detect

I’ll now get

More than one method matched your request. Type the number
of the method you want, or press Return to cancel:

 1 Enumerable#detect
 2 EnumerablePass#detect
 3 Mocha::ExpectationList#detect
>> 

You can then type a number, hit return, and call up that method’s documentation.

The other problem is solved too:

$ ri ActiveRecord::Base.find
More than one method matched your request. Type the number
of the method you want, or press Return to cancel:

 1 ActiveRecord::Base::find (1.15.6)
 2 ActiveRecord::Base::find (2.0.2)
 3 ActiveRecord::Base::find (2.1.0)
>> 

And if I type in a number, I’ll actually be able to get documentation.

You might notice that my ri enhancement doesn’t return partial method name matches if it can find exact matches. That’s because allowing partial matches will often result in really huge lists. This would make using ri needlessly complicated.

But if ri can’t find an exact match, the patch lets you choose among partial matches. Example:

$ ri link_to_
There are no exact matches, but 11 partial matches:
Type the number of the method you want, or press Return to cancel:

 1 ActionView::Helpers::JavaScriptHelper#link_to_function (1.13.6)
 2 ActionView::Helpers::JavaScriptHelper#link_to_function (2.0.2)
 3 ActionView::Helpers::PrototypeHelper#link_to_remote (1.13.6)
 4 ActionView::Helpers::PrototypeHelper#link_to_remote (2.0.2)
 5 ActionView::Helpers::UrlHelper#link_to_if (1.13.6)
 6 ActionView::Helpers::UrlHelper#link_to_image
 7 ActionView::Helpers::UrlHelper#link_to_unless (1.13.6)
 8 ActionView::Helpers::UrlHelper#link_to_unless_current (1.13.6)
 9 ActionView::Helpers::UrlHelper#link_to_if (2.0.2)
10 ActionView::Helpers::UrlHelper#link_to_unless (2.0.2)
11 ActionView::Helpers::UrlHelper#link_to_unless_current (2.0.2)
>> 

Browsing a Class or Module’s Methods

The patch also makes it easier to browse the methods of a particular Ruby class or module. You can do this by adding an asterisk to the end of a class or module name1.

Example:

$ ri ActiveRecord::Base*
---------------------------------------------- Class: ActiveRecord::Base

Class methods:
--------------
     ===, abstract_class?, aggregate_mapping, all, attr_accessible,
     attr_protected, attr_readonly, base_class, benchmark,
     class_of_active_record_descendant, clear_active_connections!,
     clear_reloadable_connections!, column_names, columns, columns_hash,
     compute_type, connected?, connection, connection=, content_columns,
     count_by_sql, create, decrement_counter, delete, delete_all,
     descends_from_active_record?, destroy, destroy_all,
     establish_connection, exists?,
     expand_hash_conditions_for_aggregates, find, find_by_sql, first,
     increment_counter, inheritance_column, inspect, last, new,
     primary_key, readonly_attributes, remove_connection, require_mysql,
     reset_column_information, respond_to?, sanitize_sql,
     sanitize_sql_array, sanitize_sql_for_assignment,
     sanitize_sql_for_conditions, sanitize_sql_hash,
     sanitize_sql_hash_for_assignment, sanitize_sql_hash_for_conditions,
     serialize, serialized_attributes, set_inheritance_column,
     set_primary_key, set_sequence_name, set_table_name, silence,
     sti_name, table_exists?, table_name, update, update_all,
     update_counters, with_exclusive_scope, with_scope

Instance methods:
-----------------
     ==, [], []=, attribute_for_inspect, attribute_names,
     attribute_present?, attributes, attributes=,
     attributes_before_type_cast, becomes, cache_key, clone,
     column_for_attribute, connection, decrement, decrement!, destroy,
     eql?, freeze, frozen?, has_attribute?, hash, id, id=, increment,
     increment!, inspect, new_record?, readonly!, readonly?, reload,
     respond_to?, save, save!, to_param, toggle, toggle!,
     update_attribute, update_attributes, update_attributes!

Enter the method name you want. You can use tab to autocomplete.
>> increment[TAB KEY PRESSED TWICE]
increment          increment_counter  increment!         
>> increment!

------------------------------------------ ActiveRecord::Base#increment!
     increment!(attribute)
------------------------------------------------------------------------
     Increments the +attribute+ and saves the record.

The tab-autocomplete feature is available if Ruby’s Readline module is enabled.

You can limit the results to just instance methods like so:

$ ri String*i

Or just class methods:

$ ri ActiveRecord::Base*c

Installing and Running

To install this patch, just follow these steps.

1. If you don’t have one yet, create a /bin directory in your home directory.

2. Put this bin directory at the head of your PATH. You can do this with a line in your .bash_profile or .bashrc file like this:

export PATH="$HOME/bin:$PATH"

3. Create a file in your new /bin directory named “ri”.

4. Copy the code on this page into the file.

5. Make the file executable:

chmod u+x ri

Now you should be able to run the enhanced version of ri.

$ ri has_many
More than one method matched your request. Type the number
of the method you want, or press Return to cancel:

 1 ActiveRecord::Associations::ClassMethods#has_many (1.15.6)
 2 ActiveRecord::Associations::ClassMethods#has_many (2.0.2)
 3 ActiveRecord::Associations::ClassMethods#has_many (2.1.0)
>> 1

---------------------- ActiveRecord::Associations::ClassMethods#has_many
     has_many(association_id, options = {}, &extension)
------------------------------------------------------------------------
     Adds the following methods for retrieval and query of collections
     of associated objects. +collection+ is replaced with the symbol
     passed as the first argument, so +has_many :clients+ would add
     among others +clients.empty?+.

     *   +collection(force_reload = false)+ - returns an array of all
         the associated objects. An empty array is returned if none are
         found.

     *   +collection<<(object, ...)+ - adds one or more objects to the
         collection by setting their foreign keys to the collection's
         primary key.
...
etc.

Enjoy.

Update

I added gem version numbers in the output to help disambiguate methods. This was Leslie Viljoen’s excellent idea on the comp.lang.ruby discussion thread about this patch. Leslie also provided the some of the code for displaying the version numbers.

Ryan Davis suggested I forward this patch to the Ruby project so it can be incorporated into a future version of ri. I’ve done so.

Footnote

1 The only catch is if you issue the command in a directory where the argument, e.g. String*, can be expanded by the shell to match a file in that directory. Then the command won’t work as expected. This scenario should be rare, but if it’s ever the case, you can enclose the argument in quotes to make the command work properly. This is what you have to do with ri anyway when looking up methods containing certain punctuation characters.

back to top

a tweaked version of the micro theme by seaofclouds, and powered by YAML, Textile, and this simple, home-grown ruby build script.