after_commit_changes 1.0.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 +7 -0
- data/CHANGELOG.md +10 -0
- data/MIT_LICENSE.txt +20 -0
- data/README.md +75 -0
- data/VERSION +1 -0
- data/after_commit_changes.gemspec +36 -0
- data/lib/after_commit_changes.rb +38 -0
- metadata +78 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 69c7f5e46ca54be22ea0fecc939d4a092681e5183f3f80ef7850a7b4588aa702
|
4
|
+
data.tar.gz: 03c2c226e947acab43f004a8b98bfe6f0b27f3959a1abde8b421715861324442
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c478de53032f858476b399ec9aff94aa46b14f8ccb341a29332f7cbfcd037a6fb7b3d85244172042f88c012550c7c876ee0f664c8c6af875eeb57044720ef40b
|
7
|
+
data.tar.gz: 99e87f8493b2fe465ef4d14866691e4fb6ffffefbd8e8dd5e193ae262961e51a82546a2e0d43480e8bbb0cc383dd52356963489824f8ed467ac235255843fe04
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## 1.0.0
|
8
|
+
|
9
|
+
### Added
|
10
|
+
- Initial release
|
data/MIT_LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2023 Brian Durand
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# AfterCommitChanges
|
2
|
+
|
3
|
+
[](https://github.com/bdurand/after_commit_changes/actions/workflows/continuous_integration.yml)
|
4
|
+
[](https://github.com/bdurand/after_commit_changes/actions/workflows/regression_test.yml)
|
5
|
+
[](https://github.com/testdouble/standard)
|
6
|
+
[](https://badge.fury.io/rb/after_commit_changes)
|
7
|
+
|
8
|
+
This gem addresses an [issue in ActiveRecord](https://github.com/rails/rails/pull/50011) with the `saved_changes` value when a record is updated multiple times in a single database transaction.
|
9
|
+
|
10
|
+
After a record is saved, you can check the set of changes with the `saved_changes` method using the [ActiveModel::Dirty API](https://api.rubyonrails.org/classes/ActiveModel/Dirty.html). However, when a record is saved multiple times, the list of saved changes is reset with each save operation. This can be an issue inside of an `after_commit` or `before_commit` callback since those callbacks are only called once for the transaction and will only get the last set of changes.
|
11
|
+
|
12
|
+
This can be a problem if you have a callback that checks for changes to specific fields. Consider this model where we want to run an asychronous job when a user changes their email address:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class User < ApplicationRecord
|
16
|
+
after_commit :notify_email_changes, if: :email_changed?
|
17
|
+
|
18
|
+
def notify_email_changes
|
19
|
+
NotifyEmailChangesJob.perform_later(id)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
This breaks down if a record is saved twice in a single transaction.
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
user.transaction do
|
28
|
+
user.update!(email: params[:email])
|
29
|
+
if user.last_visited_at < 1.day.ago
|
30
|
+
user.update!(last_visited_at: Time.now)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
In the case where we update the `last_visited_at` field, the `email_changed?` method will return false since the email address was not changed in the last save operation and `notify_email_changes` method will not be called.
|
36
|
+
|
37
|
+
This gem addresses this issue by merging all saved changes together before calling the `after_commit` or `before_commit` callbacks so that `saved_changes` will return the complete list of changes for the transaction.
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
To use the gem, you simply need to mix it into your models. You can include it in all models by including it in your `ApplicationRecord` class:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class ApplicationRecord < ActiveRecord::Base
|
45
|
+
include AfterCommitChanges
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
## Installation
|
50
|
+
|
51
|
+
Add this line to your application's Gemfile:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
gem "after_commit_changes"
|
55
|
+
```
|
56
|
+
|
57
|
+
Then execute:
|
58
|
+
```bash
|
59
|
+
$ bundle
|
60
|
+
```
|
61
|
+
|
62
|
+
Or install it yourself as:
|
63
|
+
```bash
|
64
|
+
$ gem install gem "after_commit_changes"
|
65
|
+
```
|
66
|
+
|
67
|
+
## Contributing
|
68
|
+
|
69
|
+
Open a pull request on GitHub.
|
70
|
+
|
71
|
+
Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
|
72
|
+
|
73
|
+
## License
|
74
|
+
|
75
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,36 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "after_commit_changes"
|
3
|
+
spec.version = File.read(File.join(__dir__, "VERSION")).strip
|
4
|
+
spec.authors = ["Brian Durand"]
|
5
|
+
spec.email = ["bbdurand@gmail.com"]
|
6
|
+
|
7
|
+
spec.summary = "Aggregate all changes made to an ActiveRecord model inside a transaction into a single set of changes."
|
8
|
+
spec.homepage = "https://github.com/bdurand/after_commit_changes"
|
9
|
+
spec.license = "MIT"
|
10
|
+
|
11
|
+
# Specify which files should be added to the gem when it is released.
|
12
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
13
|
+
ignore_files = %w[
|
14
|
+
.
|
15
|
+
Appraisals
|
16
|
+
Gemfile
|
17
|
+
Gemfile.lock
|
18
|
+
Rakefile
|
19
|
+
config.ru
|
20
|
+
assets/
|
21
|
+
bin/
|
22
|
+
gemfiles/
|
23
|
+
spec/
|
24
|
+
]
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
|
27
|
+
end
|
28
|
+
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_dependency "activerecord", ">= 6.0"
|
32
|
+
|
33
|
+
spec.add_development_dependency "bundler"
|
34
|
+
|
35
|
+
spec.required_ruby_version = ">= 2.5"
|
36
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AfterCommitChanges
|
4
|
+
VERSION = File.read(File.expand_path("../VERSION", __dir__)).strip
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.before_commit do
|
8
|
+
rollup_mutations_for_transaction!
|
9
|
+
end
|
10
|
+
|
11
|
+
base.after_save do
|
12
|
+
@after_commit_saved_changes ||= []
|
13
|
+
@after_commit_saved_changes << saved_changes
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def rollup_mutations_for_transaction!
|
20
|
+
return unless @after_commit_saved_changes && @after_commit_saved_changes.size > 1
|
21
|
+
|
22
|
+
attributes = @_start_transaction_state[:attributes].deep_dup
|
23
|
+
mutations = ActiveModel::AttributeMutationTracker.new(attributes)
|
24
|
+
|
25
|
+
@after_commit_saved_changes[1, @after_commit_saved_changes.length].each do |changes|
|
26
|
+
changes.each do |attr_name, value_change|
|
27
|
+
attribute = attributes[attr_name]
|
28
|
+
last_value = value_change.last
|
29
|
+
last_value = last_value.to_h if last_value.is_a?(ActiveSupport::HashWithIndifferentAccess)
|
30
|
+
attributes[attr_name] = ActiveModel::Attribute.from_user(attr_name, last_value, attribute.type, attribute)
|
31
|
+
mutations.force_change(attr_name) unless mutations.changed?(attr_name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
@after_commit_saved_changes = nil
|
36
|
+
@mutations_before_last_save = mutations
|
37
|
+
end
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: after_commit_changes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Durand
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-11-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- bbdurand@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- CHANGELOG.md
|
49
|
+
- MIT_LICENSE.txt
|
50
|
+
- README.md
|
51
|
+
- VERSION
|
52
|
+
- after_commit_changes.gemspec
|
53
|
+
- lib/after_commit_changes.rb
|
54
|
+
homepage: https://github.com/bdurand/after_commit_changes
|
55
|
+
licenses:
|
56
|
+
- MIT
|
57
|
+
metadata: {}
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '2.5'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubygems_version: 3.4.20
|
74
|
+
signing_key:
|
75
|
+
specification_version: 4
|
76
|
+
summary: Aggregate all changes made to an ActiveRecord model inside a transaction
|
77
|
+
into a single set of changes.
|
78
|
+
test_files: []
|