activerecord_defer_persist 0.1.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/README.md +58 -0
- data/lib/activerecord_defer_persist/defer_persist.rb +81 -0
- data/lib/activerecord_defer_persist/version.rb +5 -0
- metadata +159 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7db83dbd1d88f1c7f7056bc9b459eb1e212712abc09d868fc0d914c0afbaf8d1
|
4
|
+
data.tar.gz: cb352fcb2375e5f018a4299efb3f56557ff2cfa4c257c9ae80a512225dba14a1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dce63024a78abeefe8851095c72daceac3236f3a466dccf1853ccacc1b70317ff7fdb5e002ca361d031574ef48acc08f10a7006d45edfd2564dc421210c292f7
|
7
|
+
data.tar.gz: bb8d7a68d2acb158c471e7cfee9749c3432aa67f6fecddc2faea263c76740345c63fc92a93ff00d86abbee76471c9792a8faee021cb559e778cf24be7bef90d0
|
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# ActiverecordDeferPersist
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/activerecord_defer_persist)
|
4
|
+
[](https://github.com/betagouv/activerecord_defer_persist/actions)
|
5
|
+
|
6
|
+
> [!IMPORTANT]
|
7
|
+
> This gem is still experimental. Please use it with caution and make sure you understand its implications.
|
8
|
+
|
9
|
+
## TODO
|
10
|
+
|
11
|
+
- [ ] Add tests for a simple `has_many` association
|
12
|
+
- [ ] Support `ActiveModel::Dirty` so we can track changes association with `team.user_ids_changed?`
|
13
|
+
- [ ] wrap changes in a transaction
|
14
|
+
|
15
|
+
## Description
|
16
|
+
|
17
|
+
The default behavior of ActiveRecord is to persist the associated records immediately when you assign them, which can be surprising/dangerous.
|
18
|
+
For example calling `team.user_ids = [1, 2]`, or `team.assign_attributes(params.permit(:user_ids))` will persist immediately.
|
19
|
+
|
20
|
+
This gem allows you to defer persisting to the database until the `save` method is called.
|
21
|
+
|
22
|
+
## Context
|
23
|
+
|
24
|
+
Inspiration for this gem goes to [this gist by @sudoremo](https://gist.github.com/sudoremo/4204e399e547ff7e3afdd0d89a5aaf3e), thank you!
|
25
|
+
There is an ongoing (may 2025) PR in Rails to add a `defer` option to `has_many` associations: https://github.com/rails/rails/pull/55041
|
26
|
+
|
27
|
+
## Installation
|
28
|
+
|
29
|
+
Install the gem and add to the application's Gemfile by executing:
|
30
|
+
|
31
|
+
```bash
|
32
|
+
bundle add activerecord_defer_persist
|
33
|
+
```
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
class Team < ApplicationRecord
|
39
|
+
include ActiverecordDeferPersist
|
40
|
+
|
41
|
+
has_many :users
|
42
|
+
defer_persist :users
|
43
|
+
end
|
44
|
+
|
45
|
+
team = Team.new(user_ids: [])
|
46
|
+
team.user_ids = [1, 2] # thid does not persist anymore !
|
47
|
+
team.save # this will now persist
|
48
|
+
```
|
49
|
+
|
50
|
+
## Development
|
51
|
+
|
52
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
53
|
+
|
54
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
55
|
+
|
56
|
+
## Contributing
|
57
|
+
|
58
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/betagouv/activerecord_defer_persist.
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
require "logger"
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module ActiverecordDeferPersist
|
6
|
+
module Concern
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
after_save :persist_deferred
|
11
|
+
end
|
12
|
+
|
13
|
+
def persist_deferred
|
14
|
+
return if !@_defer_persist_ids && !@_defer_persist_records
|
15
|
+
|
16
|
+
@_defer_persist_ids&.each do |association, new_ids|
|
17
|
+
singular = association.to_s.singularize
|
18
|
+
send(:"original_#{singular}_ids=", new_ids)
|
19
|
+
end
|
20
|
+
@_defer_persist_ids = {}
|
21
|
+
|
22
|
+
@_defer_persist_records&.each do |association, new_records|
|
23
|
+
singular = association.to_s.singularize
|
24
|
+
send(:"original_#{singular}s=", new_records)
|
25
|
+
end
|
26
|
+
@_defer_persist_records = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def reload
|
30
|
+
@_defer_persist_ids = {}
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
class_methods do
|
35
|
+
def defer_persist(association)
|
36
|
+
singular = association.to_s.singularize
|
37
|
+
|
38
|
+
alias_method :"original_#{singular}_ids=", :"#{singular}_ids="
|
39
|
+
alias_method :"original_#{singular}s=", :"#{singular}s="
|
40
|
+
alias_method :"original_#{singular}_ids", :"#{singular}_ids"
|
41
|
+
|
42
|
+
define_method "#{singular}_ids=" do |ids|
|
43
|
+
@_defer_persist_ids ||= {}
|
44
|
+
@_defer_persist_ids[association] = ids
|
45
|
+
end
|
46
|
+
|
47
|
+
define_method "#{singular}s=" do |records|
|
48
|
+
@_defer_persist_records ||= {}
|
49
|
+
@_defer_persist_records[association] = records
|
50
|
+
# if something was stored in _defer_persist_ids, clear it now
|
51
|
+
@_defer_persist_ids.delete(association) if @_defer_persist_ids&.key?(association)
|
52
|
+
end
|
53
|
+
|
54
|
+
define_method "#{singular}_ids" do
|
55
|
+
@_defer_persist_ids ||= {}
|
56
|
+
@_defer_persist_records ||= {}
|
57
|
+
if @_defer_persist_ids.key?(association)
|
58
|
+
@_defer_persist_ids[association]
|
59
|
+
elsif @_defer_persist_records.key?(association)
|
60
|
+
@_defer_persist_records[association].map(&:id)
|
61
|
+
else
|
62
|
+
super()
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
define_method association.to_s do
|
67
|
+
@_defer_persist_ids ||= {}
|
68
|
+
@_defer_persist_records ||= {}
|
69
|
+
if @_defer_persist_ids.key?(association)
|
70
|
+
self.class.reflect_on_association(association).klass.where(id: @_defer_persist_ids[association])
|
71
|
+
elsif @_defer_persist_records.key?(association)
|
72
|
+
@_defer_persist_records[association]
|
73
|
+
else
|
74
|
+
super()
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
metadata
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activerecord_defer_persist
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- BetaGouv developers
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: activesupport
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '6'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '6'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: activerecord
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '6'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '6'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: logger
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: mutex_m
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: base64
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: bigdecimal
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
type: :development
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: rspec
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3'
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '3'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: sqlite3
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '1'
|
117
|
+
type: :development
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '1'
|
124
|
+
description: ActiveRecord defaults to immediately persisting changes to the database
|
125
|
+
on assignments like user.session_ids = [1, 2]. This is a surprising behaviour that
|
126
|
+
this gem aims to fix to be more coherent with regular assignments.
|
127
|
+
email:
|
128
|
+
- adrien.di_pasquale@beta.gouv.fr
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- README.md
|
134
|
+
- lib/activerecord_defer_persist/defer_persist.rb
|
135
|
+
- lib/activerecord_defer_persist/version.rb
|
136
|
+
homepage: https://github.com/betagouv/activerecord_defer_persist
|
137
|
+
licenses:
|
138
|
+
- MIT
|
139
|
+
metadata:
|
140
|
+
rubygems_mfa_required: 'true'
|
141
|
+
rdoc_options: []
|
142
|
+
require_paths:
|
143
|
+
- lib
|
144
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '3.2'
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
requirements: []
|
155
|
+
rubygems_version: 3.6.7
|
156
|
+
specification_version: 4
|
157
|
+
summary: Defer persisting changes to the database on ActiveRecord has_many associations
|
158
|
+
assignments
|
159
|
+
test_files: []
|