Monday, August 29, 2011

Simple Property Model in Ruby

I've talked before about how JavaScript follows what Steve Yegge calls the Universal Design Pattern. I can see the value of associating an arbitrary number of properties with an object, and to be able to say that one object is "like" another object the way JavaScript's prototype property works. I even have ideas for using this. However, I don't do my server side programming in JavasScript. Here is my first cut at a very bare bones property model using Ruby.

Code
First I'll show you the class that stores a property. It is a just a simple key/value pair.
class Property
  attr_accessor :name, :value
  
  def initialize(name, value)
    @name = name
    @value = value
  end
end
Next, is my code for the Object class (which I call Thing):
class Thing
 
  attr_accessor :prototype, :type, :properties

  def initialize
    @properties = {}
  end
 
  def self_keys
    @properties.keys
  end
 
  def keys
    self_keys | (@prototype ? @prototype.keys : [])
  end
 
  def [](key)
    return @properties[key].value if @properties.has_key? key
    @prototype ? @prototype[key] : nil
  end
 
  def []=(key, value)
    if @properties.has_key? key
      @properties[key].value = value
    else
      @properties[key] = Property.new(key, value)
    end
  end
 
  def to_hash
    hash = @prototype ? @prototype.to_hash : {}
    @properties.each_value {|p| hash[p.name] = p.value}
    hash
  end
 
  def method_missing(method, *args)
    name = method.to_s
    if (name.end_with?('='))
      self[name[0..-2]] = args[0]
    else
      return self[name]
    end
  end
end
As you can see a "Thing" consists of a type, a prototype object, and a map of key/property pairs. Property lookups (which can be done using either array type syntax or the dot syntax of methods and fields) first look at a thing's properties, and then at its prototype's properties. All in all, it is a fairly simple pair of classes.

Thoughts
There are a couple of decisions here that seem odd, so I'll explain my thinking.

Thing
Why did I call the Object type Thing? Well there is already an Object class in Ruby and I didn't want to worry about naming collisions. Like object, "thing" is a generic enough word that if I want to create a class with that name, I should probably rethink my design. So it satisfies the qualifications of describing "something" without causing any likely naming collisions.

Property
Why do I have a Property class rather than doing the more straightforward thing of having Thing just containing a Hash of key/values? Well, this is an example of me violating the YAGNI principle. I am going to want to persist this information to a database so I am envisioning a Thing table and a Property table. My assumption is that when I do that, I'll want the key/value pairs broken out into their own Property class. So I am (perhaps foolishly) breaking that out ahead of time.

method_missing
Like JavaScript, I want to be able access properties via either array syntax (thing['property']) or method syntax (thing.property). Ruby provides the functionality to handle unknown method names, via the method_missing method. As you can see, I wrote a bare bones implementation that does no error checking. If it gets passed a method that ends with '=', it assumes that it is a property assignment, otherwise it assumes it is a property read. I suppose in the future I could/should add error checking like that there are the right number of arguments to the method. I should probably also implement respond_to so it will return true for any property that exists.

Lack of Functionality
Do these classes actually buy me anything? I'm not sure. I am sure that I am going to need a bunch of additional functionality (like database persistance) before they are really useful to me. But I have to start somewhere. So rather than blog about a fully baked solution, I've decided to show you some of the raw ingredients as I go. I am hoping that this gives me a good starting point.

No comments: