Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add protection of stubbed methods from being called within a db transaction #9

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 #<Isolator::UnsafeOperationError ...>
end
```

We use the [isolator][isolator] gem under the hood

## [0.0.7] - [2019-07-01]

### Added
Expand Down Expand Up @@ -166,7 +200,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [0.0.1] - [2018-03-01]

This is a first public release with features extracted from production app.
This is the first public release with features extracted from production app.

[0.0.1]: https://github.com/nepalez/fixturama/releases/tag/v0.0.1
[0.0.2]: https://github.com/nepalez/fixturama/compare/v0.0.1...v0.0.2
Expand All @@ -175,3 +209,5 @@ This is a first public release with features extracted from production app.
[0.0.5]: https://github.com/nepalez/fixturama/compare/v0.0.4...v0.0.5
[0.0.6]: https://github.com/nepalez/fixturama/compare/v0.0.5...v0.0.6
[0.0.7]: https://github.com/nepalez/fixturama/compare/v0.0.6...v0.0.7

[isolator]: https://github.com/palkan/isolator
1 change: 0 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
source "https://rubygems.org"

# Specify your gem's dependencies in sms_aero.gemspec
gemspec

group :development, :test do
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 %>
Expand Down
1 change: 1 addition & 0 deletions fixturama.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions lib/fixturama/stubs/chain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
21 changes: 17 additions & 4 deletions lib/fixturama/stubs/chain/arguments.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

#
Expand All @@ -70,8 +74,9 @@ def to_s

# @param [Fixturama::Stubs::Chain] chain Back reference
# @param [Array<Object>] 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

Expand All @@ -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
22 changes: 22 additions & 0 deletions spec/fixturama/stub_fixture/_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
1 change: 1 addition & 0 deletions spec/fixturama/stub_fixture/stub.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
chain:
- new
- pay
within_transaction: false
arguments:
- 1
actions:
Expand Down