active_record_upsert 0.9.2 → 0.10.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
2
  SHA256:
3
- metadata.gz: 98aadc2aa0c0144f2e76c0ccfffa7102a93e3ef1ea1b073df4bc89f1f162f9cb
4
- data.tar.gz: 9066f7f3cc3b0eedc43778de03ab2d9599d645992f4466f8aa987dfff3eaeb06
3
+ metadata.gz: e985d64fa74dedda4c75a2d8fb61f91e7ca89eca2d4c6e6c1bc62f9bf8c0499c
4
+ data.tar.gz: 3acce358c953805747e39c96d1028ed92715c38ac90a07f744eb8e12f76c278a
5
5
  SHA512:
6
- metadata.gz: eec007a7ae73f4ccb52fda438a9c73182b2f87f8154a5bdfebd1ba49f7a4ea67289e224eafa9bfa1d571a028f37ddc12d8b795c1ef75772a8bde2c70369e11b7
7
- data.tar.gz: e6d6f827e67c2c77c22eba302a9be7d3bf4fc435320e0725aa501c618f5ead4d592640f958bf316a866861549adc2bd293daeee816e448d954cc8a66c28fb9f6
6
+ metadata.gz: aea0bae90f0a51b27a1b3b8e24ad7bdf59c26a80433f4997892f7f5b1d1ea616f1b70731e56f052b7c9d7241b397f0613a6e2e8d0854021ba97c5ea623ab37af
7
+ data.tar.gz: 0c9ca9e4d570cbb4741cdd34feb71bc4b236a09c263dfc4c65a0d6092a63ec1c2a776a49cb4e64386f03e7d46502b6140ab3792b5b755c2a1e809a94cc866114
@@ -0,0 +1,51 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [$default-branches]
6
+ push:
7
+ branches: [$default-branch]
8
+
9
+ jobs:
10
+ test:
11
+ name: Test
12
+ runs-on: ubuntu-latest
13
+ services:
14
+ postgres:
15
+ images: postgres
16
+ env:
17
+ POSTGRES_DB: upsert_test
18
+ POSTGRES_PASSWORD: postgres
19
+ POSTGRES_USER: postgres
20
+ options: >-
21
+ --health-cmd pg_isready
22
+ --health-interval 10s
23
+ --health-timeout 5s
24
+ --health-retries 5
25
+ ports:
26
+ - 5432:5432
27
+
28
+ strategy:
29
+ fail-fast: false
30
+ matrix:
31
+ ruby-version: [2.5.x, 2.6.x, 2.7.x]
32
+ gemfile: [Gemfile, Gemfile.rails-master, Gemfile.rails-6-1, Gemfile.rails-5-2, Gemfile.rails-5-1, Gemfile.rails-5-0]
33
+
34
+ steps:
35
+ - uses: actions/checkout@v2
36
+
37
+ - name: Prepare database
38
+ run: |
39
+ createdb --echo -U $POSTGRES_USER $POSTGRES_DB
40
+ psql -U $POSTGRES_USER $POSTGRES_DB < spec/dummy/db/structure.sql
41
+
42
+ - name: Set up Ruby
43
+ uses: ruby/setup-ruby@v1
44
+ with:
45
+ ruby-version: ${{ matrix.ruby-version }}
46
+ bundler-cache: true
47
+ env:
48
+ BUNDLE_GEMFILE: ${{ matrix.gemfile }}
49
+
50
+ - name: Run Tests
51
+ run: bundle exec rake spec
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-5-2 CHANGED
@@ -1,5 +1,5 @@
1
1
  group :development, :test do
2
- gem 'rails', '>= 5.2.0.rc1', '< 6.0'
2
+ gem 'rails', '>= 5.2.1', '< 6.0'
3
3
  end
4
4
 
5
5
  eval_gemfile "#{__dir__}/Gemfile.base"
