ar_transaction_changes 1.1.10 → 1.1.12

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
2
  SHA256:
3
- metadata.gz: 714d24f3fc939b5ab4170bd5863573aea0b5f9f918d3494f51656096579d0cc6
4
- data.tar.gz: 62d4814dccd3e3f5623958e1182ba8654eca9d37cc01bf986cfa09527fc5b6b5
3
+ metadata.gz: b6fdf694a84d42dbffbe36a1f6cb7e44f6743e7d6d0f107235a87390a66da4e5
4
+ data.tar.gz: a3e154ff527f732da68b229c33a342a7a28a578c9c694d4c8565d0c1d34bc72e
5
5
  SHA512:
6
- metadata.gz: 1a77baf0fd44d182e407c79ae17499933aa0a85aa1a9d2c7be3c4b1df2a0a6bee34a4d18d10bb82a5aea3b45aaa65dfde4d6254bf49b5aa8abb2dd293900b6ac
7
- data.tar.gz: ab7fd9f52be45e78f6911159cb7c472ac9d78151891b1968bfcf1629ee97e65a944ef71178c5b5411f935b680d5a460f99f6884c3d8ce08aad6274b882b175f5
6
+ metadata.gz: 72cc3553913ba69636a5f9a0c64361aeefbceb0fad6933d273a67fadf5ddefec6cad09168bc6a17cbd04a4bb83af287ede6f4b7b894c7acb9facc1198697760b
7
+ data.tar.gz: 38a488f2626aea343a804696bb643976a0e2830bc39ee6036ba80d1cdb47454dad90435a3371d31fb8acf8d9a835b464b5fc956efa6d55f44056cdf6ca114469
@@ -12,12 +12,15 @@ jobs:
12
12
  strategy:
13
13
  fail-fast: false
14
14
  matrix:
15
- ruby: ['3.2', '3.3', '3.4']
15
+ # Cover maintained Ruby branches, including security-only maintenance.
16
+ ruby: ['3.3', '3.4', '4.0']
16
17
  rails: ['7.0', '7.1', '7.2', '8.0']
17
18
  exclude:
18
19
  # Rails 7.0 requires Ruby < 3.4
19
20
  - ruby: '3.4'
20
21
  rails: '7.0'
22
+ - ruby: '4.0'
23
+ rails: '7.0'
21
24
 
22
25
  name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }}
23
26
 
@@ -29,7 +32,7 @@ jobs:
29
32
  run: |
30
33
  sudo apt-get update
31
34
  sudo apt-get -y install libsqlite3-dev
32
- - uses: actions/checkout@v3
35
+ - uses: actions/checkout@v7
33
36
  - name: Set up Ruby
34
37
  uses: ruby/setup-ruby@v1
35
38
  with:
@@ -45,7 +48,7 @@ jobs:
45
48
  strategy:
46
49
  fail-fast: false
47
50
  matrix:
48
- ruby: ['3.2', '3.3', '3.4']
51
+ ruby: ['3.3', '3.4', '4.0']
49
52
 
50
53
  name: Ruby ${{ matrix.ruby }} / Rails head
51
54
 
@@ -57,7 +60,7 @@ jobs:
57
60
  run: |
58
61
  sudo apt-get update
59
62
  sudo apt-get -y install libsqlite3-dev
60
- - uses: actions/checkout@v3
63
+ - uses: actions/checkout@v7
61
64
  - name: Set up Ruby
62
65
  uses: ruby/setup-ruby@v1
63
66
  with:
@@ -82,7 +85,7 @@ jobs:
82
85
  fi
83
86
  echo "✅ All required tests passed"
84
87
  if [ "${{ needs.test-head.result }}" != "success" ]; then
85
- echo "⚠️ Rails head tests failed (non-blocking)"
88
+ echo "Rails head tests failed (non-blocking)"
86
89
  else
87
- echo "✅ Rails head tests passed"
90
+ echo "Rails head tests passed"
88
91
  fi
data/README.md CHANGED
@@ -64,3 +64,8 @@ gem 'globalize', require: ['ar_transaction_changes', 'globalize']
64
64
  3. Commit your changes (`git commit -am 'Add some feature'`)
65
65
  4. Push to the branch (`git push origin my-new-feature`)
66
66
  5. Create new Pull Request
67
+
68
+ ## Releasing
69
+
70
+ Publishing is restricted to [RubyGems.org](https://rubygems.org) through the
71
+ `allowed_push_host` gem metadata.
@@ -12,6 +12,7 @@ Gem::Specification.new do |gem|
12
12
  gem.summary = %q{Store transaction changes for active record objects}
13
13
  gem.homepage = "https://github.com/Shopify/ar_transaction_changes"
14
14
  gem.license = "MIT"
15
+ gem.metadata["allowed_push_host"] = "https://rubygems.org"
15
16
 
16
17
  gem.required_ruby_version = ">= 3.2"
17
18
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ArTransactionChanges
4
- VERSION = "1.1.10"
4
+ VERSION = "1.1.12"
5
5
  end
@@ -4,37 +4,87 @@ require "ar_transaction_changes/version"
4
4
  require "active_record"
5
5
 
6
6
  module ArTransactionChanges
7
- def self.included(base)
8
- if base.respond_to?(:_commit_callbacks) && base.respond_to?(:_rollback_callbacks)
9
- if !base._commit_callbacks.empty? || !base._rollback_callbacks.empty?
10
- raise "ArTransactionChanges must be included before defining any after_commit or after_rollback callbacks"
7
+ # Since rails/rails#59191, generated attribute writers (`name=`) call
8
+ # `@attributes.write_from_user` directly instead of `_write_attribute`, so the
9
+ # `_write_attribute` override below no longer sees them. Generate writers that
10
+ # record the change around that same direct write.
11
+ module GeneratedWriters # :nodoc:
12
+ # Rails versions with that change also dropped the `owner` argument from
13
+ # `AttrNames.define_attribute_accessor_method`, which the code below relies on.
14
+ def self.needed?
15
+ ActiveModel::AttributeMethods::AttrNames
16
+ .method(:define_attribute_accessor_method)
17
+ .parameters.none? { |_type, name| name == :owner }
18
+ end
19
+
20
+ def self.raise_on_assign_to_attr_readonly?
21
+ if ActiveRecord.respond_to?(:_raise_on_assign_to_attr_readonly)
22
+ ActiveRecord._raise_on_assign_to_attr_readonly
23
+ else
24
+ ActiveRecord.raise_on_assign_to_attr_readonly
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def define_method_attribute=(canonical_name, owner:, as: canonical_name)
31
+ return super unless self < ArTransactionChanges
32
+
33
+ readonly = GeneratedWriters.raise_on_assign_to_attr_readonly? && readonly_attribute?(canonical_name)
34
+
35
+ ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
36
+ canonical_name, writer: true,
37
+ ) do |temp_method_name, attr_name_expr|
38
+ namespace = readonly ? :ar_transaction_changes_readonly : :ar_transaction_changes
39
+ owner.define_cached_method(temp_method_name, as: "#{as}=", namespace: namespace) do |batch|
40
+ batch << "def #{temp_method_name}(value)"
41
+ if readonly
42
+ batch <<
43
+ " unless new_record?" <<
44
+ " raise ActiveRecord::ReadonlyAttributeError.new(#{attr_name_expr})" <<
45
+ " end"
46
+ end
47
+ batch <<
48
+ " _store_transaction_changed_attributes(#{attr_name_expr}) do" <<
49
+ " @attributes.write_from_user(#{attr_name_expr}, value)" <<
50
+ " end" <<
51
+ "end"
52
+ end
11
53
  end
12
54
  end
13
55
  end
14
56
 
15
- if ActiveRecord.version >= Gem::Version.new('8.1.0.alpha')
16
- def _run_commit_callbacks!
57
+ if ActiveRecord.version >= Gem::Version.new('7.1')
58
+ def run_callbacks(kind, type = nil)
17
59
  super
18
60
  ensure
