active_record_upsert 0.9.2 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
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: []