!Electronic || !Bionic || !Ultrasonic

Alright, the journey begins. I received the first specification! If you don’t know what this is about perhaps you missed out the story. The first part of the story nor the sotry itslef is relevant for understanding the code that follows. Just a way to keep it interesting and mysterious, until something working comes out of box. Now let me continue the story:

First the creator explained me that after years of debate he settled down on ruby as language for Inox. He then asked me to implement a Point class. At first I objected as why such a class would be of any use and why not represent a point as an array [x, y].

Spec

He told to observe how beautiful ruby’s OO model is. And how pragmatic it is to perform computation and handle numbers like object. For cross-platform, cross-language, cross-OS, and dirty debugging, it would be preferable to have a clean object to represent a point with the following specification:

describe Point do
  before :each do
    @p = Point.new
  end
 
  it "should have a property x" do
    @p.should respond_to(:x)
  end
 
  it "should have a property y" do
    @p.should respond_to(:y)
  end
 
  it "should have default x value of 0" do
    @p.x.should == 0
  end
 
  it "should have default y value of 0" do
    @p.y.should == 0
  end
 
  it "should have a setter for x" do
    @p.x = 1313
    @p.x.should == 1313
  end
 
  it "should have setter for y" do
    @p.y = 1313
    @p.y.should == 1313
  end
 
  it "should only accept numeric values for initialization" do
    lambda { Point.new('1', '2') }.should raise_error
  end
 
  it "should only accept numeric values for x" do
    lambda { @p.x = "String" }.should raise_error
  end
 
  it "should only accept numeric values for y" do
    lambda { @p.y = "String" }.should raise_error
  end
 
  it "should being able to compare ==" do
    p2 = Point.new
    @p.should == p2
    p2.x = 1313
    @p.should_not == p2
    @p.should_not == "Test"
  end
 
  it "should be castable to an array" do
    @p.should respond_to(:to_a)
    @p.to_a.should == [0,0]
  end
 
  it "should be castable to an hash" do
    @p.should respond_to(:to_hash)
    @p.to_hash.should == {:x => 0, :y => 0}
  end
 
  it "should be castable to Point" do
    @p.should respond_to(:to_point)
    @p.to_point.should == @p
  end
 
  it "an array of two elements should be castable to Point" do
    p = [0,0].to_point
    p.should == @p
  end
 
  it "an array contain more than 2 elements raise error" do
    lambda { p = [1,2,3].to_point }.should raise_error
  end
 
  it "a hash containing ( :x => value, :y =>value ) should be castable to Point" do
    p = {:x => 0, :y => 0}.to_point
    p.should == @p
  end
 
  it "a hash to_point should raise if the keys x and y are not found" do
    lambda { p = {}.to_point }.should raise_error
  end
end

The Source

At first sight it seams like it is an easy task. Well it is, but the first attempt spawned more than 200 lines of code, for solving this simple matter. I couldn’t let it be that way and after a few rethink, I settled down for the following implementation:

module Inox
 
  # Representation of a Point, defined as Object for the ability to 
  # handle it in a good OO manner and furthur extention and
  # modification.
  # 
  # Struct is the base class of Point and as such Point inherits 
  # the already powerful methods of Struct. 
  #
  # The assignment checks for Numeric kind of values 
  # to prevent errors. 
  class Point <  Struct.new(:x, :y)
 
    # added type check to enforce Numeric values
    def initialize(*args)
      super(*(args.empty? ? [0,0] : args))
      unless self.x.kind_of? Numeric and self.y.kind_of? Numeric
        self.x = 0
        self.y = 0
        raise "#{args} not a two Numeric values"
      end
    end
 
    # added type check to enforce Numeric values    
    def x=(value)
      raise "#{num} not a Numeric value" unless value.kind_of? Numeric
      super(value)
    end
 
    # added type check to enforce Numeric values
    def y=(value)
      raise "#{num} not a Numeric value" unless value.kind_of? Numeric
      super(value)
    end
 
    def to_hash
      {:x => self.x, :y => self.y}
    end
 
    def to_point
      self
    end
  end
end
 
# Extending the ability of Array to handle
# extra Inox types
class Array
 
  # Cast an Array holding two Numerics to an instance of the class Point
  #
  # [1,3].to_point  ==  Point.new(1,3)
  def to_point
    raise "Cannot convert #{self} to Point" unless length == 2
    Inox::Point.new(self[0], self[1])
  end
end
 
# Extending the ability of the Hash class to handle
# extra Inox types
class Hash
 
  # Cast a Hash containing two keys [:x, :y] with Numeric values
  # to an instance of the class Point
  #
  # {:x => 1, y => 3}.to_point  == Point.new(1,3)
  #
  # Extra keys in the hash doesn't interfer
  def to_point
    raise "Cannot find keys 'x' and 'y'" unless key?(:x) and key?(:y) 
    Inox::Point.new(self[:x], self[:y])
  end
end

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 3: Actions Here is the pursuit of a series of related articles...
  2. Ruby Inox Part 4: Property Last time, we took a look at Actions. A standard...
  3. Ruby Inox Part 3.1: Actions Here is a short update for the Ruby Inox Part3:...
  4. Brain damaged assertions chain After a while, when I am coding and have already...
  5. Ruby Inox Part 4.1: Property In Ruby Inox Part 4: Property I described I would...
  6. Ruby Inox Part 6: Widget We are steering toward a graphical user interface. In this...
Email Icon Facebook Icon Twitter Icon Share this IconShareThis Follow Ironicwolf on Twitter