Ruby Inox Part 5: Component

2009
05.22

Something promised is something due. Only two days have passed since my last post, Ruby Inox Part4: Property. Two days human time span is like a decade of internet time span. Some bits stayed the same and some changed. Notable changes were made to the module Actions and the module Properties, stepping closer to the final goal.

In this article its all about Components!

Motivation

We are slowly, but steady advancing toward a rich OO toolkit written in ruby. The most important building block for such a toolkit is the father object for the rest of the hierarchy which I will call Component. It should possess the following criteria:

  • should have a parent
  • should be able to have children
  • should have default actions
  • should be persistent

1) Parent

All the user created components have a parent instance. If no parent is given than the very first existing object named Inox is the parent of the component. This is to ensure that every Component is tracked at any time.

The actual hierarchy of f.ex a button component should be as follow

  1. Inox
  2. Hardware
  3. OS
  4. Ruby
  5. Application
  6. Window
  7. Button

Note that for practical reasons at the time of this writing the following objects, and only existing objects, are singleton instances: Application
The Hardware object should contain all the useful methods for querying or using hardware. The OS part should contain the methods for communicating with the OS. Ruby represents the interpreter and so on. As Singleton Components their instance can be accessed at anytime.

At any time a component has only one parent. If that parent is disposed all of its children gets disposed to. So if a user created Application is closed, its children are also closed. But if a component is attached to Ruby and used in Application. And Application get closed, the Component is still available.

At the ruby level we could start one Application as background worker and a second Application as GUI.

Application is used later to describe a general Application class which is a subclass of Component; and doesn’t need to be tight to a GUI.

Up until now we have a parent attribute and an action: parent_changed, which get called with the old parent as argument when a new parent is set.

2) Children

A component should also be able to contain children, but it’s not requirement, it’s more a possible feature of a Component. So the specific interface for handling children is defined as includable module.

The module Container defines the methods add_child, remove_child, has an attribute children and includes the ruby’s Enumerable module. It also defines two actions: child_added and child_removed

Object is extended with container? method so that every object can be queried.

3) Default Actions

So far the default actions are self-explanatory: create, dispose, parent_changed. Every Component can be created. Conforms to actions; and it can be assigned a block of code to execute with the on_create method, it can be assigned a Proc/Method with simple assignment create=, it can be executed by calling create and at last there is a ‘create!’ (create bang) method which is called implicitly by create and is meant for subclasses to override.

More on actions: Ruby Part 3.1: Actions

4) Persistency

Marshall comes and he rescues us with the following small limitation: it cannot serialize the actions. No Proc, no Method and no block.

I can imagine a solution for serializing the action/Method between two components that could be sufficient to get started with:

	# adding dumping and loading of method
	class Method
		attr_accessor :target, :method_sym

		def _dump(_)
		 raise "expect method from obj.methdo(sym)" if @target = nil
		 r = Marshal.dump(@target)
		 r << ':' << @method_sym.to_s
		 r
		end

		def self._load(s)
			obj = Marshal.load(s)
			sym = s.split(':').last.to_sym
			obj.method(sym)
		end
	end

	# overriding Object.method
	class Object
		def method(sym)
			m = super(sym)
			m.target = self
			m.name = sym
			m
		end
	end

	# Test
	class Foo
		attr_accessor :test
		def initialize(t)
			@test = t
		end
	end

	class B
		def test_method
			puts "Hello World"
		end
	end

	b = B.new
	foo = Foo.new( b.method(:test_method) ) 

	c = Marshal.dump(foo)
	d = Marshal.load(c)

	d.test.call  => "Hello World"

There are still some complication at sight, but we are way to early to address this issues here. Just remember that a Component should be able to get stored and loaded, no matter what this feature costs!

Source Code: Component

class ::Object
    def self.container?; false; end
    def container?; self.class.container?; end
  end

  class Component
    include Actions
    include Properties

    attr_accessor :parent
    actions :create, :dispose, :parent_changed

    def initialize(the_parent = nil, *args, &block)
      self.parent = the_parent
      create(*args, &block)
    end

    def parent=(obj)
      assert2(obj).nil?.or.kind_of?(Component).end
      old_parent = @parent
      @parent = obj
      parent_changed(old_parent)
    end

    def dispose!
      children.each { |c| c.dispose } if container?
      parent.remove_child(self) unless parent.nil? or not parent.container?
    end
  end

  module Container
    def self.included(base)
      with base do
        include Enumerable           

        actions :child_added, :child_removed

        def self.container?; true; end

        include InstanceMethods
      end
    end

    module InstanceMethods
      def children; @children ||= []; end

      def add_child(obj)
        assert2(obj).nil?.or.kind_of?(Component).end
        children << obj
        obj.parent = self
        child_added(obj)
      end
      alias << add_child

      def remove_child(obj)
        assert2(obj).not.nil?.and.kind_of?(Component).end
        res = children.delete(obj)
        unless res.nil?
          res.parent = nil
          child_removed(res)
        end
      end
    end
  end

To be continued

This show will go on! The next article will get to the really hot part. The first true visual component class in ruby.

Ruby Inox on GitHub

This file is part of the Ruby Inox project. Inox is a work in progress and this file might not be up to date. The status and latest files are available on GitHub

Related posts:
  1. Ruby Inox Part 6: Widget We are steering toward a graphical user interface. In this...
  2. Ruby Inox Part 7: Native Originally this article formed one article with Ruby Inox Part...
  3. Ruby Inox Part 3.1: Actions Here is a short update for the Ruby Inox Part3:...
  4. Ruby Inox Part 3: Actions Here is the pursuit of a series of related articles...
  5. Ruby Inox Part 4.1: Property In Ruby Inox Part 4: Property I described I would...

Tags: , , , , , , ,

Comments are closed.


Ironic Wolf is Digg proof thanks to caching by WP Super Cache