All Projects → palkan → Isolator

palkan / Isolator

Licence: mit
Detect non-atomic interactions within DB transactions

Programming Languages

ruby
36898 projects - #4 most used programming language

Projects that are alternatives of or similar to Isolator

Logidze
Database changes log for Rails
Stars: ✭ 1,060 (+192.82%)
Mutual labels:  hacktoberfest, activerecord, rails
Faker
Faker is a pure Elixir library for generating fake data.
Stars: ✭ 673 (+85.91%)
Mutual labels:  hacktoberfest, developer-tools, testing-tools
Action cable client
A ruby client for interacting with Rails' ActionCable. -- Maintainers Wanted.
Stars: ✭ 245 (-32.32%)
Mutual labels:  hacktoberfest, rails
Clowne
A flexible gem for cloning models
Stars: ✭ 260 (-28.18%)
Mutual labels:  activerecord, rails
Takeoff
A rapid development environment using docker for convenience.
Stars: ✭ 271 (-25.14%)
Mutual labels:  hacktoberfest, developer-tools
Nunit Console
NUnit Console runner and test engine
Stars: ✭ 168 (-53.59%)
Mutual labels:  hacktoberfest, testing-tools
React Sight
Visualization tool for React, with support for Fiber, Router (v4), and Redux
Stars: ✭ 2,716 (+650.28%)
Mutual labels:  hacktoberfest, developer-tools
Elasticsearch Rails
Elasticsearch integrations for ActiveModel/Record and Ruby on Rails
Stars: ✭ 2,896 (+700%)
Mutual labels:  activerecord, rails
Rpush
The push notification service for Ruby.
Stars: ✭ 1,886 (+420.99%)
Mutual labels:  hacktoberfest, rails
Ar lazy preload
Lazy loading associations for the ActiveRecord models
Stars: ✭ 281 (-22.38%)
Mutual labels:  activerecord, rails
Pluck to hash
Extend ActiveRecord pluck to return array of hashes
Stars: ✭ 275 (-24.03%)
Mutual labels:  activerecord, rails
Pg party
ActiveRecord PostgreSQL Partitioning
Stars: ✭ 294 (-18.78%)
Mutual labels:  activerecord, rails
Cutcode
A browser extension that enables double click to copy code snippet from stack overflow.
Stars: ✭ 163 (-54.97%)
Mutual labels:  hacktoberfest, developer-tools
Rubanok
Parameters-based transformation DSL
Stars: ✭ 161 (-55.52%)
Mutual labels:  hacktoberfest, rails
Docker Registry Browser
🐳 Web Interface for the Docker Registry HTTP API V2 written in Ruby on Rails.
Stars: ✭ 239 (-33.98%)
Mutual labels:  hacktoberfest, rails
Yaaf
Easing the form object pattern in Rails applications
Stars: ✭ 161 (-55.52%)
Mutual labels:  hacktoberfest, rails
Public activity
Easy activity tracking for models - similar to Github's Public Activity
Stars: ✭ 2,822 (+679.56%)
Mutual labels:  activerecord, rails
Devise Security
A security extension for devise, meeting industrial standard security demands for web applications.
Stars: ✭ 302 (-16.57%)
Mutual labels:  hacktoberfest, activerecord
Anonydog
On the internet, nobody knows you're a dog
Stars: ✭ 138 (-61.88%)
Mutual labels:  hacktoberfest, developer-tools
Stryker Js
Mutation testing for JavaScript and friends
Stars: ✭ 2,043 (+464.36%)
Mutual labels:  hacktoberfest, testing-tools

Cult Of Martians Gem Version Build

Isolator

Detect non-atomic interactions within DB transactions.

Examples:

# HTTP calls within transaction
User.transaction do
  user = User.new(user_params)
  user.save!
  # HTTP API call
  PaymentsService.charge!(user)
end

#=> raises Isolator::HTTPError

# background job
User.transaction do
  user.update!(confirmed_at: Time.now)
  UserMailer.successful_confirmation(user).deliver_later
end

#=> raises Isolator::BackgroundJobError

Of course, Isolator can detect implicit transactions too. Consider this pretty common bad practice–enqueueing background job from after_create callback:

class Comment < ApplicationRecord
  # the good way is to use after_create_commit
  # (or not use callbacks at all)
  after_create :notify_author

  private

  def notify_author
    CommentMailer.comment_created(self).deliver_later
  end
end

Comment.create(text: "Mars is watching you!")
#=> raises Isolator::BackgroundJobError

Isolator is supposed to be used in tests and on staging.

Installation

Add this line to your application's Gemfile:

# We suppose that Isolator is used in development and test
# environments.
group :development, :test do
  gem "isolator"
end

# Or you can add it to Gemfile with `require: false`
# and require it manually in your code.
#
# This approach is useful when you want to use it in staging env too.
gem "isolator", require: false

Usage

Isolator is a plug-n-play tool, so, it begins to work right after required.

