Genetically Modified Origin

George's Blog - Digital Adventures in The Big Apple

Exploring Ruby's Core (Methods, Modules, Classes, and More, Oh My!) Through Metaprogramming & Monkeypatching

| Comments

A general disclaimer: I just learned Ruby ~3 months ago, so please let me know if any of this can be improved upon

As I’ve been learning and becoming familiar with Ruby and its various libraries / gems, one thing I’ve found myself doing often is opening up irb or pry and playing around all different kinds of methods, classes, and modules. However, I’ve found that it’s hard to explore this consistently without having to constantly refer back to the documentation for what methods exist for each class / module or what the structure of the classes or modules are. I just want to play around and avoid constantly going back online or to the source code! The solution: I ended up modifying the core ~/.irbrc and ~/.pryrc files to monkeypatch Ruby’s core classes, so that every time I start up irb, pry, or rails console I get access to my custom methods. Here’s a link to what my .pryrc file looks like:

https://gist.github.com/gglin/5930277

If you want to see an example of all this in action, go to the end of this post

Now, I can much more easily explore methods, classes, modules and other Ruby constructs:

Exploring Methods

  • any method which ends with the word "methods", i.e. returns an array of the defined methods for an object, whether it is all methods, instance methods, private methods, or whatever else, can now be prefixed with "local_" to return a much shorter array of just methods which aren’t already defined for all Ruby Objects (though you can modify this default to return the difference with any class).
    • Ruby does have a built-in way to retrieve only methods defined in the immediate scope, not any mixed-in modules or superclasses, by setting the argument in xxx_methods(arg) so that arg = false. However, having a local_xxx_methods is more customizable, and may be more telling in many cases, such as where you are totally unfamiliar with a class or module and want to see not just how it is different from its immediate ancestors, but overall, i.e. to see the methods it inherited from everything but the Object class.

See code here:

.irbrc or .pryrc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Object

  # all methods named 'local_(*)_methods' 
  # should return '(*)_methods' minus those that exist in Object
  def method_missing(method_name, klass = Object, *args, &block)
    if method_name.to_s[0..5] == "local_" && method_name.to_s[-7..-1] == "methods" && !method_name.to_s[6..-1].include?("local")
      subname = method_name.to_s[6..-1]
      self.send(subname, *args, &block) - klass.send(subname, *args, &block)
    else
      super(method_name, *args, &block)
    end
  end

  # core 2.0 documentation says "DO NOT USE THIS DIRECTLY." - why?
  def respond_to_missing?(method_name, include_private = false)
    subname = method_name.to_s[6..-1]
    method_name.to_s[0..5] == "local_" && method_name.to_s[-7..-1] == "methods" && !method_name.to_s[6..-1].include?("local") && respond_to?(subname) or super
  end

end

Exploring Namespaces

  • When I call ancestors on a module, the list of results returned is a mix of the modules mixed in and the superclasses. Breaking these into two methods, module_ancestors and class_ancestors, helps me to better understand the inheritance chain of a given module or class. For example, it’s much clearer now that the class inheritance chain for Fixnum is [Fixnum, Integer, Numeric, Object, BasicObject], while the modules [Comparable, Kernel] are only mixed in.

  • For a given namespace, such as ActiveRecord::Base, I was curious to see what the various modules and classes that exist under that namespace are, as there is (1) no easy way to see all of these without consulting the source code, which is massive for something like ActiveRecord, and not trivial to find and explore, and (2) no easy way to tell the difference between modules, classes, and other constants under that namespace.

    • I initially consulted a StackOverflow post that explains how to do this, and wrote up the methods subclasses and submodules. Unfortunately, these only look for subclasses within subclasses and submodules within submodules…
    • There are many cases where in the nested namespaces, you could have a class in a module in a class in a module, or anything else. Although more complicated, I eventually came up with a way to do this using a “subthing” helper method, which is a ~20 line monstrosity I’m not happy with. Regardless, the methods subconstruct_classes and subconstruct_methods will look into all nested namespaces, no matter how deep, and retrieve all classes or modules.

  • Modifying the above code slightly allows for retrieval of non-module, non-class constants for a given module – for example, finding that the only constants that exist for the Math module are :PI & :E, and that their values are 3.14159… & 2.71828… using subconstants and subconstant_names

See code here:

