active_record_upsert 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/LICENSE +21 -0
- data/README.md +71 -3
- data/active_record_upsert.gemspec +1 -0
- data/lib/active_record_upsert/active_record/persistence.rb +31 -10
- data/lib/active_record_upsert/active_record/relation.rb +18 -25
- data/lib/active_record_upsert/version.rb +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b6c3d046d77a439d261ab965fc32e843c070959b40300db46e299fe025bdbe2e
|
4
|
+
data.tar.gz: 21849093ff6d5ca811aa039d83da437cb9d2cb995e2e8e2c74248352f7b069ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe9a96f9703a0a3086bc83052d97093cf1aa377a3425e9e03d054e9248aa62fb0eabc9098f8483938ba1b343464470ebb015f8016a5df8bdc6564c193fb9400a
|
7
|
+
data.tar.gz: 17ef7f689953232f9ce646f8d3ff24708482d4009ea47bf9ffd26b5170928aadbf6b8d37c557a7e45995c73071eda076447b320db9a7d3bfcdc5bfd04c9acd5c
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 Jesper Josefsson
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -38,8 +38,10 @@ Or install it yourself as:
|
|
38
38
|
$ gem install active_record_upsert
|
39
39
|
|
40
40
|
## Usage
|
41
|
-
|
42
|
-
|
41
|
+
|
42
|
+
### Create
|
43
|
+
|
44
|
+
Use `ActiveRecord.upsert` or `ActiveRecord#upsert`. *ActiveRecordUpsert* respects timestamps.
|
43
45
|
|
44
46
|
```ruby
|
45
47
|
class MyRecord < ActiveRecord::Base
|
@@ -57,7 +59,69 @@ r.upsert
|
|
57
59
|
# => #<MyRecord id: 1, name: "bar", created_at: "2016-02-20 14:15:55", updated_at: "2016-02-20 14:18:49", wisdom: 3>
|
58
60
|
```
|
59
61
|
|
60
|
-
|
62
|
+
### Update
|
63
|
+
|
64
|
+
If you need to specify a condition for the update, pass it as an Arel query:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
MyRecord.upsert({id: 1, wisdom: 3}, arel_condition: MyRecord.arel_table[:updated_at].lt(1.day.ago))
|
68
|
+
```
|
69
|
+
|
70
|
+
The instance method `#upsert` can also take keyword arguments to specify a condition, or to limit which attributes to upsert
|
71
|
+
(by default, all `changed` attributes will be passed to the upsert):
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
r = MyRecord.new(id: 1)
|
75
|
+
r.name = 'bar'
|
76
|
+
r.color = 'blue'
|
77
|
+
r.upsert(attributes: [:name], arel_condition: MyRecord.arel_table[:updated_at].lt(1.day.ago))
|
78
|
+
# will only update :name, and only if the record is older than 1 day;
|
79
|
+
# but if the record does not exist, will insert with both :name and :colors
|
80
|
+
```
|
81
|
+
|
82
|
+
### Create with specific Attributes
|
83
|
+
|
84
|
+
If you want to create a record with the specific attributes, but update only a limited set of attributes,
|
85
|
+
similar to how `ActiveRecord::Base.create_with` works, you can do the following:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
existing_record = MyRecord.create(id: 1, name: 'lemon', color: 'green')
|
89
|
+
r = MyRecord.new(id: 1, name: 'banana', color: 'yellow')
|
90
|
+
r.upsert(attributes: [:color])
|
91
|
+
# => #<MyRecord id: 1, name: "lemon", color: "yellow", ...>
|
92
|
+
|
93
|
+
r = MyRecord.new(id: 2, name: 'banana', color: 'yellow')
|
94
|
+
r.upsert(attributes: [:color])
|
95
|
+
|
96
|
+
# => #<MyRecord id: 2, name: "banana", color: "yellow", ...>
|
97
|
+
|
98
|
+
# This is similar to:
|
99
|
+
|
100
|
+
MyRecord.create_with(name: 'banana').find_or_initialize_by(id: 2).update(color: 'yellow')
|
101
|
+
|
102
|
+
```
|
103
|
+
|
104
|
+
### Validations
|
105
|
+
|
106
|
+
Upsert will perform validation on the object, and return false if it is not valid. To skip validation, pass `validate: false`:
|
107
|
+
```ruby
|
108
|
+
MyRecord.upsert({id: 1, wisdom: 3}, validate: false)
|
109
|
+
```
|
110
|
+
|
111
|
+
If you want validations to raise `ActiveRecord::RecordInvalid`, use `upsert!`:
|
112
|
+
```ruby
|
113
|
+
MyRecord.upsert!(id: 1, wisdom: 3)
|
114
|
+
```
|
115
|
+
|
116
|
+
Or using the instance method:
|
117
|
+
```ruby
|
118
|
+
r = MyRecord.new(id: 1, name: 'bar')
|
119
|
+
r.upsert!
|
120
|
+
```
|
121
|
+
|
122
|
+
### Conflict Clauses
|
123
|
+
|
124
|
+
It's possible to specify which columns should be used for the conflict clause. **These must comprise a unique index in Postgres.**
|
61
125
|
|
62
126
|
```ruby
|
63
127
|
class Vehicle < ActiveRecord::Base
|
@@ -97,3 +161,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jesjos
|
|
97
161
|
- Olle Jonsson
|
98
162
|
- Simon Dahlbacka
|
99
163
|
- Paul Hoffer
|
164
|
+
- Ivan ([@me](https://github.com/me))
|
165
|
+
- Leon Miller-Out ([@sbleon](https://github.com/sbleon))
|
166
|
+
- Andrii Dmytrenko ([@Antti](https://github.com/Antti))
|
167
|
+
- Alexia McDonald ([@alexiamcdonald](https://github.com/alexiamcdonald))
|
@@ -9,6 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Jesper Josefsson", "Olle Jonsson"]
|
10
10
|
spec.email = ["jesper.josefsson@gmail.com", "olle.jonsson@gmail.com"]
|
11
11
|
spec.homepage = "https://github.com/jesjos/active_record_upsert/"
|
12
|
+
spec.license = 'MIT'
|
12
13
|
|
13
14
|
spec.summary = %q{Real PostgreSQL 9.5+ upserts using ON CONFLICT for ActiveRecord}
|
14
15
|
|
@@ -2,39 +2,60 @@ module ActiveRecordUpsert
|
|
2
2
|
module ActiveRecord
|
3
3
|
module PersistenceExtensions
|
4
4
|
|
5
|
-
def upsert(
|
5
|
+
def upsert!(attributes: nil, arel_condition: nil, validate: true)
|
6
6
|
raise ::ActiveRecord::ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
|
7
7
|
raise ::ActiveRecord::RecordSavedError, "Can't upsert a record that has already been saved" if persisted?
|
8
|
+
validate == false || perform_validations || raise_validation_error
|
8
9
|
values = run_callbacks(:save) {
|
9
10
|
run_callbacks(:create) {
|
10
|
-
|
11
|
-
|
11
|
+
attributes ||= changed
|
12
|
+
attributes = attributes +
|
12
13
|
timestamp_attributes_for_create_in_model +
|
13
14
|
timestamp_attributes_for_update_in_model
|
14
|
-
_upsert_record(
|
15
|
+
_upsert_record(attributes.map(&:to_s).uniq, arel_condition)
|
15
16
|
}
|
16
17
|
}
|
17
|
-
|
18
|
+
|
19
|
+
# When a migration adds a column to a table, the upsert will start
|
20
|
+
# returning the new attribute, and assign_attributes will fail,
|
21
|
+
# because Rails doesn't know about it yet (until the app is restarted).
|
22
|
+
#
|
23
|
+
# This checks that only known attributes are being assigned.
|
24
|
+
assign_attributes(values.first.to_h.slice(*self.attributes.keys))
|
18
25
|
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def upsert(*args)
|
29
|
+
upsert!(*args)
|
19
30
|
rescue ::ActiveRecord::RecordInvalid
|
20
31
|
false
|
21
32
|
end
|
22
33
|
|
23
|
-
|
24
|
-
|
25
|
-
|
34
|
+
|
35
|
+
def _upsert_record(upsert_attribute_names = changed, arel_condition = nil)
|
36
|
+
existing_attributes = arel_attributes_with_values_for_create(self.attributes.keys)
|
37
|
+
values = self.class.unscoped.upsert(existing_attributes, upsert_attribute_names, [arel_condition].compact)
|
26
38
|
@new_record = false
|
27
39
|
values
|
28
40
|
end
|
29
41
|
|
30
42
|
module ClassMethods
|
31
|
-
def upsert(attributes, &block)
|
43
|
+
def upsert!(attributes, arel_condition: nil, &block)
|
32
44
|
if attributes.is_a?(Array)
|
33
45
|
attributes.collect { |hash| upsert(hash, &block) }
|
34
46
|
else
|
35
|
-
new(attributes, &block).upsert(
|
47
|
+
new(attributes, &block).upsert!(
|
48
|
+
attributes: attributes.keys, arel_condition: arel_condition, validate: true
|
49
|
+
)
|
36
50
|
end
|
37
51
|
end
|
52
|
+
|
53
|
+
def upsert(*args)
|
54
|
+
upsert!(*args)
|
55
|
+
rescue ::ActiveRecord::RecordInvalid
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
38
59
|
def upsert_keys(*keys)
|
39
60
|
return @_upsert_keys if keys.empty?
|
40
61
|
keys = keys.first if keys.size == 1 # support single string/symbol, multiple string/symbols, and array
|
@@ -1,38 +1,31 @@
|
|
1
1
|
module ActiveRecordUpsert
|
2
2
|
module ActiveRecord
|
3
3
|
module RelationExtensions
|
4
|
-
def upsert(
|
5
|
-
|
4
|
+
def upsert(existing_attributes, upsert_attributes, wheres) # :nodoc:
|
5
|
+
substitutes, binds = substitute_values(existing_attributes)
|
6
|
+
upsert_keys = self.klass.upsert_keys || [primary_key]
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
k.name == primary_key
|
10
|
-
}]
|
11
|
-
end
|
8
|
+
upsert_attributes = upsert_attributes - [*upsert_keys, 'created_at']
|
9
|
+
upsert_keys_filter = ->(o) { upsert_attributes.include?(o.name) }
|
12
10
|
|
13
|
-
|
14
|
-
|
11
|
+
on_conflict_binds = binds.select(&upsert_keys_filter)
|
12
|
+
vals_for_upsert = substitutes.select { |s| upsert_keys_filter.call(s.first) }
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
on_conflict_do_update = arel_table.create_on_conflict_do_update
|
15
|
+
on_conflict_do_update.target = arel_table[upsert_keys.join(',')]
|
16
|
+
on_conflict_do_update.wheres = wheres
|
17
|
+
on_conflict_do_update.set(vals_for_upsert)
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
cm.set(substitutes.reject { |s| filter.call(s.first) })
|
25
|
-
on_conflict_binds = binds.reject(&filter)
|
26
|
-
|
27
|
-
im.on_conflict = cm.to_node
|
28
|
-
|
29
|
-
im.insert substitutes
|
19
|
+
insert_manager = arel_table.create_insert
|
20
|
+
insert_manager.into arel_table
|
21
|
+
insert_manager.on_conflict = on_conflict_do_update.to_node
|
22
|
+
insert_manager.insert substitutes
|
30
23
|
|
31
24
|
@klass.connection.upsert(
|
32
|
-
|
25
|
+
insert_manager,
|
33
26
|
'SQL',
|
34
|
-
|
35
|
-
|
27
|
+
nil, # primary key (not used)
|
28
|
+
nil, # primary key value (not used)
|
36
29
|
nil,
|
37
30
|
binds + on_conflict_binds)
|
38
31
|
end
|
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.7.
|
4
|
+
version: 0.7.1
|
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:
|
12
|
+
date: 2018-01-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -73,6 +73,7 @@ executables: []
|
|
73
73
|
extensions: []
|
74
74
|
extra_rdoc_files: []
|
75
75
|
files:
|
76
|
+
- LICENSE
|
76
77
|
- README.md
|
77
78
|
- Rakefile
|
78
79
|
- active_record_upsert.gemspec
|
@@ -98,7 +99,8 @@ files:
|
|
98
99
|
- lib/active_record_upsert/arel/visitors/to_sql.rb
|
99
100
|
- lib/active_record_upsert/version.rb
|
100
101
|
homepage: https://github.com/jesjos/active_record_upsert/
|
101
|
-
licenses:
|
102
|
+
licenses:
|
103
|
+
- MIT
|
102
104
|
metadata: {}
|
103
105
|
post_install_message:
|
104
106
|
rdoc_options: []
|
@@ -116,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
116
118
|
version: '0'
|
117
119
|
requirements: []
|
118
120
|
rubyforge_project:
|
119
|
-
rubygems_version: 2.
|
121
|
+
rubygems_version: 2.7.4
|
120
122
|
signing_key:
|
121
123
|
specification_version: 4
|
122
124
|
summary: Real PostgreSQL 9.5+ upserts using ON CONFLICT for ActiveRecord
|