What is a Singleton Object?
The GoF identifies a singleton as a way to "ensure a class only has one instance, and provide a global point of access to it".
The implementations I provide below will be lax on both of these points.
Here is a minimal example using the GoF's implementation:
Note that Ruby also provides a singleton helper module in the stdlib. I dislike it because it's true to the GoF's implementation and singletons == meh.
Singletons == meh
It sounds like a great idea: you have one logger, and everything logs through it. But can you really say that you shouldn't be able to log somewhere else or in some other way if you wanted to? What if you later decide you want database queries to log in one place and http requests to log in another? It's easy to think you'll only ever have one of something, but I can't think of any example where this doesn't risk breaking down.
You will experience difficulty testing these because if they retain state (e.g. a logger with a file_name) then you will need to figure out how to reset that state between tests. If you wind up needing to reset them or change their state for your tests, then can you really say that you won't ever need this in your production code? Can you really say there will only ever be one Rails application, one database, one print spool, and one logger?
The second reason singletons == meh is the global point of access. That's a hard dependency. Hard dependencies are convenient when you have no infrastructure to support dependency injection, but they couple your code. This causes the code depending on the singleton to have direct access to the world it lives in, giving up its modularity and its independence from its environment. This makes it difficult to use the code in novel way. It effectively turns the singleton into a global variable.
What to do about it?
I've provided a number of implementations which show how to can get around the singleness of the object.
To get around the problems of a global point of access, opt for some form of dependency injection rather than accessing these objects directly. You can pass the singleton in when initializing, or use one of the many gems that address this problem.
Here are six approaches and example tests demonstrating their use:
1. Make the singleton an instance of some other class
This approach frees that class from having to manage the single point of access to its instances. Then you can test the other class in isolation, and don't need to test your singleton explicitly. This one is my favourite, which is why it is first.
2. Provide an instance of the class, but allow the class to be instantiated
This is in line with the way singletons are traditionally presented, except it does not prevent instantiation. Any time you want to refer to the singleton, you talk to the singleton instance, but you can test against other instances.
3. Subclass the singleton in tests
This allows you to change the state of the subclass without effecting the state of the singleton. This is useful when the object is a class, a pattern which is best to avoid, but is not uncommon.
4. Ensure that there is a way to reset the singleton.
Make sure any changes to the singlenton's state can be undone. (e.g. if you can register some object with the singleton, then you need to be able to unregister it. In Rails, for example, when you subclass
Railtie it records that in an array, but you can access the array and delete the item from it).
5. Clone the class instead of testing it directly.
This came out of a gist I made. After cloning the class, you edit the clone instead of the original class. This solution works around some of the difficulties of singletons rather than addressing their problems directly. It has caveats which could cause bugs. For example if the class has instance variables, you need to worry about whether what happens to the clone could affect variables that the cloned class points to. There is no real deep copy in Ruby.
6. Develop the behaviour in modules, then extend that onto the singleton.
You would probably need to look into the
self.extended methods if you needed to initialize some variables on the object. This creates more code and disassociates the objects from their methods which may force you to traverse several files before you find the code that defines the method you're interested in. It's also an extra stop along the call chain. This gist has a slightly more involved example.
I think my preferred solution would be the ability to instantiate singleton classes. As this doesn't exist, hopefully one of these solutions will meet your needs. I've used several of them and recommend the first solution, but there are times when the others have been more appropriate. Choose the one that works for you.