Skip to content

Instantly share code, notes, and snippets.

@dplummer
Created May 14, 2014 05:11
Show Gist options
  • Save dplummer/ef07153749bb8b48fb54 to your computer and use it in GitHub Desktop.
Save dplummer/ef07153749bb8b48fb54 to your computer and use it in GitHub Desktop.
Delay and prevent multiple touches
module DeferTouching
def self.defer_touching(*klasses, &block)
to_touch = Set.new
old_methods = klasses.inject({}) do |acc, klass|
acc[klass] = klass.instance_method(:touch)
acc
end
klasses.each do |klass|
klass.send(:define_method, :touch) do
to_touch << self
end
end
res = nil
begin
res = block.call
ensure
old_methods.each do |klass, meth|
klass.send(:define_method, :touch, meth)
end
end
to_touch.each do |record|
begin
record.touch
rescue ActiveRecord::StaleObjectError
record.reload
retry
end
end
res
end
end
require 'spec_helper'
describe DeferTouching do
DeferTestError = Class.new(StandardError)
class DeferMe
attr_reader :touch_count
def initialize
@touch_count = 0
end
def touch
@touch_count += 1
end
end
class DeferMeToo < DeferMe
end
class DontDeferMeBro
attr_reader :touch_count
def initialize
@touch_count = 0
end
def touch
@touch_count += 1
end
end
let(:defer_me) { DeferMe.new }
let(:defer_me_too) { DeferMeToo.new }
let(:dont_defer) { DontDeferMeBro.new }
subject { DeferTouching }
it "only defers the classes specified" do
DeferTouching.defer_touching(DeferMe, DeferMeToo) do
dont_defer.touch
dont_defer.touch
end
dont_defer.touch_count.should == 2
end
it "defers touching inside the block only" do
DeferTouching.defer_touching(DeferMe, DeferMeToo) do
defer_me.touch
defer_me_too.touch
defer_me.touch_count.should == 0
defer_me.touch_count.should == 0
end
defer_me.touch_count.should == 1
defer_me.touch_count.should == 1
end
it "turns multiple touch calls into one" do
DeferTouching.defer_touching(DeferMe, DeferMeToo) do
defer_me.touch
defer_me.touch
end
defer_me.touch_count.should == 1
end
it "stops deferring after the block"do
DeferTouching.defer_touching(DeferMe, DeferMeToo) do
defer_me.touch
end
defer_me.touch
defer_me.touch
defer_me.touch_count.should == 3
end
context "an exception is raised within the block" do
def exploding_touch
DeferTouching.defer_touching(DeferMe) do
defer_me.touch
raise DeferTestError
end
end
it "raises the error" do
expect { exploding_touch }.to raise_error(DeferTestError)
end
it "does not touch the classes" do
begin
exploding_touch
rescue DeferTestError
end
defer_me.touch_count.should == 0
end
it "redefines the correct touch method on the klass" do
begin
exploding_touch
rescue DeferTestError
end
defer_me.touch
defer_me.touch_count.should == 1
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment