activerecord-wrapped_transaction 0.5.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 8516f90c26bfd324c7e7cb99cb66f3e1293d09f6
4
- data.tar.gz: 848e4b660600b027ca8f64a3a4aa669a2deebe0d
2
+ SHA256:
3
+ metadata.gz: ec2e05ea513d0e8840e54718aa1c20e2e7427e47386dc95c9174312ae4912bae
4
+ data.tar.gz: 1d8278fcfc51110911a860b1434b26bac62056967c30c73272c7b4f5cf811f4f
5
5
  SHA512:
6
- metadata.gz: bb0df845987d747f62e3079ca3ae7df3230f3932544ccaad7b048baa79b7a6cc77b95598e356f97673150e781d9e2b3713bb1ba1c6ff2ceed8890a3014c70c6f
7
- data.tar.gz: c53d50ac11583b99d463288c9f3342b1328df51f6970e1e432715ce69214b301a16a05b7367609b76ee14f18ea2c9eead4c545a7e799f6bce6db1e04c70ae9f7
6
+ metadata.gz: cc8889bed01589292453020f988dc5249cdb26841988348e8f845b7c744fdb2f7578bc7c682c8368fc7da2f76a5e66dc6ec04d22cca1a51b21cc29db5e5931f1
7
+ data.tar.gz: fa0e8e95052fa790cbded234bf2f464f95b5706b4349a3f00b6761623b8730470dafd4148af451853a8f90cc06606c2b9e9ec60c79e8a8379701a5231db17e49
@@ -0,0 +1,89 @@
1
+ name: "Main"
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ paths:
8
+ - 'lib/**'
9
+ - 'spec/**'
10
+ - '.github/workflows/main.yml'
11
+ pull_request:
12
+
13
+ jobs:
14
+ rspec:
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ ruby: [2.4, 2.5, 2.6, 2.7]
20
+ rails_version: [5, 6]
21
+ db: [pg, mysql2, sqlite3]
22
+ exclude:
23
+ - rails_version: 6
24
+ ruby: 2.4
25
+ env:
26
+ APPRAISAL_NAME: rails-${{ matrix.rails_version }}-${{ matrix.db }}
27
+ BUNDLE_GEMFILE: ${{ format('./gemfiles/rails_{0}_{1}.gemfile', matrix.rails_version, matrix.db) }}
28
+ DB_NAME: wrapped_transaction_test
29
+ PG_USER: test
30
+ PG_PASS: test
31
+ MYSQL_USER: root
32
+ MYSQL_PASS: root
33
+ steps:
34
+ - uses: actions/checkout@v2
35
+ - uses: ruby/setup-ruby@v1
36
+ with:
37
+ ruby-version: ${{ matrix.ruby }}
38
+ - name: "Install postgresql client"
39
+ if: ${{ matrix.db == 'pg' }}
40
+ run: |
41
+ sudo apt-get update --fix-missing
42
+ sudo apt-get -yqq install libpq-dev
43
+ - name: "Set up PG"
44
+ uses: harmon758/postgresql-action@v1
45
+ if: ${{ matrix.db == 'pg' }}
46
+ with:
47
+ postgresql version: '11'
48
+ postgresql db: ${{ env.DB_NAME }}
49
+ postgresql user: ${{ env.PG_USER }}
50
+ postgresql password: ${{ env.PG_PASS }}
51
+ - name: "Set PG DATABASE_URL"
52
+ if: ${{ matrix.db == 'pg' }}
53
+ run: |
54
+ echo "::set-env name=DATABASE_URL::postgres://${PG_USER}:${PG_PASS}@localhost/${DB_NAME}"
55
+ - name: "Shut down default MySQL database"
56
+ if: ${{ matrix.db == 'mysql2' }}
57
+ run: |
58
+ sudo systemctl stop mysql.service
59
+ - name: "Set Up MySQL"
60
+ if: ${{ matrix.db == 'mysql2' }}
61
+ uses: mirromutth/mysql-action@v1.1
62
+ with:
63
+ mysql database: ${{ env.DB_NAME }}
64
+ mysql root password: ${{ env.MYSQL_PASS }}
65
+ - name: "Set MySQL DATABASE_URL"
66
+ if: ${{ matrix.db == 'mysql2' }}
67
+ run: |
68
+ echo "::set-env name=DATABASE_URL::mysql2://${MYSQL_USER}:${MYSQL_PASS}@127.0.0.1/${DB_NAME}"
69
+ - name: "Install sqlite3"
70
+ if: ${{ matrix.db == 'sqlite3' }}
71
+ run: |
72
+ sudo apt-get update --fix-missing
73
+ sudo apt-get -yqq install sqlite3 libsqlite3-dev
74
+ echo "::set-env name=DATABASE_URL::sqlite3::memory:"
75
+ - uses: actions/cache@v2
76
+ with:
77
+ path: vendor/bundle
78
+ key: bundle-use-ruby-ubuntu-latest-${{ matrix.ruby }}-${{ env.APPRAISAL_NAME }}-${{ hashFiles(format('{0}.lock', env.BUNDLE_GEMFILE)) }}
79
+ restore-keys: |
80
+ bundle-use-ruby-ubuntu-latest-${{ matrix.ruby }}-${{ env.APPRAISAL_NAME }}
81
+ - name: bundle install
82
+ run: |
83
+ gem update --system
84
+ bundle config deployment true
85
+ bundle config path vendor/bundle
86
+ bundle install --jobs 4
87
+ - name: "Run RSpec"
88
+ run: |
89
+ bin/rspec
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
- --format documentation
2
1
  --color
