Recentemente, encontrei um bom artigo sobre como resolver problemas de simultaneidade , usando a gem fork_break . Exatamente o que eu precisava, mas ainda não era simples o suficiente para reutilizar para todos os possíveis problemas de simultaneidade no projeto. Isso deve tornar mais fácil:
em rspec / support / concurrency.rb
def make_concurrent_calls(object, method, options={})
options.reverse_merge!(:count => 2)
processes = options[:count].times.map do |i|
ForkBreak::Process.new do |breakpoints|
# Add a breakpoint after invoking the method
original_method = object.method(method)
object.stub(method) do |*args|
value = original_method.call(*args)
breakpoints << method
value
end
object.send(method)
end
end
processes.each{ |process| process.run_until(method).wait }
processes.each{ |process| process.finish.wait }
end
Um teste modelo rspec seria então:
context "unfrobbed thing" do
let(:thing) { thing.make! }
context "on concurrent calls to #frob" do
before { make_concurrent_calls(thing, :frob) }
it "should only create one frob" do
# testing that the side effect is only executed once:
expect(Frob.where(:thing_id => thing.id).count).to eq(1)
end
end
end
Alguns problemas podem exigir uma solução no nível do controlador, deve ser fácil adaptar o teste para esses casos.
Muitos dos problemas de simultaneidade aconteceram em métodos que eram realmente state_machine
transições. Aqui está uma solução reutilizável para esses casos:
em config / initializers / lock_transition.rb
module LockTransition
extend ActiveSupport::Concern
def lock_transition(*args, &block)
before_transition(*args) do |resource,transition|
resource.lock!
if block_given?
yield
else
transition.from == resource.state
end
end
end
end
class StateMachine::Machine
include LockTransition
end
Então, no modelo:
class Thing < ActiveRecord::Base
state_machine do
# (states and events)
lock_transition :from => :initial, :to => :frobbed
after_transition :from => :initial, :to => :frobbed, :do => :create_frob
end
# (methods)
end
Apenas tenha o cuidado de declarar o lock_transition
antes de qualquer before_transition
declaração contendo efeitos colaterais que devem ser protegidos.