active_record_upsert 0.10.1 → 0.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e985d64fa74dedda4c75a2d8fb61f91e7ca89eca2d4c6e6c1bc62f9bf8c0499c
4
- data.tar.gz: 3acce358c953805747e39c96d1028ed92715c38ac90a07f744eb8e12f76c278a
3
+ metadata.gz: 459493128507f8a6ffd12fca94b8270cc93259601b4136c4686ae11f2acd7433
4
+ data.tar.gz: b3c8961297902a34a92d1f947980d4e1829d8e563401f54a7094d110df77fc66
5
5
  SHA512:
6
- metadata.gz: aea0bae90f0a51b27a1b3b8e24ad7bdf59c26a80433f4997892f7f5b1d1ea616f1b70731e56f052b7c9d7241b397f0613a6e2e8d0854021ba97c5ea623ab37af
7
- data.tar.gz: 0c9ca9e4d570cbb4741cdd34feb71bc4b236a09c263dfc4c65a0d6092a63ec1c2a776a49cb4e64386f03e7d46502b6140ab3792b5b755c2a1e809a94cc866114
6
+ metadata.gz: 9007d2d2c1889c6b2742783319b7d0c878cf47d01bee568126984390b8ba73e52384aa84ebff72f1cd0f895a42833a53bf55cfe6339b5a07de0353fe307b1f5c
7
+ data.tar.gz: 8e643c603a776f476a4a0407a5bfa301b0ef8afa726000a32b79fa95a6b036ab04f8360183cf7d33b694912e2577745efbe17418dc572a9a5b6554a757cad864
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"
data/Gemfile.rails-6-1 CHANGED
@@ -1,5 +1,5 @@
1
1
  group :development, :test do
2
- gem 'rails', '>= 6.1.0-rc1', '< 6.2'
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.0', '< 5.1'
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 CONFLIC
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:
@@ -135,7 +148,7 @@ When a table is defined with a database default for a field, this gotcha can occ
135
148
  │ id │ integer │ ...
136
149
  │ prio │ integer │ 999
137
150
 
138
- And `hardwares` has a record with a non-default value for `prio`. Say, the record with `id` 1 has a `prio` of `998`.
151
+ And `hardwares` has a record with a non-default value for `prio`. Say, the record with `id` 1 has a `prio` of `998`.
139
152
 
140
153
  In this situation, upserting like:
141
154
 
