An often overlooked feature of Ruby the singleton class (aka: Eigenclass, metaclass, & virtual class). This is not be confused with the Singleton programming paradigm. The singleton class is used in Ruby to define methods on a single Object instance, rather than the normal class.
The singleton class can be accessed using Object#singleton_class and the methods using Object#singleton_methods.
There are several ways to define a singleton class method.
def my_object.my_method puts "I'm a singleton method!" end class << my_object def my_method puts "I'm a singleton method!" end end
Of course, since a Class is also an Object, it too has a singleton class.
class MyClass def my_method puts "I'm a singleton method, too!" end end MyClass.singleton_methods #=> [:my_method]
So, that's where the definitions of a class are stored, cool!
So what's the use of adding singleton methods to an instance? Well, the most common use is probably for overriding methods from the instance's class.
class MyClass def do_something puts 'okay' end end excited_object = MyClass.new def excited_object.do_something puts "Okay!" end excited_object.do_something #=> 'Okay!'
I recently found a perfect reason to use the singleton class in a Rails app. I used it to create an extension system for a model. This model needed to have a lot of different functionality, but not at all times. For example, sometimes it needs an email address, sometimes an associated user, sometimes a GPS tracker, etcetera. Having all the attributes on every instance of the model would make for slow queries and wasted storage space. Having inherited models makes for nice grouping and better querying, but sacrifices flexibility and ultimately becomes unfeasible for handling all the possible combinations (i.e. user with email, email with GPS, user with email and GPS, and so on). The singleton class is perfect as it lets me define characteristics in a manner that is very similar to the normal class approach, with things like associations and validations.
Below is a simplified example of a Resource model with a User extension. Note that I'm using Mongoid/MongoDB (ergo the use of 'field'), which is particularly well suited for a wildly changing data structure like this with no schema.
class Resource field :name, type: String field :extensions, type: Array, default: [] after_initialize :init_extensions def add_extension(extension_key) case extension_key when :user self.extend(UserMod) else raise "Unknown resource extension: #{extension_key}" end self.extensions << extension_key if self.extensions.exclude?(extension_key) self.init_class if self.respond_to?(:init_class) self.apply_defaults self end # run as after_initialize def init_extensions # Set singleton class name. Needed for associations to determine inverse relation names and such. class << self def self.name self.superclass.name end end self.extensions.each do |extension_key| add_extension(extension_key) end end module UserMod def init_class class << self field :nickname, type: String belongs_to :user validates_presence_of :user end end def name self.nickname || self[:name] || self.user.full_name end end end
I write the extensions as modules (e.g. UserMod) so that they can be used between extendable models. I store the keys for determining the extensions of the object in an array and apply them to the model on initialization. Note that when I apply the extension, I am extending the instance itself self.extend(UserMod). This way the methods of the module (e.g. #name) will be added to the Resource instance's singleton class and then calling #init_class opens up the singleton class and applies the Rails model stuff such as associations and validations.
Unfortunately, there are times when singleton classes, and inheritance for this matter, are overlooked. So watch out for implementations in libraries that make references specifically to your model instance's class. One example is in Active Support. The below method creates callback methods for an Active Record model, such as #_validate_callbacks and #_save_callback? .
def __define_runner(symbol) #:nodoc: runner_method = "_run_#{symbol}_callbacks" unless private_method_defined?(runner_method) class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{runner_method}(key = nil, &blk) self.class.__run_callback(key, :#{symbol}, self, &blk) end private :#{runner_method} RUBY_EVAL end end
As you can see, it uses self.class which will completely miss out on callbacks declared in singleton class. Below is my solution for this particular problem.
AS_CALLBACKS = [:build, :create, :destroy, :save, :touch, :update, :upsert, :validation, :validate] # :find and :initialize omitted from the list because we use that to set up the extensions themselves and wouldn't be useful in extensions anyways since they are added after creation # Active Support doesn't take into account validations on the singleton class, # so here we copy over the class validations to the singleton, and use that for validation. # *See the Active Support __define_runner method below for more info. AS_CALLBACKS.each do |callback| define_method("_run_#{callback}_callbacks") do |key = nil, &blk| singleton_callbacks = self.singleton_class.send("_#{callback}_callbacks") class_callbacks = self.class.send("_#{callback}_callbacks") # Must be careful not to cast the ActiveSupport::Callbacks::CallbackChain as an Array class_callbacks.each do |cb| singleton_callbacks << cb if singleton_callbacks.exclude?(cb) # since self.singleton_class is an object, pushing the callbacks onto the local variable updates the object itself end self.singleton_class.__run_callback(key, callback, self, &blk) end end
I copy over any callbacks that the class has which the singleton class doesn't. Then the callbacks are run according the singleton class rather than the class.
Because of their low visibility, singleton classes are not always the easiest to use. However, they are vital for achieving the flexibility of having definitions on the instance level.