19
- @transaction_changed_attributes = nil
61
+ if kind == :commit || kind == :rollback
62
+ @transaction_changed_attributes = nil
63
+ end
20
64
  end
21
65
 
22
- def _run_rollback_callbacks!
23
- super
24
- ensure
25
- @transaction_changed_attributes = nil
66
+ if ActiveRecord.version >= Gem::Version.new('8.1.0.alpha')
67
+ # We should reset @transaction_changed_attributes in the no-op version of the method.
68
+ # Even if there are no callbacks registered, attributes should be reset on commit/rollback.
69
+ def _run_commit_callbacks
70
+ super
71
+ ensure
72
+ @transaction_changed_attributes = nil
73
+ end
74
+
75
+ def _run_rollback_callbacks
76
+ super
77
+ ensure
78
+ @transaction_changed_attributes = nil
79
+ end
26
80
  end
27
81
  else
28
- def _run_commit_callbacks
82
+ def run_callbacks(kind)
29
83
  super
30
84
  ensure
31
- @transaction_changed_attributes = nil
32
- end
33
-
34
- def _run_rollback_callbacks
35
- super
36
- ensure
37
- @transaction_changed_attributes = nil
85
+ if kind == :commit || kind == :rollback
86
+ @transaction_changed_attributes = nil
87
+ end
38
88
  end
39
89
  end
40
90
 
@@ -113,3 +163,10 @@ module ArTransactionChanges
113
163
  end
114
164
  end
115
165
  end
166
+
167
+ # Install on ActiveRecord::Base rather than from an `included` hook, so models that get
168
+ # ArTransactionChanges through a concern are covered too. The writers are generated on
169
+ # first use, after the model's modules are included.
170
+ ActiveSupport.on_load(:active_record) do
171
+ singleton_class.prepend(ArTransactionChanges::GeneratedWriters) if ArTransactionChanges::GeneratedWriters.needed?
172
+ end
@@ -0,0 +1,13 @@
1
+ class Account < ActiveRecord::Base
2
+ include ArTransactionChanges
3
+
4
+ attr_readonly :handle
5
+
6
+ attr_accessor :stored_transaction_changes
7
+
8
+ after_commit :store_transaction_changes_for_tests
9
+
10
+ def store_transaction_changes_for_tests
11
+ @stored_transaction_changes = transaction_changed_attributes.to_h
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module TracksTransactionChanges
2
+ extend ActiveSupport::Concern
3
+ include ArTransactionChanges
4
+
5
+ included do
6
+ attr_accessor :stored_transaction_changes
7
+
8
+ after_commit { @stored_transaction_changes = transaction_changed_attributes.to_h }
9
+ end
10
+ end
11
+
12
+ class PlanChange < ActiveRecord::Base
13
+ self.table_name = "accounts"
14
+
15
+ include TracksTransactionChanges
16
+ end
data/test/models/user.rb CHANGED
@@ -17,6 +17,8 @@ class User < ActiveRecord::Base
17
17
  serialize :notes, Array
18
18
  end
19
19
 
20
+ alias_attribute :job, :occupation
21
+
20
22
  attr_accessor :stored_transaction_changes
21
23
 
22
24
  after_commit :store_transaction_changes_for_tests
data/test/test_helper.rb CHANGED
@@ -25,6 +25,11 @@ ActiveRecord::Base.connection.tap do |db|
25
25
  t.text :notes
26
26
  t.timestamps null: false
27
27
  end
28
+
29
+ db.create_table(:accounts) do |t|
30
+ t.string :handle
31
+ t.string :plan
32
+ end
28
33
  end
29
34
 
30
35
  Dir[test_dir.join("models/*.rb")].each{ |file| require file }
@@ -23,6 +23,43 @@ class TransactionChangesTest < Minitest::Test
23
23
  assert_equal ["Dylan", "Dillon"], @user.stored_transaction_changes["name"]
24
24
  end
25
25
 
