The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
Ruby Class and Rails Plugin Trees With Hirb

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Gabriel Horner

Posts: 62
Nickname: cldwaker
Registered: Feb, 2009

Gabriel Horner is an independent consultant who can't get enough of Ruby
Ruby Class and Rails Plugin Trees With Hirb Posted: Mar 19, 2009 12:55 AM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by Gabriel Horner.
Original Post: Ruby Class and Rails Plugin Trees With Hirb
Feed Title: Tagaholic
Feed URL: http://feeds2.feedburner.com/tagaholic
Feed Description: My ruby/rails/knowledge management thoughts
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by Gabriel Horner
Latest Posts From Tagaholic

Advertisement

In my last post, I demonstrated how Hirb can render puurty tables with automated views. In this post, I’ll show Hirb’s new tree view using its console methods and these three examples- class inheritance trees, nested class trees and Rails’ ActiveRecord trees.

First, let’s view a basic tree with Hirb.

  bash> irb -rubygems -rhirb
  
  # Import a view() method we'll use.
  irb>> extend Hirb::Console
  => main
  
  # Create a tree structure Hirb understands.
  irb>> tree = [[0,0], [1,1], [2,2], [2,3], [1,4]]
  => [[0,0], [1,1], [2,2], [2,3], [1,4]]
  
  # Render a basic tree.
  irb>> view tree, :tree
  0
      1
          2
          3
      4
  => true
  
  # Render a directory tree.
  irb>> view tree, :tree, :type=>:directory
  0
  |-- 1
  |   |-- 2
  |   `-- 3
  `-- 4
  => true
  
  # Render a tree with each level numbered.
  irb>> view tree, :tree, :type=>:number
  0. 0
      1. 1
          1. 2
          2. 3
      1. 4

A Hirb tree consists of an array of nodes. Each node is an array consisting of it’s depth/level in the tree and its value.

Class Inheritance Trees

Although that basic tree was easy to build, more complex trees require more setup. Thankfully Hirb’s ParentChildTree class makes tree building easy. It’s based on one simple assumption: each node has a method to retrieve its immediate children. Given a node, ParentChildTree uses that method to build out all the children under it. To put this class into exercise let’s build a class inheritance tree for any Ruby class. Does a Ruby class have a method to retrieve it’s immediate subclasses? No, but no worries. Ruby’s got your back:

  module InheritanceTree
    # Retrieves objects of the current class.
    def objects
      class_objects = []
      ObjectSpace.each_object(self) {|e| class_objects << e }
      class_objects
    end
    
    # Retrives immediate subclasses of the current class.
    def class_children
      (@class_objects ||= Class.objects).select {|e| e.superclass == self }
    end
  end
  
  Module.send :include, InheritanceTree
  # Note that in modifying a class as ubiquitous as Module that I extend its behavior through 
  # a traceable module. I think it's good manners to let others know you're modifying core
  # classes: http://tagaholic.me/2009/01/31/share-extensions-without-monkey-patching.html

That wasn’t too hard. class_children() is the method we seek. It iterates over all existing classes and only picks those that are subclasses of the current class. Having extended Module, let’s see some inheritance trees:

  # test drive :class_children
  irb>> Numeric.class_children
  => [Float, Integer, Date::Infinity, Rational]
  
  # Must explicitly pass :class_children since ParentChildTree assumes a :children method by default.
  irb>> view Numeric, :parent_child_tree, :children_method=>:class_children, :type=>:directory
  Numeric
  |-- Float
  |-- Integer
  |   |-- Bignum
  |   `-- Fixnum
  |-- Date::Infinity
  `-- Rational
  => true
  
  irb>> view StandardError, :parent_child_tree, :children_method=>:class_children, :type=>:directory
  # Just kidding. Don't want to double the length of this post.
  
  # We can however do a subset of that tree:
  irb>> view RuntimeError, :parent_child_tree, :children_method=>:class_children, :type=>:directory
  RuntimeError
  `-- Gem::Exception
      |-- Gem::VerificationError
      |-- Gem::RemoteSourceException
      |-- Gem::RemoteInstallationSkipped
      |-- Gem::RemoteInstallationCancelled
      |-- Gem::RemoteError
      |-- Gem::OperationNotSupportedError
      |-- Gem::InvalidSpecificationException
      |-- Gem::InstallError
      |-- Gem::GemNotFoundException
      |-- Gem::FormatException
      |-- Gem::FilePermissionError
      |-- Gem::EndOfYAMLException
      |-- Gem::DocumentError
      |-- Gem::GemNotInHomeException
      |-- Gem::DependencyRemovalException
      |-- Gem::DependencyError
      `-- Gem::CommandLineError
  => true


Nested Class Trees

Seeing how easily Hirb creates trees for us, let’s build another one. When I start using a new gem, it helps to get an overview of the classes and modules it defines. All we need is a method that can retrieve a class’ immediate nested class (ie Gem::Error is a nested class of Gem):

  module NestedTree
    # Since nested classes are just constants:
    def nested_children
      constants.map {|e| const_get(e) }.select {|e| e.is_a?(Module) }
    end
    
    def nested_name
      self.to_s.split(":")[-1]
    end
  end
  
  Module.send :include, NestedTree

With nested_children(), we’re ready to rock. Let’s view nested classes under Hirb:

  # test drive nested_children
  irb>> Hirb.nested_children
  => [Hirb::Util, Hirb::View, Hirb::Console, Hirb::Helpers, Hirb::Views, Hirb::HashStruct]

  irb>> view Hirb, :parent_child_tree, :children_method=>:nested_children, :type=>:directory
  Hirb
  |-- Hirb::Util
  |-- Hirb::View
  |-- Hirb::Console
  |-- Hirb::Helpers
  |   |-- Hirb::Helpers::ObjectTable
  |   |   `-- Hirb::Helpers::Table::TooManyFieldsForWidthError
  |   |-- Hirb::Helpers::AutoTable
  |   |-- Hirb::Helpers::ParentChildTree
  |   |   |-- Hirb::Helpers::Tree::ParentlessNodeError
  |   |   `-- Hirb::Helpers::Tree::Node
  |   |       |-- Hirb::Helpers::Tree::Node::MissingValueError
  |   |       `-- Hirb::Helpers::Tree::Node::MissingLevelError
  |   |-- Hirb::Helpers::Table
  |   |   `-- Hirb::Helpers::Table::TooManyFieldsForWidthError
  |   |-- Hirb::Helpers::ActiveRecordTable
  |   |   `-- Hirb::Helpers::Table::TooManyFieldsForWidthError
  |   `-- Hirb::Helpers::Tree
  |       |-- Hirb::Helpers::Tree::ParentlessNodeError
  |       `-- Hirb::Helpers::Tree::Node
  |           |-- Hirb::Helpers::Tree::Node::MissingValueError
  |           `-- Hirb::Helpers::Tree::Node::MissingLevelError
  |-- Hirb::Views
  |   `-- Hirb::Views::ActiveRecord_Base
  `-- Hirb::HashStruct
  => true

Sweet! A nice visualization of Hirb’s classes. But wouldn’t it be nice if a nested class only showed its nested name? That’s what we defined nested_name() for:

>> view Hirb, :parent_child_tree, :children_method=>:nested_children, :type=>:directory,
  :value_method=>:nested_name
Hirb
|-- Util
|-- View
|-- Console
|-- Helpers
|   |-- ObjectTable
|   |   `-- TooManyFieldsForWidthError
|   |-- AutoTable
|   |-- ParentChildTree
|   |   |-- ParentlessNodeError
|   |   `-- Node
|   |       |-- MissingValueError
|   |       `-- MissingLevelError
|   |-- Table
|   |   `-- TooManyFieldsForWidthError
|   |-- ActiveRecordTable
|   |   `-- TooManyFieldsForWidthError
|   `-- Tree
|       |-- ParentlessNodeError
|       `-- Node
|           |-- MissingValueError
|           `-- MissingLevelError
|-- Views
|   `-- ActiveRecord_Base
`-- HashStruct
=> true

