2.1 Inspector Gadget

Induction

We've seen how we can create methods dynamically and call them on the fly. That's a really cool way of automating related structures and generating code. But it doesn't stop there. Method provides us with an objectification of methods that is helpful in the analysis of these methods before they are called or used. We're constantly playing with our own code, generating, toying with their intermediate object states.
Example Code:

Output Window

method() returns the Method object. We call this on self because we defined monk in main. Because of this objectification, our code knows more about this method than it would otherwise, as a regular definition. We can store this object and modify it through our code itself, query it and retrieve useful metadata from it.
Example Code:

Output Window

This looks a lot like the Proc object that we used in the Blocks chapter of "Ruby Primer: Ascent". Method objects are, in fact, mostly like Proc. Proc objects however, have a binding method which returns a Binding object representing the context in which the Proc was created. This is because procs have access to the local variables outside their scope. This is untrue of Method objects because they have a self binding. This is effectively the same as saying methods can only access variables and objects inside their own scope.

The arity method in the above example returns a Fixnum representing the number of arguments that the method can accept. It returns -2 because arity returns -(n + 1), for n number of required arguments. That is, -(1 + 1) = -2 as *args2 is optional. It adds a + 1 because -1 is a reserved number for functions defined using Ruby's C API. If there are no optional arguments then it just returns the correct positive value.

The parameter method returns all the parameters that the method is defined with. It returns an array of key-value pairs. The key is a symbol representing the type of the parameter -- :req for a mandatory parameter, :rest, for the variable arguments, :opt for the default optional parameters and :block for the block parameters. The value is the symbolized form of the parameter itself.

Example Code:

Output Window

In this example, receiver is the object on which the method is bound and owner is the class that object belongs to.

Analysis

The methods method on Object allows us to tap into to the list of public methods available on an Object and its ancestors. This is equivalent to using public_methods. They return all the instance methods and the class methods belonging to that object and the ones accessible on that object's ancestors. If you want to ignore the ancestors and restrict the listing to just the receiver, you can pass in false to public_methods(false).

Other similar method listing methods allow you to get all the instance methods, public, private or protected instance methods, singleton methods etc. Refer to the Object and Module API documentation for more information.

Example Code:

Output Window

Imagine that we have an Editor class which is responsible for handling all the functionalities of a text editor that we have built. This class can be initialized as Editor.new(template) and accepts one String parameter which is a code template that the person using the editor has typed out.

This class once initialized, has a hook to a Cursor object, which can be accessed through editor.cursor. This Cursor object defines a method called read which accepts a block to deal with what the cursor is currently handling on the editor template.

Now, this block gives you an argument that holds the name of the current word that the cursor is handling. I'd like you to write an auto-complete engine which suggests two things to the user, 1) suggest appropriate methods when a class name is typed and 2) suggest appropriate parameters when a method name is typed. We've already provided a build_suggestion method that accepts an Array of suggestions and displays them to the user. Use this when you detect one of the two cases above.

  1. It should gracefully handle newlines. That is, it should throw :eol if it encounters a newline. The whole exercise will get timed-out otherwise, so make sure you do this.
  2. If the current word is a class name defined by the user in the editor, then it should build a list of suggestions of all the methods that are available on that class. To tap into the code written by the user in the editor, you can use the Template object like editor.template. editor.template.user_classes will give you all the classes defined in the editor and eval(word.to_s) to get a hold of a class name constant.
  3. If the current word is a method defined normally on the top-level of the program, then it should build a list of suggestions of the parameters that the method is defined with. To get the context of where the code is defined, call the context method on the Template object. Now, this TemplateContext object is similar to a Binding object in Ruby. You can further use this to get a method object as editor.template.context.method(name) and editor.template.context.methods to get all the methods defined on the template's top-level.
Here's a quick review of the API that you can use.

Editor
user_classes() ? Array
cursor() ? Cursor
template() ? Template

Cursor
read() { |word| block } ? nil

Template
context() ? TemplateContext

TemplateContext
binding() ? Binding
methods() ? Array
method(name) ? Method

Finish the method called auto_complete that accepts an Editor object and use the build_suggestion method that accepts an Array from 1) Object#methods 2) Method#parameters and incorporate all of the filters explained above, making the relevant specs pass.

We've completed the first filter for you as an example.

Output Window

Congratulations, guest!


% of the book completed

or

This lesson is Copyright © 2011-2024 by Sidu Ponnappa and Srushti