Resetting a Singleton Instance in Ruby

Resetting a singleton instance in Ruby

Tough question, singletons are rough. In part for the reason that you're showing (how to reset it), and in part because they make assumptions that have a tendency to bite you later (e.g. most of Rails).

There are a couple of things you can do, they're all "okay" at best. The best solution is to find a way to get rid of singletons. This is hand-wavy, I know, because there isn't a formula or algorithm you can apply, and it removes a lot of convenience, but if you can do it, it's often worthwhile.

If you can't do it, at least try to inject the singleton rather than accessing it directly. Testing might be hard right now, but imagine having to deal with issues like this at runtime. For that, you'd need infrastructure built in to handle it.

Here are six approaches I have thought of.


Provide an instance of the class, but allow the class to be instantiated. This is the most in line with the way singletons are traditionally presented. Basically any time you want to refer to the singleton, you talk to the singleton instance, but you can test against other instances. There's a module in the stdlib to help with this, but it makes .new private, so if you want to use it you'd have to use something like let(:config) { Configuration.send :new } to test it.

class Configuration
def self.instance
@instance ||= new
end

attr_writer :credentials_file

def credentials_file
@credentials_file || raise("credentials file not set")
end
end

describe Config do
let(:config) { Configuration.new }

specify '.instance always refers to the same instance' do
Configuration.instance.should be_a_kind_of Configuration
Configuration.instance.should equal Configuration.instance
end

describe 'credentials_file' do
specify 'it can be set/reset' do
config.credentials_file = 'abc'
config.credentials_file.should == 'abc'
config.credentials_file = 'def'
config.credentials_file.should == 'def'
end

specify 'raises an error if accessed before being initialized' do
expect { config.credentials_file }.to raise_error 'credentials file not set'
end
end
end

Then anywhere you want to access it, use Configuration.instance


Making the singleton an instance of some other class. Then you can test the other class in isolation, and don't need to test your singleton explicitly.

class Counter
attr_accessor :count

def initialize
@count = 0
end

def count!
@count += 1
end
end

describe Counter do
let(:counter) { Counter.new }
it 'starts at zero' do
counter.count.should be_zero
end

it 'increments when counted' do
counter.count!
counter.count.should == 1
end
end

Then in your app somewhere:

MyCounter = Counter.new

You can make sure to never edit the main class, then just subclass it for your tests:

class Configuration
class << self
attr_writer :credentials_file
end

def self.credentials_file
@credentials_file || raise("credentials file not set")
end
end

describe Config do
let(:config) { Class.new Configuration }
describe 'credentials_file' do
specify 'it can be set/reset' do
config.credentials_file = 'abc'
config.credentials_file.should == 'abc'
config.credentials_file = 'def'
config.credentials_file.should == 'def'
end

specify 'raises an error if accessed before being initialized' do
expect { config.credentials_file }.to raise_error 'credentials file not set'
end
end
end

Then in your app somewhere:

MyConfig = Class.new Configuration

Ensure that there is a way to reset the singleton. Or more generally, undo anything you do. (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).

class Configuration
def self.reset
@credentials_file = nil
end

class << self
attr_writer :credentials_file
end

def self.credentials_file
@credentials_file || raise("credentials file not set")
end
end

RSpec.configure do |config|
config.before { Configuration.reset }
end

describe Config do
describe 'credentials_file' do
specify 'it can be set/reset' do
Configuration.credentials_file = 'abc'
Configuration.credentials_file.should == 'abc'
Configuration.credentials_file = 'def'
Configuration.credentials_file.should == 'def'
end

specify 'raises an error if accessed before being initialized' do
expect { Configuration.credentials_file }.to raise_error 'credentials file not set'
end
end
end

Clone the class instead of testing it directly. This came out of a gist I made, basically you edit the clone instead of the real class.

class Configuration  
class << self
attr_writer :credentials_file
end

def self.credentials_file
@credentials_file || raise("credentials file not set")
end
end

describe Config do
let(:configuration) { Configuration.clone }

describe 'credentials_file' do
specify 'it can be set/reset' do
configuration.credentials_file = 'abc'
configuration.credentials_file.should == 'abc'
configuration.credentials_file = 'def'
configuration.credentials_file.should == 'def'
end