.irbrc or .pryrc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class Module

  def class_methods(*args, &block)
    self.methods(*args, &block) - self.instance_methods(*args, &block)
  end

  def class_ancestors
    self.ancestors.select{|a| a.class == Class}
  end

  def module_ancestors
    self.ancestors.select{|a| a.class == Module}
  end

  def subthings(recursive = false, *args, &block)
    result = self.constants.collect {|const_name| const_get(const_name)}.select {|const| yield(const) }
    return [] if result.include?(self)
    if recursive == false
      result.uniq
    else
      looper = result.clone
      looper.each do |thing|

        thing.subthings(false, *args, &block).each do |subthing|
          subthing.subthings(false, *args, &block).each do |subsubthing|
            recursive = false if result.include?(subsubthing)
          end
          result << thing.subthings(recursive, *args, &block)
        end
      end
      result.flatten.uniq
    end
  end

  # http://www.natontesting.com/2010/06/30/how-to-get-the-submodules-of-a-ruby-module/
  # return only immediate nested namespace modules
  def submodules(recursive = false)
    subthings(recursive) {|const| const.class == Module}
  end

  # return only immediate nested namespace classes
  def subclasses(recursive = false)
    subthings(recursive) {|const| const.class == Class}
  end

  def subconstructs(recursive = false)
    subthings(recursive) {|const| const.class == Class || const.class == Module}
  end

  # returns all nested namespace modules
  def subconstruct_modules
    subconstructs(true).select {|const| const.class == Module}
  end

  # returns all nested namespace classes
  def subconstruct_classes
    subconstructs(true).select {|const| const.class == Class}
  end

  # retrieves non-class, non-module constant names
  # e.g. Math.subconstants => [3.14159, 2.71828]
  def subconstants(recursive = false)
    subthings(recursive) {|const| const.class != Class && const.class != Module}
  end

  # retrieves non-class, non-module constant names
  # e.g. Math.subconstant_names => [:PI, :E]
  def subconstant_names
    constants.select {|const_name| const_get(const_name).class != Class && const_get(const_name).class != Module}
  end

end

Exploring Class Inheritance

  • Finally, for classes, there is an easy built-in way to look up the inheritance chain and find the parent of a class using the superclass method. Unfortunately the opposite isn’t true – there’s no built-in way to find all children of a parent class. Fortunately Ruby has module called ObjectSpace which allows for traversal of all living objects in memory, allowing for a simple child_classes (& alias children) method to be defined. Note that this is NOT the same thing as the subclasses method I mentioned earlier – the latter only cares about namespaces, i.e. BaseModule::AnotherModule::SubClass, while child_classes is concerned with class inheritance, i.e. ChildClass < ParentClass.

See code here:

.irbrc or .pryrc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Class

  def child_classes
    ObjectSpace.each_object(Class).select { |klass| klass < self }
  end

  def children
    child_classes
  end

  def siblings
    self.superclass.child_classes - [self]
  end

end

Not finding giants to stand on the shoulders of, aka It’s been done before

Of course, it was only after I had written all of these that I found there’s much more experienced coders who have done a lot of this already! Here are some (probably better) ways to accomplish what I monkeypatched:

  • There is an entire gem which consists of libraries which extend the core capabilities of Ruby’s built-in constructs: Facets. A lot of the methods from Facets do the above, probably in a better way. Updates to Facets have been infrequent in the last year, but nevertheless it seems to have tons of potentially useful additions to core Ruby.

  • There are in gems which make it much easier to print readable, colorful Ruby – for example, the “y” method in YAML, “pp” for prettyprint, “pretty_generate” in JSON. Among these is a great little gem called Awesome Print, which not only formats and colorizes the output, but also adds additional helpful info, such as the superclass of the printed object, the index of each element in an array, and vertically aligning the hash rocket in a hash so that the keys and values are easier to read. To always include awesome print, I added this to my .pryrc as per instructions:

.irbrc or .pryrc
1
2
require "awesome_print"
AwesomePrint.pry!

Now, this solves the problem of being able to tell the difference between class and module ancestors. For example, with ap enabled, CodeRay::WordList::CaseIgnoring.ancestors returns the following:

This is clearly much easier to read and tells you not only which ancestors are classes, but what each ancestor’s superclass is.

Regardless, by doing these monkeypatches, I’ve found it much easier to navigate around all kinds of objects and methods in Ruby to learn more about it, without necessarily having to go back to the source code – for example, find a specific class within ActiveRecord::Base and then find all the local_methods for that class. Even if there is still a lot of code I could change / clean up, this was a great learning experience to understand Ruby more deeply. As they say, it’s about the journey, not just the destination.

Example of Usage

As an example of how I can use the code I wrote, I started pry and typed in:

