active_record_upsert 0.9.5 → 0.10.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 +5 -5
- data/Gemfile.base +1 -1
- data/Gemfile.rails-6-1 +5 -0
- data/README.md +53 -2
- data/active_record_upsert.gemspec +1 -1
- data/lib/active_record_upsert.rb +1 -1
- data/lib/active_record_upsert/active_record/persistence.rb +9 -8
- data/lib/active_record_upsert/compatibility/rails51.rb +7 -6
- data/lib/active_record_upsert/compatibility/rails60.rb +9 -0
- data/lib/active_record_upsert/version.rb +1 -1
- metadata +10 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d08fb14b2c2b66a287518710472aba53e095a4cc54c581a5c1dcf5ecd86088f6
|
4
|
+
data.tar.gz: 02b6d45767b9ecfeb8bcd1b27dcef517470d7f7747f522d3c14db445725ab3fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fefab82228fbfc68fbdd96912617ec7bdea1547be32b02800d46d2507bc0ba62c78001c21bf1cccb8b689ef1d5426432b8d49d40379dac8a044a326bcc158c9e
|
7
|
+
data.tar.gz: 249d793b70c0855b907a1036ca887a7c0c4b1ab68ee28a276d22b14bbdabd1ee6cf3973b11f6f818d1507fb0df7bf27475aca0c4d1e2e1f0e613be53ab85ecf0
|
data/Gemfile.base
CHANGED
data/Gemfile.rails-6-1
ADDED
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
# ActiveRecordUpsert
|
6
6
|
|
7
|
-
Real upsert for PostgreSQL 9.5+ and Rails 5 / ActiveRecord 5
|
7
|
+
Real upsert for PostgreSQL 9.5+ and Rails 5+ / ActiveRecord 5+. Uses [ON CONFLICT DO UPDATE](http://www.postgresql.org/docs/9.5/static/sql-insert.html).
|
8
8
|
|
9
9
|
## Main points
|
10
10
|
|
@@ -15,7 +15,7 @@ Real upsert for PostgreSQL 9.5+ and Rails 5 / ActiveRecord 5. Uses [ON CONFLICT
|
|
15
15
|
## Prerequisites
|
16
16
|
|
17
17
|
- PostgreSQL 9.5+ (that's when UPSERT support was added; see Wikipedia's [PostgreSQL Release History](https://en.wikipedia.org/wiki/PostgreSQL#Release_history))
|
18
|
-
- ActiveRecord
|
18
|
+
- ActiveRecord >= 5
|
19
19
|
- For MRI: pg
|
20
20
|
|
21
21
|
- For JRuby: No support
|
@@ -123,6 +123,37 @@ r = MyRecord.new(id: 1, name: 'bar')
|
|
123
123
|
r.upsert!
|
124
124
|
```
|
125
125
|
|
126
|
+
### Gotcha with database defaults
|
127
|
+
|
128
|
+
When a table is defined with a database default for a field, this gotcha can occur when trying to explicitly upsert a record _to_ the default value (from a non-default value).
|
129
|
+
|
130
|
+
**Example**: a table called `hardwares` has a `prio` column with a default value.
|
131
|
+
|
132
|
+
┌─────────┬─────────┬───────┬
|
133
|
+
│ Column │ Type │Default│
|
134
|
+
├─────────┼─────────┼───────┼
|
135
|
+
│ id │ integer │ ...
|
136
|
+
│ prio │ integer │ 999
|
137
|
+
|
138
|
+
And `hardwares` has a record with a non-default value for `prio`. Say, the record with `id` 1 has a `prio` of `998`.
|
139
|
+
|
140
|
+
In this situation, upserting like:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
hw = { id: 1, prio: 999 }
|
144
|
+
Hardware.new(prio: hw[:prio]).upsert
|
145
|
+
```
|
146
|
+
|
147
|
+
will not mention the `prio` column in the `ON CONFLICT` clause, resulting in no update.
|
148
|
+
|
149
|
+
However, upserting like so:
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
Hardware.upsert(prio: hw[:prio]).id
|
153
|
+
```
|
154
|
+
|
155
|
+
will indeed update the record in the database back to its default value, `999`.
|
156
|
+
|
126
157
|
### Conflict Clauses
|
127
158
|
|
128
159
|
It's possible to specify which columns should be used for the conflict clause. **These must comprise a unique index in Postgres.**
|
@@ -160,6 +191,24 @@ class Account < ApplicationRecord
|
|
160
191
|
end
|
161
192
|
```
|
162
193
|
|
194
|
+
Overriding the models' `upsert_keys` when calling `#upsert` or `.upsert`:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
Account.upsert(attrs, opts: { upsert_keys: [:foo, :bar] })
|
198
|
+
# Or, on an instance:
|
199
|
+
account = Account.new(attrs)
|
200
|
+
account.upsert(opts: { upsert_keys: [:foo, :bar] })
|
201
|
+
```
|
202
|
+
|
203
|
+
Overriding the models' `upsert_options` (partial index) when calling `#upsert` or `.upsert`:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
Account.upsert(attrs, opts: { upsert_options: { where: 'foo IS NOT NULL' } })
|
207
|
+
# Or, on an instance:
|
208
|
+
account = Account.new(attrs)
|
209
|
+
account.upsert(opts: { upsert_options: { where: 'foo IS NOT NULLL } })
|
210
|
+
```
|
211
|
+
|
163
212
|
## Tests
|
164
213
|
|
165
214
|
Make sure to have an upsert_test database:
|
@@ -190,3 +239,5 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jesjos
|
|
190
239
|
- Daniel Cooper ([@danielcooper](https://github.com/danielcooper))
|
191
240
|
- Laurent Vallar ([@val](https://github.com/val))
|
192
241
|
- Emmanuel Quentin ([@manuquentin](https://github.com/manuquentin))
|
242
|
+
- Jeff Wallace ([@tjwallace](https://github.com/tjwallace))
|
243
|
+
- Kirill Zaitsev ([@Bugagazavr](https://github.com/Bugagazavr))
|
@@ -21,6 +21,6 @@ Gem::Specification.new do |spec|
|
|
21
21
|
|
22
22
|
spec.platform = Gem::Platform::RUBY
|
23
23
|
|
24
|
-
spec.add_runtime_dependency 'activerecord', '>= 5.0', '< 6.
|
24
|
+
spec.add_runtime_dependency 'activerecord', '>= 5.0', '< 6.2'
|
25
25
|
spec.add_runtime_dependency 'pg', '>= 0.18', '< 2.0'
|
26
26
|
end
|
data/lib/active_record_upsert.rb
CHANGED
@@ -13,7 +13,7 @@ require 'active_record_upsert/active_record'
|
|
13
13
|
|
14
14
|
version = defined?(Rails) ? Rails.version : ActiveRecord.version.to_s
|
15
15
|
|
16
|
-
require 'active_record_upsert/compatibility/rails60.rb' if version >= '6.0.0' && version < '6.
|
16
|
+
require 'active_record_upsert/compatibility/rails60.rb' if version >= '6.0.0' && version < '6.2.0'
|
17
17
|
require 'active_record_upsert/compatibility/rails51.rb' if version >= '5.1.0' && version < '5.2.0'
|
18
18
|
require 'active_record_upsert/compatibility/rails50.rb' if version >= '5.0.0' && version < '5.1.0'
|
19
19
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module ActiveRecordUpsert
|
2
2
|
module ActiveRecord
|
3
3
|
module PersistenceExtensions
|
4
|
-
def upsert!(attributes: nil, arel_condition: nil, validate: true)
|
4
|
+
def upsert!(attributes: nil, arel_condition: nil, validate: true, opts: {})
|
5
5
|
raise ::ActiveRecord::ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
|
6
6
|
raise ::ActiveRecord::RecordSavedError, "Can't upsert a record that has already been saved" if persisted?
|
7
7
|
validate == false || perform_validations || raise_validation_error
|
@@ -11,7 +11,7 @@ module ActiveRecordUpsert
|
|
11
11
|
attributes = attributes +
|
12
12
|
timestamp_attributes_for_create_in_model +
|
13
13
|
timestamp_attributes_for_update_in_model
|
14
|
-
_upsert_record(attributes.map(&:to_s).uniq, arel_condition)
|
14
|
+
_upsert_record(attributes.map(&:to_s).uniq, arel_condition, opts)
|
15
15
|
}
|
16
16
|
}
|
17
17
|
|
@@ -24,10 +24,10 @@ module ActiveRecordUpsert
|
|
24
24
|
false
|
25
25
|
end
|
26
26
|
|
27
|
-
def _upsert_record(upsert_attribute_names = changed, arel_condition = nil)
|
27
|
+
def _upsert_record(upsert_attribute_names = changed, arel_condition = nil, opts = {})
|
28
28
|
existing_attribute_names = attributes_for_create(attributes.keys)
|
29
29
|
existing_attributes = attributes_with_values(existing_attribute_names)
|
30
|
-
values = self.class._upsert_record(existing_attributes, upsert_attribute_names, [arel_condition].compact)
|
30
|
+
values = self.class._upsert_record(existing_attributes, upsert_attribute_names, [arel_condition].compact, opts)
|
31
31
|
@attributes = self.class.attributes_builder.build_from_database(values.first.to_h)
|
32
32
|
@new_record = false
|
33
33
|
values
|
@@ -40,12 +40,12 @@ module ActiveRecordUpsert
|
|
40
40
|
end
|
41
41
|
|
42
42
|
module ClassMethods
|
43
|
-
def upsert!(attributes, arel_condition: nil, validate: true, &block)
|
43
|
+
def upsert!(attributes, arel_condition: nil, validate: true, opts: {}, &block)
|
44
44
|
if attributes.is_a?(Array)
|
45
45
|
attributes.collect { |hash| upsert(hash, &block) }
|
46
46
|
else
|
47
47
|
new(attributes, &block).upsert!(
|
48
|
-
attributes: attributes.keys, arel_condition: arel_condition, validate: validate
|
48
|
+
attributes: attributes.keys, arel_condition: arel_condition, validate: validate, opts: opts
|
49
49
|
)
|
50
50
|
end
|
51
51
|
end
|
@@ -56,8 +56,9 @@ module ActiveRecordUpsert
|
|
56
56
|
false
|
57
57
|
end
|
58
58
|
|
59
|
-
def _upsert_record(existing_attributes, upsert_attributes_names, wheres) # :nodoc:
|
60
|
-
upsert_keys = self.upsert_keys || [primary_key]
|
59
|
+
def _upsert_record(existing_attributes, upsert_attributes_names, wheres, opts) # :nodoc:
|
60
|
+
upsert_keys = opts[:upsert_keys] || self.upsert_keys || [primary_key]
|
61
|
+
upsert_options = opts[:upsert_options] || self.upsert_options
|
61
62
|
upsert_attributes_names = upsert_attributes_names - [*upsert_keys, 'created_at']
|
62
63
|
|
63
64
|
existing_attributes = existing_attributes
|
@@ -1,10 +1,10 @@
|
|
1
1
|
module ActiveRecordUpsert
|
2
2
|
module ActiveRecord
|
3
3
|
module PersistenceExtensions
|
4
|
-
def _upsert_record(upsert_attribute_names = changed, arel_condition = nil)
|
4
|
+
def _upsert_record(upsert_attribute_names = changed, arel_condition = nil, opts = {})
|
5
5
|
upsert_attribute_names = upsert_attribute_names.map { |name| _prepare_column(name) } & self.class.column_names
|
6
6
|
existing_attributes = arel_attributes_with_values_for_create(self.class.column_names)
|
7
|
-
values = self.class.unscoped.upsert(existing_attributes, upsert_attribute_names, [arel_condition].compact)
|
7
|
+
values = self.class.unscoped.upsert(existing_attributes, upsert_attribute_names, [arel_condition].compact, opts)
|
8
8
|
@new_record = false
|
9
9
|
@attributes = self.class.attributes_builder.build_from_database(values.first.to_h)
|
10
10
|
values
|
@@ -20,9 +20,10 @@ module ActiveRecordUpsert
|
|
20
20
|
end
|
21
21
|
|
22
22
|
module RelationExtensions
|
23
|
-
def upsert(existing_attributes, upsert_attributes, wheres) # :nodoc:
|
23
|
+
def upsert(existing_attributes, upsert_attributes, wheres, opts) # :nodoc:
|
24
24
|
substitutes, binds = substitute_values(existing_attributes)
|
25
|
-
upsert_keys = self.klass.upsert_keys || [primary_key]
|
25
|
+
upsert_keys = opts[:upsert_keys] || self.klass.upsert_keys || [primary_key]
|
26
|
+
upsert_options = opts[:upsert_options] || self.klass.upsert_options
|
26
27
|
|
27
28
|
upsert_attributes = upsert_attributes - [*upsert_keys, 'created_at']
|
28
29
|
upsert_keys_filter = ->(o) { upsert_attributes.include?(o.name) }
|
@@ -34,7 +35,7 @@ module ActiveRecordUpsert
|
|
34
35
|
|
35
36
|
on_conflict_do_update = ::Arel::OnConflictDoUpdateManager.new
|
36
37
|
on_conflict_do_update.target = target
|
37
|
-
on_conflict_do_update.target_condition =
|
38
|
+
on_conflict_do_update.target_condition = upsert_options[:where]
|
38
39
|
on_conflict_do_update.wheres = wheres
|
39
40
|
on_conflict_do_update.set(vals_for_upsert)
|
40
41
|
|
@@ -43,7 +44,7 @@ module ActiveRecordUpsert
|
|
43
44
|
insert_manager.on_conflict = on_conflict_do_update.to_node
|
44
45
|
insert_manager.insert substitutes
|
45
46
|
|
46
|
-
@klass.connection.upsert(insert_manager, "#{
|
47
|
+
@klass.connection.upsert(insert_manager, "#{@klass.name} Upsert", binds + on_conflict_binds)
|
47
48
|
end
|
48
49
|
|
49
50
|
::ActiveRecord::Relation.include(self)
|
@@ -5,5 +5,14 @@ module ActiveRecordUpsert
|
|
5
5
|
with_transaction_returning_status { super }
|
6
6
|
end
|
7
7
|
end
|
8
|
+
|
9
|
+
module ConnectAdapterExtension
|
10
|
+
def upsert(*args)
|
11
|
+
::ActiveRecord::Base.clear_query_caches_for_current_thread
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(self)
|
16
|
+
end
|
8
17
|
end
|
9
18
|
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_upsert
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jesper Josefsson
|
8
8
|
- Olle Jonsson
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2020-12-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -20,7 +20,7 @@ dependencies:
|
|
20
20
|
version: '5.0'
|
21
21
|
- - "<"
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: '6.
|
23
|
+
version: '6.2'
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
26
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -30,7 +30,7 @@ dependencies:
|
|
30
30
|
version: '5.0'
|
31
31
|
- - "<"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '6.
|
33
|
+
version: '6.2'
|
34
34
|
- !ruby/object:Gem::Dependency
|
35
35
|
name: pg
|
36
36
|
requirement: !ruby/object:Gem::Requirement
|
@@ -51,7 +51,7 @@ dependencies:
|
|
51
51
|
- - "<"
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: '2.0'
|
54
|
-
description:
|
54
|
+
description:
|
55
55
|
email:
|
56
56
|
- jesper.josefsson@gmail.com
|
57
57
|
- olle.jonsson@gmail.com
|
@@ -64,6 +64,7 @@ files:
|
|
64
64
|
- Gemfile.rails-5-1
|
65
65
|
- Gemfile.rails-5-2
|
66
66
|
- Gemfile.rails-6-0
|
67
|
+
- Gemfile.rails-6-1
|
67
68
|
- Gemfile.rails-master
|
68
69
|
- LICENSE
|
69
70
|
- README.md
|
@@ -96,7 +97,7 @@ homepage: https://github.com/jesjos/active_record_upsert/
|
|
96
97
|
licenses:
|
97
98
|
- MIT
|
98
99
|
metadata: {}
|
99
|
-
post_install_message:
|
100
|
+
post_install_message:
|
100
101
|
rdoc_options: []
|
101
102
|
require_paths:
|
102
103
|
- lib
|
@@ -111,9 +112,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
112
|
- !ruby/object:Gem::Version
|
112
113
|
version: '0'
|
113
114
|
requirements: []
|
114
|
-
|
115
|
-
|
116
|
-
signing_key:
|
115
|
+
rubygems_version: 3.2.1
|
116
|
+
signing_key:
|
117
117
|
specification_version: 4
|
118
118
|
summary: Real PostgreSQL 9.5+ upserts using ON CONFLICT for ActiveRecord
|
119
119
|
test_files: []
|