active_record_upsert 0.9.5 → 0.11.1
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-0 +1 -1
- data/{Gemfile.rails-5-0 → Gemfile.rails-6-1} +1 -1
- data/{Gemfile.rails-5-1 → Gemfile.rails-7-0} +1 -1
- data/README.md +71 -5
- data/active_record_upsert.gemspec +3 -3
- data/lib/active_record_upsert/active_record/persistence.rb +14 -12
- data/lib/active_record_upsert/arel/nodes/insert_statement.rb +0 -39
- data/lib/active_record_upsert/arel/table_extensions.rb +23 -0
- data/lib/active_record_upsert/compatibility/rails60.rb +10 -1
- data/lib/active_record_upsert/compatibility/rails70.rb +59 -0
- data/lib/active_record_upsert/version.rb +1 -1
- data/lib/active_record_upsert.rb +2 -3
- metadata +15 -17
- data/lib/active_record_upsert/arel/crud.rb +0 -31
- data/lib/active_record_upsert/compatibility/rails50.rb +0 -1
- data/lib/active_record_upsert/compatibility/rails51.rb +0 -63
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0030a0300c6faa41418611278c7c703238fa18858baaaf3650f043516610a708
|
4
|
+
data.tar.gz: 8f752ca3fca4a6b23a8acf775b49bb0dd3d5f6927ff0b4f75a014512fd15d2c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82072e1c410d53bb33125e45ceea69bbe888e365e4187d2c896287aa5b2acec2671160c9b6c5a0003163fccbbe6d6625285619fcb580d2000d355146cdc4dc2e
|
7
|
+
data.tar.gz: 16d62a9697e81fd6aa64b1bb8eee2da3c95b2bbb69c25dec548d40e7bcdaa96b4e017bd605c8c65ecb4f92e707a2deaf04627ae136e10ab04be4430d8764712b
|
data/Gemfile.base
CHANGED
data/Gemfile.rails-6-0
CHANGED
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. Uses [ON CONFLICT DO UPDATE](http://www.postgresql.org/docs/9.5/static/sql-insert.html).
|
7
|
+
Real upsert for PostgreSQL 9.5+ and Rails 5.2+ / ActiveRecord 5.2+. 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,16 +15,29 @@ 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
|
19
|
-
-
|
20
|
-
|
21
|
-
- For JRuby: No support
|
18
|
+
- ActiveRecord >= 5.2
|
19
|
+
- Ruby MRI, with the `pg` gem
|
20
|
+
- _JRuby is currently not supported_
|
22
21
|
|
23
22
|
### NB: Releases to avoid
|
24
23
|
|
25
24
|
Due to a broken build matrix, v0.9.2 and v0.9.3 are incompatible with Rails
|
26
25
|
< 5.2.1. [v0.9.4](https://github.com/jesjos/active_record_upsert/releases/tag/v0.9.4) fixed this issue.
|
27
26
|
|
27
|
+
### Supported Rails versions
|
28
|
+
|
29
|
+
This library is compatible with all major Rails versions covered by the Rails
|
30
|
+
["Severe Security Issues" maintenance policy](https://guides.rubyonrails.org/maintenance_policy.html).
|
31
|
+
|
32
|
+
### Supported Ruby versions
|
33
|
+
|
34
|
+
This library may be compatible with older versions of Ruby, however we only run automated
|
35
|
+
tests using the
|
36
|
+
[officially supported Ruby versions](https://www.ruby-lang.org/en/downloads/branches/).
|
37
|
+
|
38
|
+
Please note that Ruby 3.1 is not currently tested because it is incompatible with Rails
|
39
|
+
`7.0.0`, `6.1.4.4`, `6.0.4.4` and `5.2.6` ([issue](https://github.com/rails/rails/issues/43998)).
|
40
|
+
|
28
41
|
## Installation
|
29
42
|
|
30
43
|
Add this line to your application's Gemfile:
|
@@ -123,6 +136,37 @@ r = MyRecord.new(id: 1, name: 'bar')
|
|
123
136
|
r.upsert!
|
124
137
|
```
|
125
138
|
|
139
|
+
### Gotcha with database defaults
|
140
|
+
|
141
|
+
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).
|
142
|
+
|
143
|
+
**Example**: a table called `hardwares` has a `prio` column with a default value.
|
144
|
+
|
145
|
+
┌─────────┬─────────┬───────┬
|
146
|
+
│ Column │ Type │Default│
|
147
|
+
├─────────┼─────────┼───────┼
|
148
|
+
│ id │ integer │ ...
|
149
|
+
│ prio │ integer │ 999
|
150
|
+
|
151
|
+
And `hardwares` has a record with a non-default value for `prio`. Say, the record with `id` 1 has a `prio` of `998`.
|
152
|
+
|
153
|
+
In this situation, upserting like:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
hw = { id: 1, prio: 999 }
|
157
|
+
Hardware.new(prio: hw[:prio]).upsert
|
158
|
+
```
|
159
|
+
|
160
|
+
will not mention the `prio` column in the `ON CONFLICT` clause, resulting in no update.
|
161
|
+
|
162
|
+
However, upserting like so:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
Hardware.upsert(prio: hw[:prio]).id
|
166
|
+
```
|
167
|
+
|
168
|
+
will indeed update the record in the database back to its default value, `999`.
|
169
|
+
|
126
170
|
### Conflict Clauses
|
127
171
|
|
128
172
|
It's possible to specify which columns should be used for the conflict clause. **These must comprise a unique index in Postgres.**
|
@@ -160,6 +204,24 @@ class Account < ApplicationRecord
|
|
160
204
|
end
|
161
205
|
```
|
162
206
|
|
207
|
+
Overriding the models' `upsert_keys` when calling `#upsert` or `.upsert`:
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
Account.upsert(attrs, opts: { upsert_keys: [:foo, :bar] })
|
211
|
+
# Or, on an instance:
|
212
|
+
account = Account.new(attrs)
|
213
|
+
account.upsert(opts: { upsert_keys: [:foo, :bar] })
|
214
|
+
```
|
215
|
+
|
216
|
+
Overriding the models' `upsert_options` (partial index) when calling `#upsert` or `.upsert`:
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
Account.upsert(attrs, opts: { upsert_options: { where: 'foo IS NOT NULL' } })
|
220
|
+
# Or, on an instance:
|
221
|
+
account = Account.new(attrs)
|
222
|
+
account.upsert(opts: { upsert_options: { where: 'foo IS NOT NULLL } })
|
223
|
+
```
|
224
|
+
|
163
225
|
## Tests
|
164
226
|
|
165
227
|
Make sure to have an upsert_test database:
|
@@ -190,3 +252,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jesjos
|
|
190
252
|
- Daniel Cooper ([@danielcooper](https://github.com/danielcooper))
|
191
253
|
- Laurent Vallar ([@val](https://github.com/val))
|
192
254
|
- Emmanuel Quentin ([@manuquentin](https://github.com/manuquentin))
|
255
|
+
- Jeff Wallace ([@tjwallace](https://github.com/tjwallace))
|
256
|
+
- Kirill Zaitsev ([@Bugagazavr](https://github.com/Bugagazavr))
|
257
|
+
- Nick Campbell ([@nickcampbell18](https://github.com/nickcampbell18))
|
258
|
+
- Mikhail Doronin ([@misdoro](https://github.com/misdoro))
|
@@ -13,14 +13,14 @@ Gem::Specification.new do |spec|
|
|
13
13
|
|
14
14
|
spec.summary = %q{Real PostgreSQL 9.5+ upserts using ON CONFLICT for ActiveRecord}
|
15
15
|
|
16
|
-
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(bin|test|spec|features)/}) } -
|
17
|
-
%w[.gitignore .rspec
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(.github|bin|test|spec|features)/}) } -
|
17
|
+
%w[.gitignore .rspec Dockerfile Gemfile Gemfile.docker docker-compose.yml]
|
18
18
|
spec.bindir = "exe"
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
22
|
spec.platform = Gem::Platform::RUBY
|
23
23
|
|
24
|
-
spec.add_runtime_dependency 'activerecord', '>= 5.
|
24
|
+
spec.add_runtime_dependency 'activerecord', '>= 5.2', '< 7.1'
|
25
25
|
spec.add_runtime_dependency 'pg', '>= 0.18', '< 2.0'
|
26
26
|
end
|
@@ -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,25 +11,26 @@ 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
|
|
18
18
|
self
|
19
19
|
end
|
20
20
|
|
21
|
-
def upsert(
|
22
|
-
upsert!(
|
21
|
+
def upsert(**kwargs)
|
22
|
+
upsert!(**kwargs)
|
23
23
|
rescue ::ActiveRecord::RecordInvalid
|
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
|
+
changes_applied
|
33
34
|
values
|
34
35
|
end
|
35
36
|
|
@@ -40,24 +41,25 @@ module ActiveRecordUpsert
|
|
40
41
|
end
|
41
42
|
|
42
43
|
module ClassMethods
|
43
|
-
def upsert!(attributes, arel_condition: nil, validate: true, &block)
|
44
|
+
def upsert!(attributes, arel_condition: nil, validate: true, opts: {}, &block)
|
44
45
|
if attributes.is_a?(Array)
|
45
46
|
attributes.collect { |hash| upsert(hash, &block) }
|
46
47
|
else
|
47
48
|
new(attributes, &block).upsert!(
|
48
|
-
attributes: attributes.keys, arel_condition: arel_condition, validate: validate
|
49
|
+
attributes: attributes.keys, arel_condition: arel_condition, validate: validate, opts: opts
|
49
50
|
)
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
53
|
-
def upsert(
|
54
|
-
upsert!(
|
54
|
+
def upsert(attributes, **kwargs, &block)
|
55
|
+
upsert!(attributes, **kwargs, &block)
|
55
56
|
rescue ::ActiveRecord::RecordInvalid
|
56
57
|
false
|
57
58
|
end
|
58
59
|
|
59
|
-
def _upsert_record(existing_attributes, upsert_attributes_names, wheres) # :nodoc:
|
60
|
-
upsert_keys = self.upsert_keys || [primary_key]
|
60
|
+
def _upsert_record(existing_attributes, upsert_attributes_names, wheres, opts) # :nodoc:
|
61
|
+
upsert_keys = opts[:upsert_keys] || self.upsert_keys || [primary_key]
|
62
|
+
upsert_options = opts[:upsert_options] || self.upsert_options
|
61
63
|
upsert_attributes_names = upsert_attributes_names - [*upsert_keys, 'created_at']
|
62
64
|
|
63
65
|
existing_attributes = existing_attributes
|
@@ -1,47 +1,8 @@
|
|
1
|
-
# module ActiveRecordUpsert
|
2
|
-
# module Arel
|
3
|
-
# module Nodes
|
4
|
-
# module InsertStatementExtensions
|
5
|
-
# attr_accessor :on_conflict
|
6
|
-
#
|
7
|
-
# def initialize
|
8
|
-
# @on_conflict = nil
|
9
|
-
# super()
|
10
|
-
# end
|
11
|
-
#
|
12
|
-
# def hash
|
13
|
-
# [@relation, @columns, @values, @select, @on_conflict].hash
|
14
|
-
# end
|
15
|
-
#
|
16
|
-
# def eql? other
|
17
|
-
# self.class == other.class &&
|
18
|
-
# self.relation == other.relation &&
|
19
|
-
# self.columns == other.columns &&
|
20
|
-
# self.select == other.select &&
|
21
|
-
# self.values == other.values &&
|
22
|
-
# self.on_conflict == other.on_conflict
|
23
|
-
# end
|
24
|
-
# end
|
25
|
-
#
|
26
|
-
# ::Arel::Nodes::InsertStatement.prepend(InsertStatementExtensions)
|
27
|
-
# end
|
28
|
-
# end
|
29
|
-
# end
|
30
|
-
|
31
1
|
module Arel
|
32
2
|
module Nodes
|
33
3
|
class InsertStatement
|
34
4
|
attr_accessor :on_conflict
|
35
5
|
|
36
|
-
def initialize
|
37
|
-
super()
|
38
|
-
@relation = nil
|
39
|
-
@columns = []
|
40
|
-
@values = nil
|
41
|
-
@select = nil
|
42
|
-
@on_conflict = nil
|
43
|
-
end
|
44
|
-
|
45
6
|
def hash
|
46
7
|
[@relation, @columns, @values, @select, @on_conflict].hash
|
47
8
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveRecordUpsert
|
2
|
+
module Arel
|
3
|
+
module TableExtensions
|
4
|
+
def compile_upsert(upsert_keys, upsert_options, upsert_values, insert_values, wheres)
|
5
|
+
# Support non-attribute key (like `md5(my_attribute)``)
|
6
|
+
target = self[upsert_options.key?(:literal) ? ::Arel::Nodes::SqlLiteral.new(upsert_options[:literal]) : upsert_keys.join(',')]
|
7
|
+
on_conflict_do_update = ::Arel::OnConflictDoUpdateManager.new
|
8
|
+
|
9
|
+
on_conflict_do_update.target = target
|
10
|
+
on_conflict_do_update.target_condition = upsert_options[:where]
|
11
|
+
on_conflict_do_update.wheres = wheres
|
12
|
+
on_conflict_do_update.set(upsert_values)
|
13
|
+
|
14
|
+
insert_manager = ::Arel::InsertManager.new
|
15
|
+
insert_manager.on_conflict = on_conflict_do_update.to_node
|
16
|
+
insert_manager.into insert_values.first.first.relation
|
17
|
+
insert_manager.insert(insert_values)
|
18
|
+
insert_manager
|
19
|
+
end
|
20
|
+
end
|
21
|
+
::Arel::Table.prepend(TableExtensions)
|
22
|
+
end
|
23
|
+
end
|
@@ -1,9 +1,18 @@
|
|
1
1
|
module ActiveRecordUpsert
|
2
2
|
module ActiveRecord
|
3
3
|
module TransactionsExtensions
|
4
|
-
def upsert(*args)
|
4
|
+
def upsert(*args, **kwargs)
|
5
5
|
with_transaction_returning_status { super }
|
6
6
|
end
|
7
7
|
end
|
8
|
+
|
9
|
+
module ConnectAdapterExtension
|
10
|
+
def upsert(*args, **kwargs)
|
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
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module ActiveRecordUpsert
|
2
|
+
module ActiveRecord
|
3
|
+
module PersistenceExtensions
|
4
|
+
module ClassMethods
|
5
|
+
def __substitute_values(values, table)
|
6
|
+
values.map do |name, value|
|
7
|
+
attr = table[name]
|
8
|
+
unless ::Arel.arel_node?(value) || value.is_a?(::ActiveModel::Attribute)
|
9
|
+
type = type_for_attribute(attr.name)
|
10
|
+
value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
|
11
|
+
end
|
12
|
+
[attr, value]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def _upsert_record(existing_attributes, upsert_attributes_names, wheres, opts) # :nodoc:
|
17
|
+
upsert_keys = opts[:upsert_keys] || self.upsert_keys || [primary_key]
|
18
|
+
upsert_options = opts[:upsert_options] || self.upsert_options
|
19
|
+
upsert_attributes_names = upsert_attributes_names - [*upsert_keys, 'created_at']
|
20
|
+
|
21
|
+
existing_attributes = existing_attributes
|
22
|
+
.transform_keys { |name| _prepare_column(name) }
|
23
|
+
.reject { |key, _| key.nil? }
|
24
|
+
|
25
|
+
upsert_attributes_names = upsert_attributes_names
|
26
|
+
.map { |name| _prepare_column(name) }
|
27
|
+
.compact
|
28
|
+
|
29
|
+
values_for_upsert = existing_attributes.select { |(name, _value)| upsert_attributes_names.include?(name) }
|
30
|
+
|
31
|
+
insert_manager = arel_table.compile_upsert(
|
32
|
+
upsert_keys,
|
33
|
+
upsert_options,
|
34
|
+
__substitute_values(values_for_upsert, arel_table),
|
35
|
+
__substitute_values(existing_attributes, arel_table),
|
36
|
+
wheres
|
37
|
+
)
|
38
|
+
|
39
|
+
connection.upsert(insert_manager, "#{self} Upsert")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module TransactionsExtensions
|
45
|
+
def upsert(*args, **kwargs)
|
46
|
+
with_transaction_returning_status { super }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module ConnectAdapterExtension
|
51
|
+
def upsert(*args, **kwargs)
|
52
|
+
::ActiveRecord::Base.clear_query_caches_for_current_thread
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(self)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/active_record_upsert.rb
CHANGED
@@ -13,9 +13,8 @@ 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/
|
17
|
-
require 'active_record_upsert/compatibility/
|
18
|
-
require 'active_record_upsert/compatibility/rails50.rb' if version >= '5.0.0' && version < '5.1.0'
|
16
|
+
require 'active_record_upsert/compatibility/rails70.rb' if version >= '7.0.0' && version < '7.2.0'
|
17
|
+
require 'active_record_upsert/compatibility/rails60.rb' if version >= '6.0.0' && version < '6.2.0'
|
19
18
|
|
20
19
|
module ActiveRecordUpsert
|
21
20
|
# Your code goes here...
|
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.11.1
|
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: 2022-01-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -17,20 +17,20 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '5.
|
20
|
+
version: '5.2'
|
21
21
|
- - "<"
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: '
|
23
|
+
version: '7.1'
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
26
|
version_requirements: !ruby/object:Gem::Requirement
|
27
27
|
requirements:
|
28
28
|
- - ">="
|
29
29
|
- !ruby/object:Gem::Version
|
30
|
-
version: '5.
|
30
|
+
version: '5.2'
|
31
31
|
- - "<"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '7.1'
|
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
|
@@ -60,10 +60,10 @@ extensions: []
|
|
60
60
|
extra_rdoc_files: []
|
61
61
|
files:
|
62
62
|
- Gemfile.base
|
63
|
-
- Gemfile.rails-5-0
|
64
|
-
- Gemfile.rails-5-1
|
65
63
|
- Gemfile.rails-5-2
|
66
64
|
- Gemfile.rails-6-0
|
65
|
+
- Gemfile.rails-6-1
|
66
|
+
- Gemfile.rails-7-0
|
67
67
|
- Gemfile.rails-master
|
68
68
|
- LICENSE
|
69
69
|
- README.md
|
@@ -77,7 +77,6 @@ files:
|
|
77
77
|
- lib/active_record_upsert/active_record/timestamp.rb
|
78
78
|
- lib/active_record_upsert/active_record/transactions.rb
|
79
79
|
- lib/active_record_upsert/arel.rb
|
80
|
-
- lib/active_record_upsert/arel/crud.rb
|
81
80
|
- lib/active_record_upsert/arel/insert_manager.rb
|
82
81
|
- lib/active_record_upsert/arel/nodes.rb
|
83
82
|
- lib/active_record_upsert/arel/nodes/do_nothing.rb
|
@@ -87,16 +86,16 @@ files:
|
|
87
86
|
- lib/active_record_upsert/arel/nodes/on_conflict.rb
|
88
87
|
- lib/active_record_upsert/arel/nodes/on_conflict_action.rb
|
89
88
|
- lib/active_record_upsert/arel/on_conflict_do_update_manager.rb
|
89
|
+
- lib/active_record_upsert/arel/table_extensions.rb
|
90
90
|
- lib/active_record_upsert/arel/visitors/to_sql.rb
|
91
|
-
- lib/active_record_upsert/compatibility/rails50.rb
|
92
|
-
- lib/active_record_upsert/compatibility/rails51.rb
|
93
91
|
- lib/active_record_upsert/compatibility/rails60.rb
|
92
|
+
- lib/active_record_upsert/compatibility/rails70.rb
|
94
93
|
- lib/active_record_upsert/version.rb
|
95
94
|
homepage: https://github.com/jesjos/active_record_upsert/
|
96
95
|
licenses:
|
97
96
|
- MIT
|
98
97
|
metadata: {}
|
99
|
-
post_install_message:
|
98
|
+
post_install_message:
|
100
99
|
rdoc_options: []
|
101
100
|
require_paths:
|
102
101
|
- lib
|
@@ -111,9 +110,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
110
|
- !ruby/object:Gem::Version
|
112
111
|
version: '0'
|
113
112
|
requirements: []
|
114
|
-
|
115
|
-
|
116
|
-
signing_key:
|
113
|
+
rubygems_version: 3.2.7
|
114
|
+
signing_key:
|
117
115
|
specification_version: 4
|
118
116
|
summary: Real PostgreSQL 9.5+ upserts using ON CONFLICT for ActiveRecord
|
119
117
|
test_files: []
|
@@ -1,31 +0,0 @@
|
|
1
|
-
# module ActiveRecordUpsert
|
2
|
-
# module Arel
|
3
|
-
# module CrudExtensions
|
4
|
-
# def create_on_conflict_do_update
|
5
|
-
# OnConflictDoUpdateManager.new
|
6
|
-
# end
|
7
|
-
# end
|
8
|
-
#
|
9
|
-
# ::Arel::Crud.prepend(CrudExtensions)
|
10
|
-
# end
|
11
|
-
# end
|
12
|
-
module Arel
|
13
|
-
module Crud
|
14
|
-
def compile_upsert(upsert_keys, upsert_options, upsert_values, insert_values, wheres)
|
15
|
-
# Support non-attribute key (like `md5(my_attribute)``)
|
16
|
-
target = self[upsert_options.key?(:literal) ? ::Arel::Nodes::SqlLiteral.new(upsert_options[:literal]) : upsert_keys.join(',')]
|
17
|
-
on_conflict_do_update = OnConflictDoUpdateManager.new
|
18
|
-
|
19
|
-
on_conflict_do_update.target = target
|
20
|
-
on_conflict_do_update.target_condition = upsert_options[:where]
|
21
|
-
on_conflict_do_update.wheres = wheres
|
22
|
-
on_conflict_do_update.set(upsert_values)
|
23
|
-
|
24
|
-
insert_manager = create_insert
|
25
|
-
insert_manager.on_conflict = on_conflict_do_update.to_node
|
26
|
-
insert_manager.into insert_values.first.first.relation
|
27
|
-
insert_manager.insert(insert_values)
|
28
|
-
insert_manager
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1 +0,0 @@
|
|
1
|
-
require_relative 'rails51'
|
@@ -1,63 +0,0 @@
|
|
1
|
-
module ActiveRecordUpsert
|
2
|
-
module ActiveRecord
|
3
|
-
module PersistenceExtensions
|
4
|
-
def _upsert_record(upsert_attribute_names = changed, arel_condition = nil)
|
5
|
-
upsert_attribute_names = upsert_attribute_names.map { |name| _prepare_column(name) } & self.class.column_names
|
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)
|
8
|
-
@new_record = false
|
9
|
-
@attributes = self.class.attributes_builder.build_from_database(values.first.to_h)
|
10
|
-
values
|
11
|
-
end
|
12
|
-
|
13
|
-
def _prepare_column(name)
|
14
|
-
if self.class.reflections.key?(name)
|
15
|
-
self.class.reflections[name].foreign_key
|
16
|
-
else
|
17
|
-
name
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
module RelationExtensions
|
23
|
-
def upsert(existing_attributes, upsert_attributes, wheres) # :nodoc:
|
24
|
-
substitutes, binds = substitute_values(existing_attributes)
|
25
|
-
upsert_keys = self.klass.upsert_keys || [primary_key]
|
26
|
-
|
27
|
-
upsert_attributes = upsert_attributes - [*upsert_keys, 'created_at']
|
28
|
-
upsert_keys_filter = ->(o) { upsert_attributes.include?(o.name) }
|
29
|
-
|
30
|
-
on_conflict_binds = binds.select(&upsert_keys_filter)
|
31
|
-
vals_for_upsert = substitutes.select { |s| upsert_keys_filter.call(s.first) }
|
32
|
-
|
33
|
-
target = arel_table[upsert_options.key?(:literal) ? ::Arel::Nodes::SqlLiteral.new(upsert_options[:literal]) : upsert_keys.join(',')]
|
34
|
-
|
35
|
-
on_conflict_do_update = ::Arel::OnConflictDoUpdateManager.new
|
36
|
-
on_conflict_do_update.target = target
|
37
|
-
on_conflict_do_update.target_condition = self.klass.upsert_options[:where]
|
38
|
-
on_conflict_do_update.wheres = wheres
|
39
|
-
on_conflict_do_update.set(vals_for_upsert)
|
40
|
-
|
41
|
-
insert_manager = arel_table.create_insert
|
42
|
-
insert_manager.into arel_table
|
43
|
-
insert_manager.on_conflict = on_conflict_do_update.to_node
|
44
|
-
insert_manager.insert substitutes
|
45
|
-
|
46
|
-
@klass.connection.upsert(insert_manager, "#{self} Upsert", binds + on_conflict_binds)
|
47
|
-
end
|
48
|
-
|
49
|
-
::ActiveRecord::Relation.include(self)
|
50
|
-
end
|
51
|
-
|
52
|
-
module ConnectionAdapters
|
53
|
-
module Postgresql
|
54
|
-
module DatabaseStatementsExtensions
|
55
|
-
def upsert(arel, name = nil, binds = [])
|
56
|
-
sql = to_sql(arel, binds)
|
57
|
-
exec_upsert(sql, name, binds)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|