pry
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[1] pry(main)> CodeRay.subconstruct_modules
[
    [0] CodeRay::FileType,
    [1] CodeRay::Styles,
    [2] CodeRay::Encoders,
    [3] CodeRay::PluginHost,
    [4] CodeRay::Plugin,
    [5] CodeRay::Scanners,
    [6] CodeRay::GZip,
    [7] CodeRay::Tokens::Undumping
]
[2] pry(main)> CodeRay.subconstruct_classes
[
    [ 0] CodeRay::TokensProxy < Object,
    [ 1] CodeRay::Tokens < Array,
    [ 2] CodeRay::WordList < Hash,
    [ 3] CodeRay::Duo < Object,
    [ 4] CodeRay::Encoders::Encoder < Object,
    [ 5] CodeRay::Encoders::Terminal < CodeRay::Encoders::Encoder,
    [ 6] CodeRay::PluginHost::PluginNotFound < LoadError,
    [ 7] CodeRay::PluginHost::HostNotFound < LoadError,
    [ 8] CodeRay::Scanners::Scanner < StringScanner,
    [ 9] CodeRay::Scanners::Ruby < CodeRay::Scanners::Scanner,
    [10] CodeRay::WordList::CaseIgnoring < CodeRay::WordList,
    [11] CodeRay::FileType::UnknownFileType < Exception,
    [12] CodeRay::Styles::Style < Object
]

Now if I want to know the local class methods available for CodeRay::WordList and local instance methods available for CodeRay::TokensProxy, I can type:

pry
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[3] pry(main)> CodeRay::Styles::Style.local_methods
[
    [0]      aliases()       Class (CodeRay::Plugin)
    [1]  plugin_host(*host)  Class (CodeRay::Plugin)
    [2]    plugin_id()       Class (CodeRay::Plugin)
    [3] register_for(id)     Class (CodeRay::Plugin)
    [4]        title(*title) Class (CodeRay::Plugin)
]

[4] pry(main)> CodeRay::TokensProxy.local_instance_methods
[
    [ 0]    block()                  CodeRay::TokensProxy (unbound)
    [ 1]   block=(arg1)              CodeRay::TokensProxy (unbound)
    [ 2]     each(*args, &blk)       CodeRay::TokensProxy (unbound)
    [ 3]   encode(encoder, *options) CodeRay::TokensProxy (unbound)
    [ 4]    input()                  CodeRay::TokensProxy (unbound)
    [ 5]   input=(arg1)              CodeRay::TokensProxy (unbound)
    [ 6]     lang()                  CodeRay::TokensProxy (unbound)
    [ 7]    lang=(arg1)              CodeRay::TokensProxy (unbound)
    [ 8]  options()                  CodeRay::TokensProxy (unbound)
    [ 9] options=(arg1)              CodeRay::TokensProxy (unbound)
    [10]  scanner()                  CodeRay::TokensProxy (unbound)
    [11]   tokens()                  CodeRay::TokensProxy (unbound)
]

Some other fun things to try out:

pry
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
[5] pry(main)> Fixnum.class_ancestors
[
    [0] Fixnum < Integer,
    [1] Integer < Numeric,
    [2] Numeric < Object,
    [3] Object < BasicObject,
    [4] BasicObject
]
[6] pry(main)> Fixnum.siblings
[
    [0] Bignum < Integer
]
[7] pry(main)> require 'active_record'
true
[8] pry(main)> ActiveRecord::Base.subconstant_names
[
    [0] :ACTIONS,
    [1] :UNASSIGNABLE_KEYS,
    [2] :CALLBACKS,
    [3] :ATTRIBUTE_TYPES_CACHED_BY_DEFAULT,
    [4] :NAME_COMPILABLE_REGEXP,
    [5] :CALL_COMPILABLE_REGEXP,
    [6] :CALLBACK_FILTER_TYPES
]
[9] pry(main)> ActiveRecord::Base.subconstants
[
    [0] [
        [0] :create,
        [1] :destroy,
        [2] :update
    ],
    [1] [
        [0] "id",
        [1] "_destroy"
    ],
    [2] [
        [ 0] :after_initialize,
        [ 1] :after_find,
        [ 2] :after_touch,
        [ 3] :before_validation,
        [ 4] :after_validation,
        [ 5] :before_save,
        [ 6] :around_save,
        [ 7] :after_save,
        [ 8] :before_create,
        [ 9] :around_create,
        [10] :after_create,
        [11] :before_update,
        [12] :around_update,
        [13] :after_update,
        [14] :before_destroy,
        [15] :around_destroy,
        [16] :after_destroy,
        [17] :after_commit,
        [18] :after_rollback
    ],
    [3] [
        [0] :datetime,
        [1] :timestamp,
        [2] :time,
        [3] :date
    ],
    [4] /\A[a-zA-Z_]\w*[!?=]?\z/,
    [5] /\A[a-zA-Z_]\w*[!?]?\z/,
    [6] [
        [0] :before,
        [1] :after,
        [2] :around
    ]
]

Thanks to the following posts on StackOverflow for guidance:

  1. http://www.natontesting.com/2010/06/30/how-to-get-the-submodules-of-a-ruby-module/
  2. http://stackoverflow.com/questions/2393697/look-up-all-descendants-of-a-class-in-ruby

Comments