3
2
  --require spec_helper
@@ -0,0 +1,29 @@
1
+ appraise "rails-5-pg" do
2
+ gem "activerecord", "~> 5", "< 6"
3
+ gem "pg"
4
+ end
5
+
6
+ appraise "rails-6-pg" do
7
+ gem "activerecord", "~> 6", "< 7"
8
+ gem "pg"
9
+ end
10
+
11
+ appraise "rails-5-mysql2" do
12
+ gem "activerecord", "~> 5", "< 6"
13
+ gem "mysql2"
14
+ end
15
+
16
+ appraise "rails-6-mysql2" do
17
+ gem "activerecord", "~> 6", "< 7"
18
+ gem "mysql2"
19
+ end
20
+
21
+ appraise "rails-5-sqlite3" do
22
+ gem "activerecord", "~> 5", "< 6"
23
+ gem "sqlite3"
24
+ end
25
+
26
+ appraise "rails-6-sqlite3" do
27
+ gem "activerecord", "~> 6", "< 7"
28
+ gem "sqlite3"
29
+ end
data/README.md CHANGED
@@ -1,16 +1,20 @@
1
1
  # Activerecord::WrappedTransaction
2
2
 
3
- Wrap transactions in a way that lets you easily detect if the block succeeded or rolled back
4
- for complex, procedural usage with an object interface.
3
+ Wrap transactions in an object-oriented way so that you can tell if an individual transaction
4
+ succeeded, rolled back, or was cancelled.
5
5
 
6
- It supports MySQL, PostgreSQL, and SQLite.
6
+ Supported versions and databases:
7
+
8
+ * Rails 5 and 6
9
+ * MySQL, PostgreSQL, and SQLite
10
+ * Ruby 2.4, 2.5, 2.6, 2.7
7
11
 
8
12
  ## Installation
9
13
 
10
14
  Add this line to your application's Gemfile:
11
15
 
12
16
  ```ruby
13
- gem 'activerecord-wrapped_transaction'
17
+ gem "activerecord-wrapped_transaction", "~> 0.9"
14
18
  ```
15
19
 
16
20
  And then execute:
@@ -19,29 +23,53 @@ And then execute:
19
23
 
20
24
  Or install it yourself as:
