diff --git a/CHANGELOG.md b/CHANGELOG.md index 4de15ee..de41774 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## 0.0.8 - WIP + +### Added + +- Protection of stubbed methods from being called within a db transaction (nepalez) + + ```yaml + --- + - class: GoogleTranslateDiff + - chain: translate + - within_transaction: false + - arguments: + - Color + - :from: en + :to: de + - actions: + - return: Farbe + ``` + + This method will raise an exception if the method is executed in a transaction: + + ```ruby + GoogleTranslateDiff.translate "Color", from: "en", to: "de" + # => "Farbe" + + # but not within a transaction + ActiveRecord::Base.transaction do + GoogleTranslateDiff.translate "Color", from: "en", to: "de" + # => raise # + end + ``` + + We use the [isolator][isolator] gem under the hood + ## [0.0.7] - [2019-07-01] ### Added diff --git a/README.md b/README.md index c84ac68..b0464c3 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,7 @@ For message chains: - `class` for stubbed class - `chain` for messages chain - `arguments` (optional) for specific arguments +- `within_transaction` (default to `true`) if the method can be called within a database transaction - `actions` for an array of actions for consecutive invocations of the chain For constants: @@ -161,6 +162,7 @@ Every action either `return` some value, or `raise` some exception - class: Notifier chain: - create + within_transaction: false arguments: - :profileDeleted - <%= profile_id %> diff --git a/fixturama.gemspec b/fixturama.gemspec index 9ae5976..3e8ca51 100644 --- a/fixturama.gemspec +++ b/fixturama.gemspec @@ -16,6 +16,7 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency "factory_bot", "~> 4.0" gem.add_runtime_dependency "rspec", "~> 3.0" gem.add_runtime_dependency "hashie", "~> 3.6" + gem.add_runtime_dependency "isolator", "~> 0.6.1" gem.add_development_dependency "rake", "~> 10" gem.add_development_dependency "rspec-its", "~> 1.2" diff --git a/lib/fixturama/stubs/chain.rb b/lib/fixturama/stubs/chain.rb index 2cbd902..9dbcaf9 100644 --- a/lib/fixturama/stubs/chain.rb +++ b/lib/fixturama/stubs/chain.rb @@ -24,11 +24,11 @@ def to_s # @option (see Fixturama::Stubs::Arguments#add_action) # @return [self] # - def update!(actions:, arguments: nil, **) + def update!(actions:, arguments: nil, within_transaction: true, **) Utils.array(arguments).tap do |args| stub = find_by(args) unless stub - stub = Stubs::Chain::Arguments.new(self, args) + stub = Stubs::Chain::Arguments.new(self, within_transaction, args) stubs << stub end stub.add!(*actions) diff --git a/lib/fixturama/stubs/chain/arguments.rb b/lib/fixturama/stubs/chain/arguments.rb index a3c0d21..0117464 100644 --- a/lib/fixturama/stubs/chain/arguments.rb +++ b/lib/fixturama/stubs/chain/arguments.rb @@ -3,7 +3,7 @@ module Fixturama # Collection of arguments for a stub with a list of actions to be called # class Stubs::Chain::Arguments - attr_reader :chain, :arguments + attr_reader :chain, :arguments, :within_transaction # # Register new action for these set of arguments @@ -54,7 +54,11 @@ def reset! # @raise [StandardError] # def call_next! - list.fetch(counter) { list.last }.call.tap { @counter += 1 } + action = list.fetch(counter) { list.last } + isolate! unless within_transaction + action.call + ensure + @counter += 1 end # @@ -70,8 +74,9 @@ def to_s # @param [Fixturama::Stubs::Chain] chain Back reference # @param [Array] list Definition of arguments - def initialize(chain, list) - @chain = chain + def initialize(chain, within_transaction, list) + @chain = chain + @within_transaction = within_transaction || (require("isolator") || false) @arguments = Utils.array(list) end @@ -82,5 +87,13 @@ def counter def list @list ||= [] end + + def isolate! + return unless ::Isolator.within_transaction? + + raise ::Isolator::UnsafeOperationError, <<~MESSAGE + You're trying to call #{self} inside db transaction + MESSAGE + end end end diff --git a/spec/fixturama/stub_fixture/_spec.rb b/spec/fixturama/stub_fixture/_spec.rb index ee917db..4cd11ae 100644 --- a/spec/fixturama/stub_fixture/_spec.rb +++ b/spec/fixturama/stub_fixture/_spec.rb @@ -34,6 +34,28 @@ def pay(_) end end + context "when called within a transaction" do + before do + allow(Isolator).to receive(:within_transaction?).and_return(true) + end + + context "without the :within_transaction option" do + let(:arguments) { [2] } + + it "raises an exception" do + expect { subject }.not_to raise_error + end + end + + context "when the option :within_transaction was set to false" do + let(:arguments) { [1] } + + it "raises an exception" do + expect { subject }.to raise_error(Isolator::UnsafeOperationError) + end + end + end + context "with several actions" do let(:arguments) { [2] * 4 } diff --git a/spec/fixturama/stub_fixture/stub.yml b/spec/fixturama/stub_fixture/stub.yml index 979dd71..295ae67 100644 --- a/spec/fixturama/stub_fixture/stub.yml +++ b/spec/fixturama/stub_fixture/stub.yml @@ -19,6 +19,7 @@ chain: - new - pay + within_transaction: false arguments: - 1 actions: