Strange Symphonies Don’t worry, be happy

2Jun/070

The Potential For Abuse With Open Classes

It is believed that open classes provide an invitation for for potential abuse. My argument is that anything provides an invitation for potential abuse, hell I can be sniffing glue in my room for all you know. (No I don't).

It is commonly understood that PHP is repeatedly abused to produce spaghetti code of PHP, SQL, HTML, JavaScript, and CSS. Yummy! This is not a fault of PHP, but a fault of the developers of the spaghetti code. There are alot of new frameworks out there that are based on a Model View Controller design pattern to separate login from presentation. symfony and CodeIgniter come to mind.

Anyone can write spaghetti code in any language. It is the job of the developer to organize code.

Much in the same way in that anything can be done via open classes. It is up to the developer to write good code.

For example Rails comes with a reasonable set of core extensions which don't abuse the use of open classes.

1Jun/070

Ruby and Open Classes

When I introduce people to Ruby (at least to programmers) I tend to highlight the differences between Ruby and established programming languages, such as Java, PHP, and C/C++. I opt highlight the differences because at least all (if not those that based around C) generally have the same structure, with regards to conditions, loops, etc.

Note: The intention is not to create a flame war, or to do any bashing

Disclaimer: Out of the listed programming languages, my strength is primarily in PHP, so if I am wrong about something, do bring it up! It will make for a good discussion, and I'll learn something new.

Note: If you are unfamiliar with Ruby, the last statement in Ruby is always returned. You can opt to use a return statement, but why? Especially when you expect the method to return something.

Classes are Open

Ruby is unique in that classes are open. Generally once you close off those brackets in that class your writing, you can't continue adding methods to it. In Ruby you can. You can extend (not in the same way as inheritance) a class once it's been "closed".

You can also extend Ruby's core classes, such as String, Hash, Numeric, Fixnum, etc.

Example: Sanitizing a String

Say for instance you want to sanitize a string of all non alphanumeric characters. Let's say the definition of sanitize, is to replace all non alphanumeric characters with an underscore.

In PHP, you'd probably have to do this:

function sanitize_string($string) {
  return preg_replace('/[^[:alnum:]]/+', '_', $string)
}

echo sanitize_string("Let's sanitize this string baby")

In Ruby, you can do this:

class String
  def sanitize
    self.gsub(/[^[:alnum:]]+/, '_')
  end
end

puts "Let's sanitize this string baby".sanitize

Now which one seems more intuitive?

In PHP we are calling a separate function entirely, that may operate on any variable type. This can be improved in several ways: Adding error checking code. Following PHP naming conventions for String functions, and call it strsanitize.

In Ruby we are basically redeclaring a new method for the String class, which operates on itself.

Example: Getting a Random Element from an Array

PHP already comes with a function to do precisely this: array_rand.

Sadly Ruby doesn't, but hey. Let's plug it in!

class Array
  def rand
    self[Kernel::rand(self.size)]
   end
end

Example: Easier Time Calculations

class Numeric
  def seconds
    self
  end
  alias :second :seconds

  def minutes
    self * 60
  end
  alias :minute :minutes

  def hours
    self * 60.minutes
  end
  alias :hour :hours

  def days
    self * 24.hours
  end
  alias :day :days

  def weeks
    self * 7.days
  end
  alias :week :weeks
end

Note: the alias statement allows us to easily duplicate a method, so I'm just duplicating them here to make singular representations

1.second # 1
1.minute # 60
7.5.minutes # 450.0
3.hours # 10800
10.75.hours # 38700
24.hours # 86400
31.days # 2678400
101.days # 8726400
51.weeks # 30844800
365.days # 31536000
15.minutes + 3.days + 4.hours # 274500

Cool eh?

Well lets extend it once again to give us the time in relation to now.

class Numeric
  def ago(time = Time.now)
    time - self
  end
  alias :until :ago

  def since(time = Time.now)
    time + self
  end
  alias :from_now
end

So now we'll be using the core class Time, which when used with the addition and subtraction methods removes seconds from the time.

Time.now # Thu May 31 23:46:44 0800 2007
5.minutes.ago # Thu May 31 23:41:44 0800 2007
20.minutes.from_now # Thu June 1 00:06:44 0800 2007
2.hours.since(4.hours.ago) # Thu May 31 17:21:09 0800 2007

Note: Ruby on Rails actually uses this to extend much of the functionality not found by default in the Ruby core classes. They are packaged inside ActiveSupport, and are called Core Extensions. These blocks of code is the same as found in ActiveSupport. (activesupport/lib/active_support/core_ext/numeric/time.rb File)

The Rails Way

To achieve the simplicity found in Rails, Rails extends alot of Ruby's core classes as seen above. Have a look into activesupport/lib/active_support/core_ext/ File. As of ActiveSupport v1.4.2 there are 63 files, providing a plethora of methods that make life easier.

Advance Example: Extending ActiveRecord

Lets pull out the big guns now and see how to extend ActiveRecord.

The convention in Rails is to put things in name space. In this I'll use an Aizatto namespace inside ActiveRecord (ActiveRecord::Aizatto). Similarly they tend to separate the class methods and instance methods into two different modules. Giving them a unique namespace each.

So here is a rough skeleton for you to work with and see how things go:

module ActiveRecord
  module Aizatto
    def self.included(base)
      base.extend ClassMethods
      base.class_eval do
        include InstanceMethods
      end
    end

    module ClassMethods
      def acts_as_aizatto
        logger.warn "Why would you want to do that?"
      end
    end

    module InstanceMethods
    end
  end
end

ActiveRecord::Base.send :include, ActiveRecord::Aizatto

Conclusion

Well thats the basics of Open Classes in Ruby. As can be seen open classes makes it easier to introduce new methods to existing Classes (even core classes), to add any additional methods. Sadly there isn't really much else for me to discuss here, so enjoy!