Skip to content

Commit 364d42f

Browse files
authored
Add MysqlAdapter transactions support (#58)
1 parent f68a534 commit 364d42f

File tree

20 files changed

+159
-33
lines changed

20 files changed

+159
-33
lines changed

.circleci/config.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ jobs:
4141
POSTGRES_USER: "circleci"
4242
POSTGRES_DB: "safer_rails_console_test"
4343
POSTGRES_HOST_AUTH_METHOD: "trust"
44+
- image: cimg/mysql:8.0
45+
environment:
46+
MYSQL_DATABASE: "safer_rails_console_test"
47+
MYSQL_ROOT_HOST: "%"
48+
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
4449
working_directory: ~/safer_rails_console
4550
steps:
4651
- checkout
@@ -60,6 +65,9 @@ jobs:
6065
paths:
6166
- "vendor/bundle"
6267
- "gemfiles/vendor/bundle"
68+
- run:
69+
name: Wait for Mysql
70+
command: dockerize -wait tcp://localhost:3306 -timeout 1m
6371
- run:
6472
name: Run Tests
6573
command: |

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@
1515
/gemfiles/*.gemfile.lock
1616
out
1717
*.sqlite3
18+
19+
.idea

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## v0.9.0
4+
- Add MySql support
5+
36
## v0.8.0
47
- Drop support for Ruby 2.7.
58
- Drop support for Rails 6.0.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![Build Status](https://circleci.com/gh/salsify/safer_rails_console.svg?style=svg)](https://circleci.com/gh/salsify/safer_rails_console)
44
[![Gem Version](https://badge.fury.io/rb/safer_rails_console.svg)](https://badge.fury.io/rb/safer_rails_console)
55

6-
This gem makes Rails console sessions less dangerous in specified environments by warning, color-coding, and auto-sandboxing PostgreSQL connections. In the future we'd like to extend this to make other external connections read-only too (e.g. disable job queueing, non-GET HTTP requests, etc.)
6+
This gem makes Rails console sessions less dangerous in specified environments by warning, color-coding, and auto-sandboxing PostgreSQL and MySQL connections. In the future we'd like to extend this to make other external connections read-only too (e.g. disable job queueing, non-GET HTTP requests, etc.)
77

88
## Installation
99

lib/safer_rails_console/patches/sandbox/auto_rollback.rb

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ def self.rollback_and_begin_new_transaction
1111
connection.begin_db_transaction
1212
end
1313

14-
def self.handle_and_reraise_exception(error)
15-
if error.message.include?('PG::ReadOnlySqlTransaction')
14+
def self.handle_and_reraise_exception(error, message = 'PG::ReadOnlySqlTransaction')
15+
if error.message.include?(message)
1616
puts SaferRailsConsole::Colors.color_text( # rubocop:disable Rails/Output
1717
'An operation could not be completed due to read-only mode.',
1818
SaferRailsConsole::Colors::RED
@@ -28,13 +28,27 @@ module PostgreSQLAdapterPatch
2828
def execute_and_clear(...)
2929
super
3030
rescue StandardError => e
31-
SaferRailsConsole::Patches::Sandbox::AutoRollback.handle_and_reraise_exception(e)
31+
# rubocop:disable Layout/LineLength
32+
SaferRailsConsole::Patches::Sandbox::AutoRollback.handle_and_reraise_exception(e, 'PG::ReadOnlySqlTransaction')
33+
# rubocop:enable Layout/LineLength
3234
end
3335
end
3436

3537
if defined?(::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
3638
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(PostgreSQLAdapterPatch)
3739
end
40+
41+
module MySQLPatch
42+
def execute_and_free(...)
43+
super
44+
rescue StandardError => e
45+
SaferRailsConsole::Patches::Sandbox::AutoRollback.handle_and_reraise_exception(e, 'READ ONLY transaction')
46+
end
47+
end
48+
49+
if defined?(::ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter)
50+
::ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.prepend(MySQLPatch)
51+
end
3852
end
3953
end
4054
end

lib/safer_rails_console/patches/sandbox/transaction_read_only.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,27 @@ def begin_db_transaction
1111
end
1212
end
1313

14+
module MySQLPatch
15+
def begin_db_transaction
16+
execute 'SET TRANSACTION READ ONLY'
17+
super
18+
end
19+
end
20+
1421
if defined?(::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
1522
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(PostgreSQLAdapterPatch)
1623

1724
# Ensure transaction is read-only if it was began before this patch was loaded
1825
connection = ::ActiveRecord::Base.connection
1926
connection.execute 'SET TRANSACTION READ ONLY' if connection.open_transactions > 0
2027
end
28+
29+
if defined?(::ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter)
30+
::ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.prepend(MySQLPatch)
31+
32+
# Not possible to change a running transaction to read-only in MySQL
33+
# https://dev.mysql.com/doc/refman/8.4/en/set-transaction.html
34+
end
2135
end
2236
end
2337
end

lib/safer_rails_console/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module SaferRailsConsole
4-
VERSION = '0.8.0'
4+
VERSION = '0.9.0'
55
end

safer_rails_console.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Gem::Specification.new do |spec|
3939
spec.add_development_dependency 'bundler', '~> 2.0'
4040
spec.add_development_dependency 'climate_control', '~> 0.2.0'
4141
spec.add_development_dependency 'mixlib-shellout', '~> 2.2'
42+
spec.add_development_dependency 'mysql2', '~> 0.5'
4243
spec.add_development_dependency 'overcommit', '~> 0.39.0'
4344
spec.add_development_dependency 'pg', '~> 1.1'
4445
spec.add_development_dependency 'rake', '~> 12.0'

spec/contexts/db_sandbox.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
shared_context "db sandbox context" do
4+
let(:adapter) {}
5+
6+
shared_examples_for "auto_rollback" do
7+
it "automatically executes rollback and begins a new transaction after executing a invalid SQL statement" do
8+
run_console_commands('Model.create!', 'Model.where(invalid: :statement)', 'Model.create!')
9+
10+
# Run a new console session to ensure the database changes were not saved
11+
result = run_console_commands("puts \"Model Count = \#{Model.count}\"")
12+
expect(result.stdout).to include('Model Count = 0')
13+
end
14+
end
15+
16+
shared_examples_for "read_only" do
17+
it "enforces a read_only transaction" do
18+
# Run a console session that makes some database changes
19+
run_console_commands('Model.create!', 'Model.create!')
20+
21+
# Run a new console session to ensure the database changes were not saved
22+
result = run_console_commands("puts \"Model Count = \#{Model.count}\"")
23+
expect(result.stdout).to include('Model Count = 0')
24+
end
25+
26+
it "lets the user know that an operation could not be completed" do
27+
result = run_console_commands('Model.create!')
28+
expect(result.stdout).to include('An operation could not be completed due to read-only mode.')
29+
end
30+
end
31+
32+
def run_console_commands(*commands)
33+
commands += ['exit']
34+
environment = "development#{adapter.nil? ? '' : "-#{adapter}"}"
35+
run_console('--sandbox', input: commands.join("\n"), rails_env: environment)
36+
end
37+
end
Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,19 @@
11
# frozen_string_literal: true
22

3+
require_relative '../../contexts/db_sandbox'
4+
35
describe "Integration: patches/sandbox" do
4-
context "auto_rollback" do
5-
it "automatically executes rollback and begins a new transaction after executing a invalid SQL statement" do
6-
run_console_commands('Model.create!', 'Model.where(invalid: :statement)', 'Model.create!')
6+
include_context "db sandbox context"
77

8-
# Run a new console session to ensure the database changes were not saved
9-
result = run_console_commands('puts "Model Count = #{Model.count}"') # rubocop:disable Lint/InterpolationCheck
10-
expect(result.stdout).to include('Model Count = 0')
11-
end
8+
context "for PostgreSQL" do
9+
it_behaves_like "auto_rollback"
10+
it_behaves_like "read_only"
1211
end
1312

14-
context "read_only" do
15-
it "enforces a read_only transaction" do
16-
# Run a console session that makes some database changes
17-
run_console_commands('Model.create!', 'Model.create!')
18-
19-
# Run a new console session to ensure the database changes were not saved
20-
result = run_console_commands('puts "Model Count = #{Model.count}"') # rubocop:disable Lint/InterpolationCheck
21-
expect(result.stdout).to include('Model Count = 0')
22-
end
23-
24-
it "lets the user know that an operation could not be completed" do
25-
result = run_console_commands('Model.create!')
26-
expect(result.stdout).to include('An operation could not be completed due to read-only mode.')
27-
end
28-
end
13+
context "for Mysql" do
14+
let(:adapter) { :mysql2 }
2915

30-
def run_console_commands(*commands)
31-
commands += ['exit']
32-
run_console('--sandbox', input: commands.join("\n"))
16+
it_behaves_like "auto_rollback"
17+
it_behaves_like "read_only"
3318
end
3419
end

0 commit comments

Comments
 (0)