data/Gemfile.rails-6-0 ADDED
@@ -0,0 +1,5 @@
1
+ group :development, :test do
2
+ gem 'rails', '>= 6.0.0.rc1', '< 6.1'
3
+ end
4
+
5
+ eval_gemfile "#{__dir__}/Gemfile.base"
data/Gemfile.rails-6-1 ADDED
@@ -0,0 +1,5 @@
1
+ group :development, :test do
2
+ gem 'rails', '>= 6.1.0-rc1', '< 6.2'
3
+ end
4
+
5
+ eval_gemfile "#{__dir__}/Gemfile.base"
data/README.md CHANGED
@@ -1,11 +1,10 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/active_record_upsert.svg)](https://badge.fury.io/rb/active_record_upsert)
2
2
  [![Build Status](https://travis-ci.org/jesjos/active_record_upsert.svg?branch=master)](https://travis-ci.org/jesjos/active_record_upsert)
3
3
  [![Code Climate](https://codeclimate.com/github/jesjos/active_record_upsert/badges/gpa.svg)](https://codeclimate.com/github/jesjos/active_record_upsert)
4
- [![Dependency Status](https://gemnasium.com/badges/github.com/jesjos/active_record_upsert.svg)](https://gemnasium.com/github.com/jesjos/active_record_upsert)
5
4
 
6
5
  # ActiveRecordUpsert
7
6
 
8
- 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+ / ActiveRecord 5+. Uses [ON CONFLICT DO UPDATE](http://www.postgresql.org/docs/9.5/static/sql-insert.html).
9
8
 
10
9
  ## Main points
11
10
 
@@ -15,12 +14,17 @@ Real upsert for PostgreSQL 9.5+ and Rails 5 / ActiveRecord 5. Uses [ON CONFLICT
15
14
 
16
15
  ## Prerequisites
17
16
 
18
- - PostgreSQL 9.5+
19
- - ActiveRecord ~> 5
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
20
19
  - For MRI: pg
21
20
 
22
21
  - For JRuby: No support
23
22
 
23
+ ### NB: Releases to avoid
24
+
25
+ Due to a broken build matrix, v0.9.2 and v0.9.3 are incompatible with Rails
26
+ < 5.2.1. [v0.9.4](https://github.com/jesjos/active_record_upsert/releases/tag/v0.9.4) fixed this issue.
27
+
24
28
  ## Installation
25
29
 
26
30
  Add this line to your application's Gemfile:
@@ -119,6 +123,37 @@ r = MyRecord.new(id: 1, name: 'bar')
119
123
  r.upsert!
120
124
  ```
121
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
+
122
157
  ### Conflict Clauses
123
158
 
124
159
  It's possible to specify which columns should be used for the conflict clause. **These must comprise a unique index in Postgres.**
@@ -156,6 +191,24 @@ class Account < ApplicationRecord
156
191
  end
157
192
  ```
158
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
+
159
212
  ## Tests
160
213
 
161
214
  Make sure to have an upsert_test database:
@@ -186,3 +239,6 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jesjos
186
239
  - Daniel Cooper ([@danielcooper](https://github.com/danielcooper))
187
240
  - Laurent Vallar ([@val](https://github.com/val))
188
241
  - Emmanuel Quentin ([@manuquentin](https://github.com/manuquentin))
242
+ - Jeff Wallace ([@tjwallace](https://github.com/tjwallace))
243
+ - Kirill Zaitsev ([@Bugagazavr](https://github.com/Bugagazavr))
244
+ - Nick Campbell ([@nickcampbell18](https://github.com/nickcampbell18))
@@ -21,7 +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.0'
25
- spec.add_runtime_dependency 'arel', '> 7.0', '< 10.0'
24
+ spec.add_runtime_dependency 'activerecord', '>= 5.0', '< 6.2'
26
25
  spec.add_runtime_dependency 'pg', '>= 0.18', '< 2.0'
27
26
  end
@@ -13,6 +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.2.0'
16
17
  require 'active_record_upsert/compatibility/rails51.rb' if version >= '5.1.0' && version < '5.2.0'
17
18
  require 'active_record_upsert/compatibility/rails50.rb' if version >= '5.0.0' && version < '5.1.0'
18
19
 
@@ -9,7 +9,7 @@ module ActiveRecordUpsert
9
9
  end
10
10
 
11
11
  def exec_upsert(sql, name, binds)
12
- exec_query("#{sql} RETURNING *, (xmax::text::int = 0) AS _upsert_created_record", name, binds)
12
+ exec_query("#{sql} RETURNING *, (xmax = 0) AS _upsert_created_record", name, binds)
13
13
  end
14
14
  end
15
15
  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,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,11 +24,13 @@ module ActiveRecordUpsert
24
24
  false
25
25
  end
26
26
 
27
- def _upsert_record(upsert_attribute_names = changed, arel_condition = nil)
28
- existing_attributes = attributes_with_values_for_create(self.attributes.keys)
29
- values = self.class._upsert_record(existing_attributes, upsert_attribute_names, [arel_condition].compact)
27
+ def _upsert_record(upsert_attribute_names = changed, arel_condition = nil, opts = {})
28
+ existing_attribute_names = attributes_for_create(attributes.keys)
29
+ existing_attributes = attributes_with_values(existing_attribute_names)
30
+ values = self.class._upsert_record(existing_attributes, upsert_attribute_names, [arel_condition].compact, opts)
30
31
  @attributes = self.class.attributes_builder.build_from_database(values.first.to_h)
31
32
  @new_record = false
33
+ changes_applied
32
34
  values
33
35
  end
34
36
 
@@ -39,12 +41,12 @@ module ActiveRecordUpsert
39
41
  end
40
42
 
41
43
  module ClassMethods
42
- def upsert!(attributes, arel_condition: nil, validate: true, &block)
44
+ def upsert!(attributes, arel_condition: nil, validate: true, opts: {}, &block)
43
45
  if attributes.is_a?(Array)
44
46
  attributes.collect { |hash| upsert(hash, &block) }
45
47
  else
46
48
  new(attributes, &block).upsert!(
47
- attributes: attributes.keys, arel_condition: arel_condition, validate: validate
49
+ attributes: attributes.keys, arel_condition: arel_condition, validate: validate, opts: opts
48
50
  )
49
51
  end
50
52
  end
@@ -55,8 +57,9 @@ module ActiveRecordUpsert
55
57
  false
56
58
  end
57
59
 
58
- def _upsert_record(existing_attributes, upsert_attributes_names, wheres) # :nodoc:
59
- 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
60
63
  upsert_attributes_names = upsert_attributes_names - [*upsert_keys, 'created_at']
61
64
 
62
65
  existing_attributes = existing_attributes
@@ -1,18 +1,30 @@
1
1
  module ActiveRecordUpsert
2
2
  module ActiveRecord
3
3
  module PersistenceExtensions
4
- def _upsert_record(upsert_attribute_names = changed, arel_condition = nil)
5
- existing_attributes = arel_attributes_with_values_for_create(self.attributes.keys)
6
- values = self.class.unscoped.upsert(existing_attributes, upsert_attribute_names, [arel_condition].compact)
4
+ def _upsert_record(upsert_attribute_names = changed, arel_condition = nil, opts = {})
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, opts)
7
8
  @new_record = false
9
+ @attributes = self.class.attributes_builder.build_from_database(values.first.to_h)
10
+ changes_applied
8
11
  values
9
12
  end
13
+
14
+ def _prepare_column(name)
15
+ if self.class.reflections.key?(name)
16
+ self.class.reflections[name].foreign_key
17
+ else
18
+ name
19
+ end
20
+ end
10
21
  end
11
22
 
12
23
  module RelationExtensions
13
- def upsert(existing_attributes, upsert_attributes, wheres) # :nodoc:
24
+ def upsert(existing_attributes, upsert_attributes, wheres, opts) # :nodoc:
14
25
  substitutes, binds = substitute_values(existing_attributes)
15
- upsert_keys = self.klass.upsert_keys || [primary_key]
26
+ upsert_keys = opts[:upsert_keys] || self.klass.upsert_keys || [primary_key]
27
+ upsert_options = opts[:upsert_options] || self.klass.upsert_options
16
28
 
17
29
  upsert_attributes = upsert_attributes - [*upsert_keys, 'created_at']
18
30
  upsert_keys_filter = ->(o) { upsert_attributes.include?(o.name) }
@@ -20,9 +32,11 @@ module ActiveRecordUpsert
20
32
  on_conflict_binds = binds.select(&upsert_keys_filter)
21
33
  vals_for_upsert = substitutes.select { |s| upsert_keys_filter.call(s.first) }
22
34
 
35
+ target = arel_table[upsert_options.key?(:literal) ? ::Arel::Nodes::SqlLiteral.new(upsert_options[:literal]) : upsert_keys.join(',')]
36
+
23
37
  on_conflict_do_update = ::Arel::OnConflictDoUpdateManager.new
24
- on_conflict_do_update.target = arel_table[upsert_keys.join(',')]
25
- on_conflict_do_update.target_condition = self.klass.upsert_options[:where]
38
+ on_conflict_do_update.target = target
39
+ on_conflict_do_update.target_condition = upsert_options[:where]
26
40
  on_conflict_do_update.wheres = wheres
27
41
  on_conflict_do_update.set(vals_for_upsert)
28
42
 
@@ -31,7 +45,7 @@ module ActiveRecordUpsert
31
45
  insert_manager.on_conflict = on_conflict_do_update.to_node
32
46
  insert_manager.insert substitutes
33
47
 
34
- @klass.connection.upsert(insert_manager, "#{self} Upsert", binds + on_conflict_binds)
48
+ @klass.connection.upsert(insert_manager, "#{@klass.name} Upsert", binds + on_conflict_binds)
35
49
  end
36
50
 
37
51
  ::ActiveRecord::Relation.include(self)
@@ -0,0 +1,18 @@
1
+ module ActiveRecordUpsert
2
+ module ActiveRecord
3
+ module TransactionsExtensions
4
+ def upsert(*args)
5
+ with_transaction_returning_status { super }
6
+ end
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
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordUpsert
2
- VERSION = "0.9.2"
2
+ VERSION = "0.10.1"
3
3
  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.9.2
4
+ version: 0.10.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: 2018-08-12 00:00:00.000000000 Z
12
+ date: 2021-04-21 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.0'
23
+ version: '6.2'
24
24
  type: :runtime
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
@@ -30,27 +30,7 @@ dependencies:
30
30
  version: '5.0'
31
31
  - - "<"
32
32
  - !ruby/object:Gem::Version
33
- version: '6.0'
34
- - !ruby/object:Gem::Dependency
35
- name: arel
36
- requirement: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">"
39
- - !ruby/object:Gem::Version
40
- version: '7.0'
41
- - - "<"
42
- - !ruby/object:Gem::Version
43
- version: '10.0'
44
- type: :runtime
45
- prerelease: false
46
- version_requirements: !ruby/object:Gem::Requirement
47
- requirements:
48
- - - ">"
49
- - !ruby/object:Gem::Version
50
- version: '7.0'
51
- - - "<"
52
- - !ruby/object:Gem::Version
53
- version: '10.0'
33
+ version: '6.2'
54
34
  - !ruby/object:Gem::Dependency
55
35
  name: pg
56
36
  requirement: !ruby/object:Gem::Requirement
@@ -71,7 +51,7 @@ dependencies:
71
51
  - - "<"
72
52
  - !ruby/object:Gem::Version
73
53
  version: '2.0'
74
- description:
54
+ description:
75
55
  email:
76
56
  - jesper.josefsson@gmail.com
77
57
  - olle.jonsson@gmail.com
@@ -79,10 +59,13 @@ executables: []
79
59
  extensions: []
80
60
  extra_rdoc_files: []
81
61
  files:
62
+ - ".github/workflows/ci.yml"
82
63
  - Gemfile.base
83
64
  - Gemfile.rails-5-0
84
65
  - Gemfile.rails-5-1
85
66
  - Gemfile.rails-5-2
67
+ - Gemfile.rails-6-0
68
+ - Gemfile.rails-6-1
86
69
  - Gemfile.rails-master
87
70
  - LICENSE
88
71
  - README.md
@@ -109,12 +92,13 @@ files:
109
92
  - lib/active_record_upsert/arel/visitors/to_sql.rb
110
93
  - lib/active_record_upsert/compatibility/rails50.rb
111
94
  - lib/active_record_upsert/compatibility/rails51.rb
95
+ - lib/active_record_upsert/compatibility/rails60.rb
112
96
  - lib/active_record_upsert/version.rb
113
97
  homepage: https://github.com/jesjos/active_record_upsert/
114
98
  licenses:
115
99
  - MIT
116
100
  metadata: {}
117
- post_install_message:
101
+ post_install_message:
118
102
  rdoc_options: []
119
103
  require_paths:
120
104
  - lib
@@ -129,9 +113,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
113
  - !ruby/object:Gem::Version
130
114
  version: '0'
131
115
  requirements: []
132
- rubyforge_project:
133
- rubygems_version: 2.7.7
134
- signing_key:
116
+ rubygems_version: 3.2.7
117
+ signing_key:
135
118
  specification_version: 4
136
119
  summary: Real PostgreSQL 9.5+ upserts using ON CONFLICT for ActiveRecord
137
120
  test_files: []