I\'ve seen how to define a class as being a singleton (how to create a singleton in ruby):
require \'singleton\'
class Example
include Singleton
end
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.
Here is an approach I used to solve a similar problem, which I wanted to share in case you or other people find it suitable:
require 'singleton'
class Logger
attr_reader :file_name
def initialize file_name
@file_name = file_name
end
end
class MyLogger < Logger
include Singleton
def self.new
super "path/to/file.log"
end
# You want to make {.new} private to maintain the {Singleton} approach;
# otherwise other instances of {MyLogger} can be easily constructed.
private_class_method :new
end
p MyLogger.instance.file_name
# => "path/to/file.log"
MyLogger.new "some/other/path"
# => ...private method `new' called for MyLogger:Class (NoMethodError)
I've tested the code in 2.3
, 2.4
and 2.5
; earlier versions may of course exhibit divergent behavior.
This allows you to have a general parametrized Logger
class, which can be used to create additional instances for testing or future alternative configurations, while defining MyLogger
as a single instance of it following to Ruby's standardized Singleton
pattern. You can split instance methods across them as you find appropriate.
Ruby's Singleton
constructs the instance automatically when first needed, so the Logger#initialize
parameters must be available on-demand in MyLogger.new
, but you can of course pull the values from the environment or set them up as MyLogger
class instance variables during configuration before the singleton instance is ever used, which is consistent with the singleton instance being effectively global.
This was too long to put into a comment (e.g. stackoverflow said it was too long)
Ok so here's what I came up with:
class MyLogger
@@singleton__instance__ = nil
@@singleton__mutex__ = Mutex.new
def self.config_instance file_name
return @@singleton__instance__ if @@singleton__instance__
@@singleton__mutex__.synchronize {
return @@singleton__instance__ if @@singleton__instance__
@@singleton__instance__ = new(file_name)
def self.instance
@@singleton__instance__
end
private_class_method :new
}
@@singleton__instance__
end
def self.instance
raise "must call MyLogger.config_instance at least once"
end
private
def initialize file_name
@file_name = file_name
end
end
This uses 'config_instance' to create and configure the singleton instance. It redefines the self.instance method once an instance is ready.
It also makes the 'new' class method private after creating the first instance.
Simple singleton that doesn't depend on Singleton
module
class MyLogger
def self.instance(filepath = File.join('some', 'default', 'path'))
@@instance ||= new(filepath).send(:configure)
end
def initialize(filepath)
@filepath = filepath
end
private_class_method :new
def info(msg)
puts msg
end
private
def configure
# do stuff
self
end
end
Example usage
logger_a = MyLogger.instance
# => #<MyLogger:0x007f8ec4833060 @filepath="some/default/path">
logger_b = MyLogger.instance
# => #<MyLogger:0x007f8ec4833060 @filepath="some/default/path">
logger_a.info logger_a.object_id
# 70125579507760
# => nil
logger_b.info logger_b.object_id
# 70125579507760
# => nil
logger_c = MyLogger.new('file/path')
# NoMethodError: private method `new' called for MyLogger:Class
Here's another way to do it -- put the log file name in a class variable:
require 'singleton'
class MyLogger
include Singleton
@@file_name = ""
def self.file_name= fn
@@file_name = fn
end
def initialize
@file_name = @@file_name
end
end
Now you can use it this way:
MyLogger.file_name = "path/to/log/file"
log = MyLogger.instance # => #<MyLogger:0x000.... @file_name="path/to/log/file">
Subsequent calls to instance
will return the same object with the path name unchanged, even if you later change the value of the class variable. A nice further touch would be to use another class variable to keep track of whether an instance has already been created, and have the file_name=
method raise an exception in that case. You could also have initialize
raise an exception if @@file_name
has not yet been set.