21
25
 
22
- $ gem install activerecord-wrapped_transaction
26
+ $ gem install activerecord-wrapped_transaction -v "~> 0.9"
23
27
 
24
28
  ## Usage
25
29
 
30
+ Contrived example:
31
+
26
32
  ```ruby
27
33
  ActiveRecord::Base.include ActiveRecord::WrappedTransaction
28
34
 
29
- wrapped_result = ActiveRecord::Base.wrapped_transaction do
30
- # Do something
35
+ wrapped_result = ActiveRecord::Base.wrapped_transaction do |context|
36
+ user = User.create! attributes
37
+
38
+ failable_result = OptionalThing.wrapped_transaction requires_new: true do
39
+ # This can fail, but we'll let it
40
+ OptionalThing.create! user: user, foo: "bar"
41
+ end
42
+
43
+ failable_result.rolled_back? # => true
44
+
45
+ # There is also a shorthand that uses the optional context helper
46
+ # This creates a new transaction layer that has requires_new: true
47
+ # set implicitly.
48
+ other_failable = context.maybe do
49
+ Something.explodes!
50
+ end
51
+
52
+ other_failable.rolled_back? # => true
53
+
54
+ cancelled = context.maybe do |inner_context|
55
+ inner_context.cancel! "arbitrarily"
56
+ end
57
+
58
+ cancelled.cancelled? # => true
59
+ cancelled.rolled_back? # => true
60
+ cancelled.cancellation_reason # => "arbitrarily"
61
+
62
+ # return our result
63
+ user
31
64
  end
32
65
 
33
- wrapped_result.result #
34
- wrapped_result.success?
35
- wrapped_result.rolled_back?
66
+ wrapped_result.result # => user
67
+ wrapped_result.success? # => true
68
+ wrapped_result.rolled_back? # => false
36
69
  ```
37
70
 
38
71
  You can pass the same options you would to `ActiveRecord::Base.transaction`: `requires_new`, `isolation`, `joinable`
39
72
 
40
- ## Todo
41
-
42
- * Test coverage for multiple connections (should be supported, but not guaranteed)
43
- * `Maybe` monad support for being able to execute complex logic more fluently.
44
-
45
73
  ## Development
46
74
 
47
75
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -56,4 +84,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/scrypt
56
84
  ## License
57
85
 
58
86
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
59
-
data/Rakefile CHANGED
@@ -5,4 +5,4 @@ RSpec::Core::RakeTask.new(:spec) do |c|
5
5
  c.verbose = false
6
6
  end
7
7
 
8
- task :default => :spec
8
+ task default: :spec
@@ -19,14 +19,12 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "activerecord", ">= 3.2", "< 6"
22
+ spec.add_dependency "activerecord", ">= 5", "< 7"
23
23
 
24
- spec.add_development_dependency "simplecov", "~> 0.12.0"
25
- spec.add_development_dependency "bundler", "~> 1.11"
26
- spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "appraisal", "~> 2.3.0"
25
+ spec.add_development_dependency "simplecov", "~> 0.18.5"
26
+ spec.add_development_dependency "database_cleaner-active_record", "~> 1.8.0"
27
+ spec.add_development_dependency "rake", ">= 12.3.3"
27
28
  spec.add_development_dependency "rspec", "~> 3.5"
28
29
  spec.add_development_dependency "pry"
29
- spec.add_development_dependency "pg", "~> 0.18.0"
30
- spec.add_development_dependency "mysql2", "~> 0.4.4"
31
- spec.add_development_dependency "sqlite3"
32
30
  end
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'appraisal' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("appraisal", "appraisal")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rake", "rake")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rspec-core", "rspec")
data/bin/setup CHANGED
@@ -16,5 +16,5 @@ IFS=$'\n\t'
16
16
  bundle install >/dev/null
17
17
 
18
18
  sqlite3 "$PROJECT_ROOT/db/wrapped_transaction_test.sqlite3" '.databases' >/dev/null