Better! Note that we passed a :value_method option to tell ParentChildTree what method to call to represent a node. We didn’t have to specify this in the inheritance trees because it defaults to name() which Ruby classes have defined.

ActiveRecord Trees

Having had to build our children methods for our last two tree type examples, this example should be easy. Let’s setup a tree-enabled Rails app to use acts_as_tree :

  # generate rails app
  bash> rails tree
  bash> cd tree

  # generate model
  bash> script/generate model category name:string parent_id:integer
  bash> rake db:migrate

  # install plugin
  bash> script/plugin install git://github.com/rails/acts_as_tree.git

  # modify app/models/category.rb to look like:
  # class Category < ActiveRecord::Base
  #   acts_as_tree
  # end

With this app setup, let’s fire up script/console to create some records:

  bash> script/console
  Loading development environment (Rails 2.2.2)

  ## Create some categories
  irb>> conf.echo = false
  irb>> Category.create(:name=>'root')
  irb>> _.children.create(:name=>'gems')
  irb>> _.children.create(:name=>'hirb')
  irb>> _.parent.children.create(:name=>'utility_belt')
  irb>> Category.root.children.create(:name=>'plugins')
  irb>> _.children.create(:name=>'acts_as_tree')
  irb>> conf.echo = true

  # Enable Hirb so we get table views for ActiveRecord objects.
  irb>> require 'hirb'; Hirb::View.enable
  => nil

  # Let's see what we've created.
  irb>> Category.all
  +----+-------------------------+--------------+-----------+-------------------------+
  | id | created_at              | name         | parent_id | updated_at              |
  +----+-------------------------+--------------+-----------+-------------------------+
  | 5  | 2009-03-18 20:41:43 UTC | root         |           | 2009-03-18 20:46:52 UTC |
  | 6  | 2009-03-18 20:46:27 UTC | gems         | 5         | 2009-03-18 20:46:27 UTC |
  | 7  | 2009-03-18 20:48:06 UTC | hirb         | 6         | 2009-03-18 20:48:06 UTC |
  | 8  | 2009-03-18 20:48:28 UTC | utility_belt | 6         | 2009-03-18 20:48:28 UTC |
  | 9  | 2009-03-18 20:54:24 UTC | plugins      | 5         | 2009-03-18 20:54:24 UTC |
  | 10 | 2009-03-18 20:54:38 UTC | acts_as_tree | 9         | 2009-03-18 20:54:38 UTC |
  +----+-------------------------+--------------+-----------+-------------------------+
  => true

With our app and records setup, it’s time to see the trees:

  irb>> extend Hirb::Console
  => main

  # View tree from root node.
  irb>> view Category.root, :parent_child_tree, :type=>:directory
  root
  |-- gems
  |   |-- hirb
  |   `-- utility_belt
  `-- plugins
      `-- acts_as_tree
  => true

  # We can view a tree from any node:
  irb>> view Category.find_by_name('gems'), :parent_child_tree, :type=>:directory
  gems
  |-- hirb
  `-- utility_belt
  => true

Sweet! Hirb’s trees should work fine with other tree plugins that have a children method ie rails’ nested set plugin and the awesome nested_set plugin. Don’t forget to pass a :children_method option as we did in previous examples when nodes don’t retrieve their children with children().

Conclusion

In this post, we focused on using Hirb with its console methods. If you didn’t read the previous post, know that any of these views can be automatically echoed based on the output’s class. For the last example, we could trigger an automated directory tree for any Category nodes with:

  irb>> Hirb::View.enable {|c| c.output = {"Category"=>{:class=>"Hirb::Helpers::ParentChildTree", :options=>{:type=>:directory}}} }

As always, I encourage you to fork and share your custom views for irb. Some food for thought for more tree visualizations. If you haven’t already:


gem install cldwalker-hirb —source http://gems.github.com

Read: Ruby Class and Rails Plugin Trees With Hirb

Topic: Retrospective Trust Level Previous Topic   Next Topic Topic: You Are Invited

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use