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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 861dbd4abac80eae38c3644d5a982f3b4c7d95a8
4
- data.tar.gz: fe700280c47b2cff78e3f8eeed2a14d966a10b72
2
+ SHA256:
3
+ metadata.gz: b6c3d046d77a439d261ab965fc32e843c070959b40300db46e299fe025bdbe2e
4
+ data.tar.gz: 21849093ff6d5ca811aa039d83da437cb9d2cb995e2e8e2c74248352f7b069ff
5
5
  SHA512:
6
- metadata.gz: 3254084380ac0b9a14189ed071f53ec45e74d2701962b892720839d93b2855156e684f26e025742d19e8575031b3062e662c034bc19705caacabfafddde1a6ad
7
- data.tar.gz: 859f1a55e2f6f2b64f192f18f305ae634fc50b4d24475a01bd5d0049cf4ad6d70cb931bd7215d4cfae492941046c04b4f15ca7a907fc2bbe844635bec5a0e340
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
- Just use `ActiveRecord.upsert` or `ActiveRecord#upsert`.
42
- *ActiveRecordUpsert* respects timestamps.
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
- Also, it's possible to specify which columns should be used for the conflict clause. **These must comprise a unique index in Postgres.**
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(attribute_names=nil)
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
- attribute_names ||= changed
11
- attribute_names = attribute_names.map(&:to_s) +
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(attribute_names.uniq)
15
+ _upsert_record(attributes.map(&:to_s).uniq, arel_condition)
15
16
  }
16
17
  }
17
- assign_attributes(values.first.to_h)
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
- def _upsert_record(attribute_names = changed)
24
- attributes_values = arel_attributes_with_values_for_create(attribute_names)
25
- values = self.class.unscoped.upsert attributes_values
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(attributes.keys)
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(values) # :nodoc:
5
- primary_key_value = nil
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
- if primary_key && Hash === values
8
- primary_key_value = values[values.keys.find { |k|
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
- im = arel_table.create_insert
14
- im.into arel_table
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
- substitutes, binds = substitute_values values
17
- column_arr = self.klass.upsert_keys || [primary_key]
18
- column_name = column_arr.join(',')
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
- cm = arel_table.create_on_conflict_do_update
21
- cm.target = arel_table[column_name]
22
- filter = ->(o) { [*column_arr, 'created_at'].include?(o.name) }
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
- im,
25
+ insert_manager,
33
26
  'SQL',
34
- primary_key, # not used
35
- primary_key_value, # not used
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
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordUpsert
2
- VERSION = "0.7.0"
2
+ VERSION = "0.7.1"
3
3
  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.0
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: 2017-08-22 00:00:00.000000000 Z
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.6.12
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