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
)
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/
. 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!