However, there are some potential caveats:

  1. Isolator tries to detect the environment automatically and includes only necessary adapters. Thus the order of loading gems matters: make sure that isolator is required in the end (NOTE: in Rails, all adapters loaded after application initialization).

  2. Isolator does not distinguish framework-level adapters. For example, :active_job spy doesn't take into account which AJ adapter you use; if you are using a safe one (e.g. Que) just disable the :active_job adapter to avoid false negatives (i.e. Isolator.adapters.active_job.disable!).

  3. Isolator tries to detect the test environment and slightly change its behavior: first, it respect transactional tests; secondly, error raising is turned on by default (see below).

  4. Experimental multiple databases has been added in v0.7.0. Please, let us know if you encounter any issues.

Configuration

Isolator.configure do |config|
  # Specify a custom logger to log offenses
  config.logger = nil

  # Raise exception on offense
  config.raise_exceptions = false # true in test env

  # Send notifications to uniform_notifier
  config.send_notifications = false

  # Customize backtrace filtering (provide a callable)
  # By default, just takes the top-5 lines
  config.backtrace_filter = ->(backtrace) { backtrace.take(5) }

  # Define a custom ignorer class (must implement .prepare)
  # uses a row number based list from the .isolator_todo.yml file
  config.ignorer = Isolator::Ignorer
end

Isolator relies on uniform_notifier to send custom notifications.

NOTE: uniform_notifier should be installed separately (i.e., added to Gemfile).

Transactional tests support

Supported ORMs

  • ActiveRecord >= 4.1
  • ROM::SQL (only if Active Support instrumentation extension is loaded)

Adapters

Isolator has a bunch of built-in adapters:

  • :http – built on top of Sniffer
  • :active_job
  • :sidekiq
  • :resque
  • :resque_scheduler
  • :sucker_punch
  • :mailer
  • :webmock – track mocked HTTP requests (unseen by Sniffer) in tests

You can dynamically enable/disable adapters, e.g.:

# Disable HTTP adapter == do not spy on HTTP requests
Isolator.adapters.http.disable!

# Enable back

Isolator.adapters.http.enable!

Fix Offenses

For the actions that should be executed only after successful transaction commit (which is mostly always so), you can try to use the after_commit callback from after_commit_everywhere gem (or use native AR callback in models if it's applicable).

Ignore Offenses

Since Isolator adapter is just a wrapper over original code, it may lead to false positives when there is another library patching the same behaviour. In that case you might want to ignore some offenses.

Consider an example: we use Sidekiq along with sidekiq-postpone–gem that patches Sidekiq::Client#raw_push and allows you to postpone jobs enqueueing (e.g. to enqueue everything when a transaction is commited–we don't want to raise exceptions in such situation).

To ignore offenses when sidekiq-postpone is active, you can add an ignore proc:

Isolator.adapters.sidekiq.ignore_if { Thread.current[:sidekiq_postpone] }

You can add as many ignores as you want, the offense is registered iff all of them return false.

Using with legacy Rails codebases

If you already have a huge Rails project it can be tricky to turn Isolator on because you'll immediately get a lot of failed specs. If you want to fix detected issues one by one, you can list all of them in the special file .isolator_todo.yml in the following way:

sidekiq:
  - app/models/user.rb:20
  - app/models/sales/**/*.rb

All the exceptions raised in the listed lines will be ignored.

Using with legacy Ruby codebases

If you are not using Rails, you'll have to load ignores from file manually, using Isolator::Ignorer.prepare(path:), for instance Isolator::Ignorer.prepare(path: "./config/.isolator_todo.yml")

Custom Adapters

An adapter is just a combination of a method wrapper and lifecycle hooks.

Suppose that you have a class Danger with a method #explode, which is not safe to be run within a DB transaction. Then you can isolate it (i.e., register with Isolator):

# The first argument is a unique adapter id,
# you can use it later to enable/disable the adapter
#
# The second argument is the method owner and
# the third one is a method name.
Isolator.isolate :danger, Danger, :explode, options

# NOTE: if you want to isolate a class method, use singleton_class instead
Isolator.isolate :danger, Danger.singleton_class, :explode, options

Possible options are:

  • exception_class – an exception class to raise in case of offense
  • exception_message – custom exception message (could be specified without a class)
  • details_message – a block to generate additional exception message information:
Isolator.isolate :active_job,
  target: ActiveJob::Base,
  method_name: :enqueue,
  exception_class: Isolator::BackgroundJobError,
  details_message: ->(obj, _args) {
    "#{obj.class.name}(#{obj.arguments})"
  }

You can also add some callbacks to be run before and after the transaction:

Isolator.before_isolate do
 # right after we enter the transaction
end

Isolator.after_isolate do
 # right after the transaction has been committed/rolled back
end

Troubleshooting

Verbose output

In most cases, turning on verbose output for Isolator helps to identify the issue. To do that, you can either specify ISOLATOR_DEBUG=true environment variable or set Isolator.debug_enabled manually.

Tests failing after upgrading to Rails 6.0.3 while using Combustion

The reason is that Rails started using a separate connection pool for advisory locks since 6.0.3. Since Combustion usually applies migrations for every test run, this pool becomse visible to test fixtures, which resulted in 2 transactional commits tracked by Isolator, which only expects one. That leads to false negatives.

To fix this disable migrations advisory locks by adding advisory_locks: false to your database configuration in (spec|test)/internal/config/database.yml.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/palkan/isolator.

License

The gem is available as open source under the terms of the MIT License.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].