Resetting a singleton instance in Ruby - ruby

How do I reset a singleton object in Ruby? I know that one'd never want to do this in real code but what about unit tests?
Here's what I am trying to do in an RSpec test -
describe MySingleton, "#not_initialised" do
it "raises an exception" do
expect {MySingleton.get_something}.to raise_error(RuntimeError)
end
end
It fails because one of my previous tests initialises the singleton object. I have tried following Ian White's advice from this link which essentially monkey patches Singleton to provide a reset_instance method but I get an undefined method 'reset_instance' exception.
require 'singleton'
class <<Singleton
def included_with_reset(klass)
included_without_reset(klass)
class <<klass
def reset_instance
Singleton.send :__init__, self
self
end
end
end
alias_method :included_without_reset, :included
alias_method :included, :included_with_reset
end
describe MySingleton, "#not_initialised" do
it "raises an exception" do
MySingleton.reset_instance
expect {MySingleton.get_something}.to raise_error(RuntimeError)
end
end
What is the most idiomatic way to do this 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

I guess simply do this will fix your problem:
describe MySingleton, "#not_initialised" do
it "raises an exception" do
Singleton.__init__(MySingleton)
expect {MySingleton.get_something}.to raise_error(RuntimeError)
end
end
or even better add to before callback:
describe MySingleton, "#not_initialised" do
before(:each) { Singleton.__init__(MySingleton) }
end

To extract a TL;DR from the nice longer answer above, for future lazy visitors like me - I found this to be clean and easy:
If you had this before
let(:thing) { MyClass.instance }
Do this instead
let(:thing) { MyClass.clone.instance }

Related

Calling methods from a different class on method_missing

I have a class that loads a collection and loads items into a collection based on a certain criteria
require_relative 'thing'
class Someclass
def self.tings
things=load_things()
end
def self.select_things(something)
return things.select { |thing| thing.some_property == something }
end
end
I would like to use method_missing instead of the direct defintions
require_relative 'thing'
class Someclass
def self.method_missing(method, *args)
things=load_things()
if method == 'select_things'
self.send("things.select { |thing| thing.some_property == args }")
end
end
end
However, this approach doesn't work and method_missing just outputs the code string. Is there a proper way to call a code from method_missing?
Thank you very much everyone in advance
There are two issues with your method_missing implementation:
The method name is given as a symbol, not as a string:
def self.method_missing(method, *args)
if method == :select_things
# ...
end
end
You have to call super if you don't process the message yourself:
def self.method_missing(method, *args)
if method == :select_things
# ...
else
super
end
end
If you don't call super your object is going to swallow any message without ever raising a NoMethodError and you'll have a very hard time understanding why your code isn't working.
In addition, you should also implement respond_to_missing? to return true for the messages you are responding to, e.g.:
def self.respond_to_missing?(method, include_all = false)
[:select_things].include?(method) || super
end
The above gives you:
Someclass.respond_to?(:select_things) #=> true

Ruby assignment methods won't receive a block?

