After giving a bad advice on using Ruby Singleton Pattern in my previous post, I decided to remove it, and write new one. Destroy. Erase. Improve.
Singleton is perhaps the most hated of all programming patterns. You can read some of the reasons for this in Why Singletons are Evil post pointed out by Tom Ward previously. However, it has some good usages, so I will first start by describing what a singleton pattern is, some classic and alternative ways of implementing it in Ruby, and point to it’s proper usage in Rails at the end. The whole post will be kind of a short summary of Singleton Pattern from Design Patterns in Ruby book.
Singleton is a design pattern that restricts instantiation of a class to only one instance and that instance is globally available. It is useful in situations when you need that instance to be accessible in different parts of the application, usually for logging functionality, communication with external systems, database access, etc. There are few ways of implementing singleton pattern in Ruby, starting with the classic one:
- Single Instance of a class
class Logger
def initialize
@log = File.open("log.txt", "a")
end
@@instance = Logger.new
def self.instance
return @@instance
end
def log(msg)
@log.puts(msg)
end
private_class_method :new
end
Logger.instance.log('message 1')
In this code example, inside class Logger we create instance of the very same class Logger and we can access that instance with class method Logger.instance whenever we need to write something to the log file using the instance method “log”. In the initialize method we just opened a log file for appending, and at the end of Logger class, we made method “new” private so that we cannot create new instances of class Logger. And, that is Singleton Pattern: only one instance, globally available.
Ruby Standard Library has a Singleton module which implements the Singleton pattern. Previous example when using the Singleton module would translate to:
require 'singleton'
class Logger
include Singleton
def initialize
@log = File.open("log.txt", "a")
end
def log(msg)
@log.puts(msg)
end
end
Logger.instance.log('message 2')
Here we require and include Singleton module inside Logger class, define “initialize” method which opens the log file for appending and instance method “log” for writing to that log file. It’s worth pointing out that Ruby Singleton module also does lazy instantiation (creates instance from Logger class at the moment when we call Logger.instance method) and not during load time (like in the previous example). Also, Ruby Singleton module makes “new” method private, so we don’t have to call private_class_method.
Now, we can look at some of the alternative ways of implementing similar functionality to Singleton Pattern in Ruby:
Because singleton instance has global access, we can actually create a global variable $logger and use it throughout application. But, this is really bad idea, as for all global variables, the problem is that it can be very easy redefined to point to some other object without noticing that.
$logger = Logger.new
$logger.log("message 3")
Constant solves the previous problem with redefining (it cannot be redefined without getting any warning), but here the problem is that we cannot have lazy instantiation.
LOGGER = Logger.new
LOGGER.log("message 4")
We can also implement singleton functionality with class methods and class variables. This way we are sure that we cannot create another instance, but we have a problem with lazy initialization (log file will be opened at the time when this Logger class gets loaded and not at the time when we call “log” method).
class Logger
@@log = File.open("log.txt", "a")
def self.log(msg)
@@log.puts(msg)
end
end
Logger.log('message 5')
This implementation is very same with previous, with the advantage that modules can’t be instantiated. Also, lazy instantiation is still problem here.
module Logger
@@log = File.open("log.txt", "a")
def self.log(msg)
@@log.puts(msg)
end
end
Logger.log('message 6')
Update: Actually, as Tammer Saleh pointed out in the comments we can achieve lazy instantiation here with the following:
module Logger
def self.log(msg)
@@log ||= File.open("log.txt", "a")
@@log.puts(msg)
end
end
Logger.log('message 7')
Same for the previous implementation with class method.
- Unit testing Singleton problem
It’s not possible to write correct unit tests for singleton. Using the same instance in all tests, means that its state could change in one of the tests and the following would not start with a known state which is critical requirement for good unit test. So, the workaround here is to actually write tests for instance that is not a singleton (instance of Logger), and in the application use Singleton instance (instance of SingletonLogger) which inherits from Logger and includes Singleton module, so that way it becomes singleton:
require 'singleton'
class Logger
# Logger implementation
end
class SingletonLogger < Logger
include Singleton
end
- Example of Singleton Pattern in Rails
One of good usages of singleton pattern with Ruby, would be the implementation of class Inflections in Rails. It is a single instance (Inflections.instance) that gives global access to all inflection rules used in different parts of Rails. Here are blank definitions:
module ActiveSupport
module Inflector
class Inflections
def self.instance
@__instance__ ||= new
end
attr_reader :plurals, :singulars, :uncountables, :humans
def initialize
@plurals, @singulars, @uncountables, @humans = [], [], [], []
end
def plural(rule, replacement)
end
def singular(rule, replacement)
end
def irregular(singular, plural)
end
def uncountable(*words)
end
def human(rule, replacement)
end
def clear(scope = :all)
end
end
# Yields a singleton instance of Inflector::Inflections
# so you can specify additional inflector rules.
def inflections
if block_given?
yield Inflections.instance
else
Inflections.instance
end
end
def pluralize(word)
end
def singularize(word)
end
def humanize(lower_case_and_underscored_word)
end
def titleize(word)
end
def tableize(class_name)
end
def classify(table_name)
end
end
end
ActiveSupport::Inflector::Inflections class implements the singleton pattern. Singleton instance is returned or yielded by inflections method (defined in ActiveSupport::Inflector module) depending if it is called with a block or not. Methods like: pluralize, singularize, humanize, titleize, tableize and classify defined in ActiveSupport::Inflector module calls inflections method to get singleton instance and implement theirs functionality, and also inflections method is called in other parts of Rails like in config/initializers/inflections.rb with a block for adding other inflections that are not added by default:
ActiveSupport::Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
end
Anyone to point to other good usages of Singleton Pattern in Ruby libraries?