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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 70b72c5ef238f1ffe3f7cfd5e2cc53f3b4c9e31f
4
- data.tar.gz: '09bfa1ad9bf6e4ea635371382d1b8e02f5fe8fb0'
2
+ SHA256:
3
+ metadata.gz: 0030a0300c6faa41418611278c7c703238fa18858baaaf3650f043516610a708
4
+ data.tar.gz: 8f752ca3fca4a6b23a8acf775b49bb0dd3d5f6927ff0b4f75a014512fd15d2c3
5
5
  SHA512:
6
- metadata.gz: 2e75ee846435aeb19b077eb34d844345d86d04374b9bdfb09d5311b3fea00c029dc8acd590b61541186d38efa907166df01faaeffcf9bae022e6116ef3baa405
7
- data.tar.gz: 2b2b56642038a8c350bfd6d7cc8a6d5bd1349e353b69b206509ffe68b1e716b95e23e2c71a4c5f2ccdda13b541f1152d69167da1bd6ba07f9fd054daee936430
6
+ metadata.gz: 82072e1c410d53bb33125e45ceea69bbe888e365e4187d2c896287aa5b2acec2671160c9b6c5a0003163fccbbe6d6625285619fcb580d2000d355146cdc4dc2e
7
+ data.tar.gz: 16d62a9697e81fd6aa64b1bb8eee2da3c95b2bbb69c25dec548d40e7bcdaa96b4e017bd605c8c65ecb4f92e707a2deaf04627ae136e10ab04be4430d8764712b
data/Gemfile.base CHANGED
@@ -5,7 +5,7 @@ gemspec
5
5
  group :development, :test do
6
6
  gem 'bundler', '>= 1.13'
7
7
  gem 'database_cleaner', '~> 1.6'
8
- gem 'pg', '~> 0.18'
8
+ gem 'pg', '~> 1.1'
9
9
  gem 'pry', '> 0'
10
10
  gem 'rake', '>= 10.0'
11
11
  gem 'rspec', '>= 3.0', '< 4'
data/Gemfile.rails-6-0 CHANGED
@@ -1,5 +1,5 @@
1
1
  group :development, :test do
2
- gem 'rails', '>= 6.0.0.rc1', '< 6.1'
2
+ gem 'rails', '~> 6.0'
3
3
  end
4
4
 
5
5
  eval_gemfile "#{__dir__}/Gemfile.base"
@@ -1,5 +1,5 @@
1
1
  group :development, :test do
2
- gem 'rails', '>= 5.0', '< 5.1'
2
+ gem 'rails', '~> 6.1'
3
3
  end
4
4
 
5
5
  eval_gemfile "#{__dir__}/Gemfile.base"
@@ -1,5 +1,5 @@
1
1
  group :development, :test do
2
- gem 'rails', '>= 5.1', '< 5.2'
2
+ gem 'rails', '~> 7.0'
3
3
  end
4
4
 
5
5
  eval_gemfile "#{__dir__}/Gemfile.base"
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 ~> 5
19
- - For MRI: pg
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 .travis.yml Dockerfile Gemfile Gemfile.docker docker-compose.yml]
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.0', '< 6.1'
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(*args)
22
- upsert!(*args)
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(*args)
54
- upsert!(*args)
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
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordUpsert
2
- VERSION = "0.9.5"
2
+ VERSION = "0.11.1"
3
3
  end
@@ -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/rails60.rb' if version >= '6.0.0' && version < '6.1.0'
17
- require 'active_record_upsert/compatibility/rails51.rb' if version >= '5.1.0' && version < '5.2.0'
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.9.5
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: 2019-04-26 00:00:00.000000000 Z
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.0'
20
+ version: '5.2'
21
21
  - - "<"
22
22
  - !ruby/object:Gem::Version
23
- version: '6.1'
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.0'
30
+ version: '5.2'
31
31
  - - "<"
32
32
  - !ruby/object:Gem::Version
33
- version: '6.1'
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
- rubyforge_project:
115
- rubygems_version: 2.6.14.4
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