I am building a DSL and have this module
module EDAApiBuilder
module Client
attr_accessor :api_client, :endpoint, :url
def api_client(api_name)
#apis ||= {}
raise ArgumentError.new('API name already exists.') if #apis.has_key?(api_name)
#api_client = api_name
#apis[#api_client] = {}
yield(self) if block_given?
end
def fetch_client(api_name)
#apis[api_name]
end
def endpoint(endpoint_name)
raise ArgumentError.new("Endpoint #{endpoint_name} already exists for #{#api_client} API client.") if fetch_client(#api_client).has_key?(endpoint_name)
#endpoint = endpoint_name
#apis[#api_client][#endpoint] = {}
yield(self) if block_given?
end
def url=(endpoint_url)
fetch_client(#api_client)[#endpoint]['url'] = endpoint_url
end
end
end
so that I have tests like
context 'errors' do
it 'raises an ArgumentError when trying to create an already existent API client' do
expect {
obj = MixinTester.new
obj.api_client('google')
obj.api_client('google')
}.to raise_error(ArgumentError,'API name already exists.')
end
it 'raises an ArgumentError when trying to create a repeated endpoint for the same API client' do
expect {
obj = MixinTester.new
obj.api_client('google') do |apic|
apic.endpoint('test1')
apic.endpoint('test1')
end
}.to raise_error(ArgumentError,"Endpoint test1 already exists for google API client.")
end
end
I would rather have #api_clientwritten as an assignment block
def api_client=(api_name)
so that I could write
obj = MixinTester.new
obj.api_client = 'google' do |apic| # <=== Notice the difference here
apic.endpoint('test1')
apic.endpoint('test1')
end
because I think this notation (with assignment) is more meaningful. But then, when I run my tests this way I just get an error saying that the keyworkd_do is unexpected in this case.
It seems to me that the definition of an assignment block is syntactic sugar which won't contemplate blocks.
Is this correct? Does anyone have some information about this?
By the way: MixinTester is just a class for testing, defined in my spec/spec_helper.rb as
class MixinTester
include EDAApiBuilder::Client
end
SyntaxError
It seems to me that the definition of an assignment [method] is syntactic
sugar which won't contemplate blocks.
It seems you're right. It looks like no method with = can accept a block, even with the normal method call and no syntactic sugar :
class MixinTester
def name=(name,&block)
end
def set_name(name, &block)
end
end
obj = MixinTester.new
obj.set_name('test') do |x|
puts x
end
obj.name=('test') do |x| # <- syntax error, unexpected keyword_do, expecting end-of-input
puts x
end
Alternative
Hash parameter
An alternative could be written with a Hash :
class MixinTester
def api(params, &block)
block.call(params)
end
end
obj = MixinTester.new
obj.api client: 'google' do |apic|
puts apic
end
#=> {:client=>"google"}
You could adjust the method name and hash parameters to taste.
Parameter with block
If the block belongs to the method parameter, and not the setter method, the syntax is accepted :
def google(&block)
puts "Instantiate Google API"
block.call("custom apic object")
end
class MixinTester
attr_writer :api_client
end
obj = MixinTester.new
obj.api_client = google do |apic|
puts apic
end
# =>
# Instantiate Google API
# custom apic object
It looks weird, but it's pretty close to what you wanted to achieve.

What's best practice for mocking new method on different class

I have next scenario:
module Module
class CommandPattern
def initialize(value)
command = []
#var = value['something']
#abc = value['abc']
#command << value
end
def add(value)
#command << value
end
def get_command
#command
end
end
end
module Module
class Implementator
def initialize(value)
#value = value
end
def method_to_test(argument)
var = "command1"
cmd = CommandPattern.new(var)
var2 = "command2"
cmd.add(var2)
var3 = argument
cmd.add(var3)
commands = var + var2 + var3
commands
end
end
end
So, when I'm testing Module::B.method_I_want_to_test, what would be the best practice to mock "var = A.new(some_stuff)"? Beside refactoring and moving this line into separate method, is there some nice way to do this?
Little bit of background on this question - this style (Module::ClassA and Module::ClassB) - I'm using http://naildrivin5.com/gli/ and reason for this approach is that class A is actually implementing Command Pattern.
So issue I was apparently getting was due to wrong way of trying to write specs.
What I did before was (on the way how #spickermann advised):
RSpec.describe Module::Implementator do
describe "#method_to_test" do
let(:command_argument) { "command" }
let(:cmnd) { double(CommandPattern, :new => command_argument, :add => command_argument)}
subject(:method_to_test) do
Implementator.new("value").method_to_test("dejan")
end
before do
allow(CommandPattern).to receive(:new).with(any_args).and_return(cmnd)
allow(CommandPattern).to receive(:add).with(any_args).and_return(cmnd)
end
it 'does something' do
expect{ method_to_test }.not_to raise_error
end
it 'does something else' do
result = method_to_test
expect(result).to eq("command1command2dejan")
end
end
end
Issue was apparently in testing Module::Implementator, didn't realise I can put module around my RSpec.describe block and solve my first issue:
module Module
RSpec.describe Implementator do
describe "#method_to_test" do
let(:command_argument) { "command" }
let(:cmnd) { double(CommandPattern, :new => command_argument, :add => command_argument)}
subject(:method_to_test) do
Implementator.new("value").method_to_test("dejan")
end
before do
allow(CommandPattern).to receive(:new).with(any_args).and_return(cmnd)
allow(CommandPattern).to receive(:add).with(any_args).and_return(cmnd)
end
it 'does something' do
expect{ method_to_test }.not_to raise_error
end
it 'does something else' do
result = method_to_test
expect(result).to eq("command1command2dejan")
end
end
end
end
Another issue I had was global variable keeping YAML structure, which I missed to see and declare in spec_helper.rb
However, thank's to #spickermann's advices, issue is solved.
I would start with something like this:
describe '#method_I_want_to_test' do
let(:something) { # whatever something needs to be }
let(:a) { double(A, # methods you need from a) }
subject(:method_I_want_to_test) do
B.new(something).method_I_want_to_test
end
before do
allow(A).to receive(:new).with(something).and_return(a)
end
it 'does what I expect' do
expect(method_I_want_to_test).to eq(# what do_semething_else returns)
end
end
The interesting part is the before block that stubs the new method on A. It returns always the double defined in the let(:a) line instead of a real instance of A

Define class and instance methods at once - Ruby

I would like:
module MyLog
def log
unless #log
#log = Logger.new(log_path)
#log.formatter = proc do |severity, datetime, progname, msg|
"#{datetime} #{msg}\n"
end
end
#log
end
end
To be reused between other classes like this:
Class A
def self.log_path; 'log/a.log' end
def log_path; 'log/a.log' end
include MyLog
extend MyLog
def some_method
log.debug 'some thing'
end
def self.some_class_method
log.debug 'in a class method'
end
end
Is there a shorter way than those four lines at start of class A?
Another thing
I would like to log by batches:
def expire
expired_ids = []
failed_ids = []
all.each do |event|
if event.expire # saves record
expired_ids << event.id
else
failed_ids << event.id
end
end
log.debug "These ids were expired: #{ expired_ids }"
log.debug "These ids failed to expire: #{ failed_ids }"
end
Is there a way I can do this cleanly? Separating logging from method logic?
This is what I've been doing recentrly when faced with problems like yours:
class A
class << self
include MyLog
def log_path
'log/a.log'
end
end
delegate :log_path, :log, to: "self.class"
end
Otherwise, choosing the best programing approach depends on your situation, on how often you are going to reuse the script, how many times will you have to refactor it during its lifetime, and so on. But in any case, please, don't try to save the lines of code, try to save the trouble to yourself when you are reading the code next time after yourself.
As for your second question, the main dilemma you are facing is, whether it is worth bother for you to introduce Events class:
class Events < Array
def foobar
each_with_object [[], []] do |event, (expired_ids, failed_ids)|
( event.expire ? expired_ids : failed_ids ) << event.id
end
end
end
And then when the time comes:
def expire
expired_ids, failed_ids = Events[ all ].foobar
log.debug "These ids were expired: #{ expired_ids }"
log.debug "These ids failed to expire: #{ failed_ids }"
end
You can use the included hook to automatically define class methods and instance methods:
module MyLog
def self.included(base)
base.extend(Methods)
base.send(:include, Methods)
end
module Methods
def log_path; 'log/a.log' end
def log
unless #log
#log = Logger.new(log_path)
#log.formatter = proc do |severity, datetime, progname, msg|
"#{datetime} #{msg}\n"
end
end
#log
end
end
end
Like this, both class and instance methods will be automatically defined once the module gets included.
For your second problem, use partition and return the values, then log them:
def expire
all.partition(&:expire)
end
Then, where you call expire, you can log the return values:
def call_something
expired, failed = expire
log.debug "These ids were expired: #{expired.map(&:id)}"
log.debug "These ids failed to expire: #{failed.map(&:id)}"
end
If your log path is always supposed to be log/<lower_case_class>.log, you can implement that in your module and you should be fine. When the module's methods are executed, self will still be the object the method was called on, so you can say self.class and get A not your module name.
Check out the accepted answer on this question for how to add methods to both your class as well as your objects of that class
Why 'self' method of module cannot become a singleton method of class?
You can use Enumerable#partition to break up your "all" (you didn't actually say where that came from in your code you posted)
expired, failed = all.partition(&:expire)

RSpec lazy subject

When testing class methods, I don't need an instance to be created automatically. Is an implicit subject created automatically, or only when referenced?
describe MyClass do
it 'uses implicit subject' do
subject.my_method.should be_true
end
it 'does not create a subject' do
MyClass.works?.should be_true
# subject should not have been created
end
end
subject appears to be a method which creates the object necessary and returns it. So it would only create a subject object when called.
It's easy enough to test yourself though...
class MyClass
cattr_accessor :initialized
def initialize
MyClass.initialized = true
end
def my_method
true
end
def self.works?
true
end
end
describe MyClass do
it 'uses implicit subject' do
MyClass.initialized = false
subject.my_method.should be_true
MyClass.initialized.should == true
end
it 'does not create a subject' do
MyClass.initialized = false
MyClass.works?.should be_true
MyClass.initialized.should == false
end
end
Those specs pass, proving that it's lazy.

Resources