19
- mysql -e 'CREATE DATABASE IF NOT EXISTS wrapped_transaction_test;' >/dev/null
19
+ mysql -u root -e 'CREATE DATABASE IF NOT EXISTS wrapped_transaction_test;' >/dev/null
20
20
  psql -U $PG_USER -tc "SELECT 1 FROM pg_database WHERE datname = 'wrapped_transaction_test'" | grep -q 1 || psql -U $PG_USER -c "CREATE DATABASE wrapped_transaction_test"
@@ -1,32 +1,50 @@
1
- require 'active_record'
1
+ require "active_record"
2
2
 
3
3
  class DBConnector
4
4
  attr_reader :options
5
+ attr_reader :rails_version
6
+ attr_reader :type
7
+ attr_reader :url
5
8
 
6
9
  def initialize
7
10
  @options = {}
8
11
 
9
- case ENV['DB']
10
- when /mysql/
11
- options[:adapter] = 'mysql2'
12
- options[:username] = 'root'
13
- options[:database] = 'wrapped_transaction_test'
14
- options[:encoding] = 'utf8'
15
- when /postgres/
16
- options[:adapter] = 'postgresql'
17
- options[:database] = 'wrapped_transaction_test'
12
+ raise "No BUNDLE_GEMFILE set" if ENV["BUNDLE_GEMFILE"].blank?
13
+
14
+ @gemfile = File.basename(ENV["BUNDLE_GEMFILE"] || "", ".gemfile")
15
+
16
+ @url = ENV["DATABASE_URL"]
17
+
18
+ @options[:url] = @url if @url.present?
19
+
20
+ _, @rails_version, @type = *@gemfile.match(/\Arails_(\d+)_(.+)\z/)
21
+
22
+ case @type
23
+ when /mysql2\z/
24
+ options[:adapter] = "mysql2"
25
+ options[:username] = ENV.fetch("MYSQL_USER", "root")
26
+ options[:password] = ENV.fetch("MYSQL_PASS", nil)
27
+ options[:database] = ENV.fetch("DB_NAME", "wrapped_transaction_test")
28
+ options[:encoding] = "utf8"
29
+ when /pg\z/
30
+ options[:adapter] = "postgresql"
31
+ options[:database] = ENV.fetch("DB_NAME", "wrapped_transaction_test")
32
+ when /sqlite3\z/
33
+ options[:adapter] = "sqlite3"
34
+ options[:database] = File.join(__dir__, "wrapped_transaction_test.sqlite3")
18
35
  else
19
- options[:adapter] = 'sqlite3'
20
- options[:database] = File.join(__dir__, 'wrapped_transaction_test.sqlite3')
36
+ raise "Unknown db adapter: #{@type}"
21
37
  end
22
38
  end
23
39
 
24
40
  def ci?
25
- ENV['CI'].present?
41
+ ENV["CI"].present?
26
42
  end
27
43
  end
28
44
 
29
- ActiveRecord::Base.establish_connection DBConnector.new.options
45
+ DBENV = DBConnector.new
46
+
47
+ ActiveRecord::Base.establish_connection DBENV.url || DBENV.options
30
48
 
31
- require_relative './schema'
32
- require_relative './models'
49
+ require_relative "./schema"
50
+ require_relative "./models"
@@ -5,5 +5,5 @@ class ApplicationRecord < ActiveRecord::Base
5
5
  end
6
6
 
7
7
  class Widget < ApplicationRecord
8
- validates_presence_of :name
8
+ validates :name, presence: true, uniqueness: true
9
9
  end
@@ -5,5 +5,7 @@ ActiveRecord::Schema.define do
5
5
  t.string :name
6
6
 
7
7
  t.timestamps null: false
8
+
9
+ t.index :name, unique: true
8
10
  end
9
11
  end
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 5", "< 6"
6
+ gem "mysql2"
7
+
8
+ gemspec path: "../"