@@ -242,3 +255,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jesjos
242
255
  - Jeff Wallace ([@tjwallace](https://github.com/tjwallace))
243
256
  - Kirill Zaitsev ([@Bugagazavr](https://github.com/Bugagazavr))
244
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.2'
24
+ spec.add_runtime_dependency 'activerecord', '>= 5.2', '< 7.1'
25
25
  spec.add_runtime_dependency 'pg', '>= 0.18', '< 2.0'
26
26
  end
@@ -18,8 +18,8 @@ module ActiveRecordUpsert
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
@@ -51,8 +51,8 @@ module ActiveRecordUpsert
51
51
  end
52
52
  end
53
53
 
54
- def upsert(*args)
55
- upsert!(*args)
54
+ def upsert(attributes, **kwargs, &block)
55
+ upsert!(attributes, **kwargs, &block)
56
56
  rescue ::ActiveRecord::RecordInvalid
57
57
  false
58
58
  end
@@ -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,13 +1,13 @@
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
8
 
9
9
  module ConnectAdapterExtension
10
- def upsert(*args)
10
+ def upsert(*args, **kwargs)
11
11
  ::ActiveRecord::Base.clear_query_caches_for_current_thread
12
12
  super
13
13
  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.10.1"
2
+ VERSION = "0.11.0"
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/rails70.rb' if version >= '7.0.0' && version < '7.2.0'
16
17
  require 'active_record_upsert/compatibility/rails60.rb' if version >= '6.0.0' && version < '6.2.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'
19
18
 
20
19
  module ActiveRecordUpsert
21
20
  # Your code goes here...
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_upsert
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.1
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jesper Josefsson
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2021-04-21 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.2'
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.2'
33
+ version: '7.1'
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: pg
36
36
  requirement: !ruby/object:Gem::Requirement
@@ -59,13 +59,11 @@ executables: []
59
59
  extensions: []
60
60
  extra_rdoc_files: []
61
61
  files:
62
- - ".github/workflows/ci.yml"
63
62
  - Gemfile.base
64
- - Gemfile.rails-5-0
65
- - Gemfile.rails-5-1
66
63
  - Gemfile.rails-5-2
67
64
  - Gemfile.rails-6-0
68
65
  - Gemfile.rails-6-1
66
+ - Gemfile.rails-7-0
69
67
  - Gemfile.rails-master
70
68
  - LICENSE
71
69
  - README.md
@@ -79,7 +77,6 @@ files:
79
77
  - lib/active_record_upsert/active_record/timestamp.rb
80
78
  - lib/active_record_upsert/active_record/transactions.rb
81
79
  - lib/active_record_upsert/arel.rb
82
- - lib/active_record_upsert/arel/crud.rb
83
80
  - lib/active_record_upsert/arel/insert_manager.rb
84
81
  - lib/active_record_upsert/arel/nodes.rb
85
82
  - lib/active_record_upsert/arel/nodes/do_nothing.rb
@@ -89,10 +86,10 @@ files:
89
86
  - lib/active_record_upsert/arel/nodes/on_conflict.rb
90
87
  - lib/active_record_upsert/arel/nodes/on_conflict_action.rb
91
88
  - lib/active_record_upsert/arel/on_conflict_do_update_manager.rb
89
+ - lib/active_record_upsert/arel/table_extensions.rb
92
90
  - lib/active_record_upsert/arel/visitors/to_sql.rb
93
- - lib/active_record_upsert/compatibility/rails50.rb
94
- - lib/active_record_upsert/compatibility/rails51.rb
95
91
  - lib/active_record_upsert/compatibility/rails60.rb
92
+ - lib/active_record_upsert/compatibility/rails70.rb
96
93
  - lib/active_record_upsert/version.rb
97
94
  homepage: https://github.com/jesjos/active_record_upsert/
98
95
  licenses:
@@ -1,51 +0,0 @@
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.rails-5-1 DELETED
@@ -1,5 +0,0 @@
1
- group :development, :test do
2
- gem 'rails', '>= 5.1', '< 5.2'
3
- end
4
-
5
- eval_gemfile "#{__dir__}/Gemfile.base"
@@ -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,65 +0,0 @@
1
- module ActiveRecordUpsert
2
- module ActiveRecord
3
- module PersistenceExtensions
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)
8
- @new_record = false
9
- @attributes = self.class.attributes_builder.build_from_database(values.first.to_h)
10
- changes_applied
11
- values
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
21
- end
22
-
23
- module RelationExtensions
24
- def upsert(existing_attributes, upsert_attributes, wheres, opts) # :nodoc:
25
- substitutes, binds = substitute_values(existing_attributes)
26
- upsert_keys = opts[:upsert_keys] || self.klass.upsert_keys || [primary_key]
27
- upsert_options = opts[:upsert_options] || self.klass.upsert_options
28
-
29
- upsert_attributes = upsert_attributes - [*upsert_keys, 'created_at']
30
- upsert_keys_filter = ->(o) { upsert_attributes.include?(o.name) }
31
-
32
- on_conflict_binds = binds.select(&upsert_keys_filter)
33
- vals_for_upsert = substitutes.select { |s| upsert_keys_filter.call(s.first) }
34
-
35
- target = arel_table[upsert_options.key?(:literal) ? ::Arel::Nodes::SqlLiteral.new(upsert_options[:literal]) : upsert_keys.join(',')]
36
-
37
- on_conflict_do_update = ::Arel::OnConflictDoUpdateManager.new
38
- on_conflict_do_update.target = target
39
- on_conflict_do_update.target_condition = upsert_options[:where]
40
- on_conflict_do_update.wheres = wheres
41
- on_conflict_do_update.set(vals_for_upsert)
42
-
43
- insert_manager = arel_table.create_insert
44
- insert_manager.into arel_table
45
- insert_manager.on_conflict = on_conflict_do_update.to_node
46
- insert_manager.insert substitutes
47
-
48
- @klass.connection.upsert(insert_manager, "#{@klass.name} Upsert", binds + on_conflict_binds)
49
- end
50
-
51
- ::ActiveRecord::Relation.include(self)
52
- end
53
-
54
- module ConnectionAdapters
55
- module Postgresql
56
- module DatabaseStatementsExtensions
57
- def upsert(arel, name = nil, binds = [])
58
- sql = to_sql(arel, binds)
59
- exec_upsert(sql, name, binds)
60
- end
61
- end
62
- end
63
- end
64
- end
65
- end