26
+ def test_transaction_changes_for_aliased_attribute_writer
27
+ @user.job = "Tester"
28
+ @user.save!
29
+
30
+ assert_equal ["Developer", "Tester"], @user.stored_transaction_changes["occupation"]
31
+ end
32
+
33
+ def test_readonly_attribute_writer_still_raises_on_persisted_records
34
+ account = Account.create!(handle: "dylan", plan: "basic")
35
+ assert_equal({ "handle" => nil, "plan" => nil }, account.stored_transaction_changes.slice("handle", "plan"))
36
+
37
+ raises = if ActiveRecord.respond_to?(:_raise_on_assign_to_attr_readonly)
38
+ ActiveRecord._raise_on_assign_to_attr_readonly
39
+ elsif ActiveRecord.respond_to?(:raise_on_assign_to_attr_readonly)
40
+ ActiveRecord.raise_on_assign_to_attr_readonly
41
+ end
42
+ if raises
43
+ assert_raises(ActiveRecord::ReadonlyAttributeError) { account.handle = "dillon" }
44
+ end
45
+
46
+ account.plan = "pro"
47
+ account.save!
48
+ assert_equal({ "plan" => "basic" }, account.stored_transaction_changes)
49
+ ensure
50
+ Account.delete_all
51
+ end
52
+
53
+ def test_transaction_changes_when_included_through_a_concern
54
+ record = PlanChange.create!(handle: "dylan", plan: "basic")
55
+ record.plan = "pro"
56
+ record.save!
57
+
58
+ assert_equal({ "plan" => "basic" }, record.stored_transaction_changes)
59
+ ensure
60
+ PlanChange.delete_all
61
+ end
62
+
26
63
  def test_transaction_changes_for_updating_attribute
27
64
  @user[:name] = "Dillon"
28
65
  @user.save!
@@ -174,28 +211,4 @@ class TransactionChangesTest < Minitest::Test
174
211
 
175
212
  assert_equal [[], ['a', 'b']], @user.stored_transaction_changes['notes']
176
213
  end
177
-
178
- def test_do_not_allow_adding_this_gem_to_a_model_after_commit_callbacks_are_defined
179
- assert_raises(RuntimeError) do
180
- Class.new(ActiveRecord::Base) do
181
- after_commit :some_method
182
-
183
- include ArTransactionChanges
184
-
185
- def some_method; end
186
- end
187
- end
188
- end
189
-
190
- def test_do_not_allow_adding_this_gem_to_a_model_after_rollback_callbacks_are_defined
191
- assert_raises(RuntimeError) do
192
- Class.new(ActiveRecord::Base) do
193
- after_rollback :some_method
194
-
195
- include ArTransactionChanges
196
-
197
- def some_method; end
198
- end
199
- end
200
- end
201
214
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar_transaction_changes
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.10
4
+ version: 1.1.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dylan Thacker-Smith
@@ -64,13 +64,16 @@ files:
64
64
  - lib/ar_transaction_changes.rb
65
65
  - lib/ar_transaction_changes/version.rb
66
66
  - test/database.yml.default
67
+ - test/models/account.rb
68
+ - test/models/plan_change.rb
67
69
  - test/models/user.rb
68
70
  - test/test_helper.rb
69
71
  - test/transaction_changes_test.rb
70
72
  homepage: https://github.com/Shopify/ar_transaction_changes
71
73
  licenses:
72
74
  - MIT
73
- metadata: {}
75
+ metadata:
76
+ allowed_push_host: https://rubygems.org
74
77
  rdoc_options: []
75
78
  require_paths:
76
79
  - lib
@@ -85,11 +88,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
88
  - !ruby/object:Gem::Version
86
89
  version: '0'
87
90
  requirements: []
88
- rubygems_version: 3.6.9
91
+ rubygems_version: 4.0.23
89
92
  specification_version: 4
90
93
  summary: Store transaction changes for active record objects
91
94
  test_files:
92
95
  - test/database.yml.default
96
+ - test/models/account.rb
97
+ - test/models/plan_change.rb
93
98
  - test/models/user.rb
94
99
  - test/test_helper.rb
95
100
  - test/transaction_changes_test.rb