specify 'raises an error if accessed before being initialized' do
expect { configuration.credentials_file }.to raise_error 'credentials file not set'
end
end
end

Develop the behaviour in modules, then extend that onto singleton. Here is a slightly more involved example. Probably you'd have to look into the self.included and self.extended methods if you needed to initialize some variables on the object.

module ConfigurationBehaviour
attr_writer :credentials_file
def credentials_file
@credentials_file || raise("credentials file not set")
end
end

describe Config do
let(:configuration) { Class.new { extend ConfigurationBehaviour } }

describe 'credentials_file' do
specify 'it can be set/reset' do
configuration.credentials_file = 'abc'
configuration.credentials_file.should == 'abc'
configuration.credentials_file = 'def'
configuration.credentials_file.should == 'def'
end

specify 'raises an error if accessed before being initialized' do
expect { configuration.credentials_file }.to raise_error 'credentials file not set'
end
end
end

Then in your app somewhere:

class Configuration  
extend ConfigurationBehaviour
end

How to programmatically remove singleton information on an instance to make it marshal?

You can define custom marshal_dump and marshal_load methods:

class X
def break_marshalling!
meta_class = class << self
self
end
meta_class.send(:define_method, :method_y) do
return
end
end

# This should return an array of instance variables
# needed to correctly restore any X instance. Assuming none is needed
def marshal_dump
[]
end

# This should define instance variables
# needed to correctly restore any X instance. Assuming none is needed
def marshal_load(_)
[]
end
end

# Works fine
restored_instance_of_x =
Marshal.load Marshal.dump(X.new.tap { |x| x.break_marshalling! })

# Does not work
restored_instance_of_x.method_y

If you want you can manage dynamic methods definitions via method_missing:

class X
def method_missing(name, *args)
if name == :method_y
break_marshalling!
public_send name, *args
else
super
end
end
end

# Works fine
Marshal.load(Marshal.dump(X.new)).method_y

How to retrieve correct Rails singleton instance from Resque workers

As Stefan commented, it is not possible to access a ruby object from another process, and using a singleton to hold assets was a bad idea anyway. I learnt that you should always avoid singletons when you can. I finally cached the assets in Redis and retrieve them when needed.

Ruby Singletion Class

The problem is that singleton is about Ruby object, not about record in the database. And because of multi-threading the processes in Rails are isolated

So if you need to keep just one record don't use require 'singleton'

Define method

def self.get
first || create # more Rails way - first_or_create
end

or

def self.get
first || new # more Rails way - first_or_initialize
end

And in your code call HomePage.get

How can I mix in Singleton to create a class that accepts initialization parameters?

Singleton does not provide this functionality, but instead of using singleton you could write it by yourself

class MyLogger
@@singleton__instance__ = nil
@@singleton__mutex__ = Mutex.new

def self.instance(file_name)
return @@singleton__instance__ if @@singleton__instance__

@@singleton__mutex__.synchronize do
return @@singleton__instance__ if @@singleton__instance__

@@singleton__instance__ = new(file_name)
end
@@singleton__instance__
end

private

def initialize(file_name)
@file_name = file_name
end
private_class_method :new
end

It should work, but I did not tested the code.

This code forces you to use MyLogger.instance <file_name> or at least at the first call if you know it will be first time calling.

How to reset singleton instance?

I create all my Singletons with an optional Singleton instance.
However I also make this private and use a function to fetch it.
If the Singleton is nil it creates a new instance.

This is actually the only good way to set up a Singleton. If you have a regular object that you can't deinitialize it's a memory problem. Singletons are no different, except that you have to write a function to do it.

Singletons have to be completely self managed. This means from init to deinit.

I have a couple of templates on github for Singeltons, one of them with a fully implemented read/write lock.

class Singleton {

private static var privateShared : Singleton?

class func shared() -> Singleton { // change class to final to prevent override
guard let uwShared = privateShared else {
privateShared = Singleton()
return privateShared!
}
return uwShared
}

class func destroy() {
privateShared = nil
}

private init() {
print("init singleton")
}

deinit {
print("deinit singleton")
}
}


Related Topics



Leave a reply



Submit