protobuf-activerecord 3.4.4 → 3.5.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
  SHA1:
3
- metadata.gz: b94b8ba65793cc1162ee6905716c95d8ba2afb16
4
- data.tar.gz: 02fd2e40603b22fc90c9f44f81d724c9940f5054
3
+ metadata.gz: 6ebddfb7f1a2b6a894d213d63cec1a179e69c88c
4
+ data.tar.gz: 2ddf9cc3994a46fb2ec4c3118121be657faf7e2a
5
5
  SHA512:
6
- metadata.gz: 1c2ceafcbbfe007d619a96fb3289443fa1dfbffef453e139359618d36e4b845a575cefad8bfe248ef3b9ab8aacfe9233325a727941a60cb0cd7ab1176c15bcf7
7
- data.tar.gz: 678912754b8c1a7ec44a7d69c9232d4971bd6d225526a098e795a9a864a494c9b36a29badf1b4cb3217df56039bb5f02653c298c40e92d2339ba6575cb2b2d3b
6
+ metadata.gz: 4a5386b5374b38a42c3b507ff848c2afef0bea3d93ac3e8435e6c9863c87ff6fe1ef6bdf2c3d9ff7de7931b9735d92e0b673e268744140b28e23a733d7617ff9
7
+ data.tar.gz: f17ccbe9174babd312ebb75d35ffb65f8aff1c31886e20420d5f314029a3febf258eb377bc995b12e7cdf9e9256286938994d63bf801b0b83676d1b666581b45
data/README.md CHANGED
@@ -255,6 +255,53 @@ User.limit(10).search_scope(request)
255
255
 
256
256
  Protobuf Active Record also provides some aliases for the `search_scope` method in the event that you'd like something a little more descriptive: `by_fields` and `scope_from_proto` are all aliases of `search_scope`.
257
257
 
258
+ #### Upsert
259
+
260
+ Protobuf Active Record provides the ability to create a new record, or update an existing record if an existing record is found. This is implemented using Active Record's `first_or_initialize` method.
261
+
262
+ ```Ruby
263
+ class User < ActiveRecord::Base
264
+ scope :by_guid, lambda { |*values| where(:guid => values) }
265
+ scope :by_first_name, lambda { |*values| where(:first_name => values) }
266
+ scope :by_last_name, lambda { |*values| where(:last_name => values) }
267
+
268
+ field_scope :guid
269
+ field_scope :first_name
270
+ field_scope :last_name
271
+
272
+ upsert_key :guid
273
+ upsert_key :first_name, :last_name
274
+ end
275
+
276
+ @user = User.for_upsert(request)
277
+ ```
278
+
279
+ Note: An upsert_key should only be defined on a field or set of fields that have a unique constraint
280
+
281
+ Note: All fields used in an upsert key must also have a field_scope defined
282
+
283
+ If multiple upsert_keys match the request, the first matching upsert key will be used, in order of declaration. In the typical use-case where upsert keys have corresponding unique constraints the results should be equivalent regardless of order.
284
+
285
+ Protobuf Active Record provides several methods for invoking an upsert.
286
+
287
+ The first approach is to use the `for_upsert` method to look up a record.
288
+
289
+ ```Ruby
290
+ @user = User.for_upsert(proto)
291
+ ```
292
+
293
+ Alternatively, you can use `upsert` to look up the record and perform the persistence in the same call.
294
+
295
+ ```Ruby
296
+ # Example
297
+ User.upsert(proto)
298
+
299
+ # This is equivalent to
300
+ user = User.for_upsert(proto)
301
+ user.assign_attributes(proto)
302
+ user.save
303
+ ```
304
+
258
305
  ## Contributing
259
306
 
260
307
  1. Fork it
@@ -38,5 +38,13 @@ module Protobuf
38
38
  # Raised by `field_scope` when given scope is not defined.
39
39
  class SearchScopeError < ProtobufActiveRecordError
40
40
  end
41
+
42
+ # Raised by `upsert_scope` when a given scope is not defined
43
+ class UpsertScopeError < ProtobufActiveRecordError
44
+ end
45
+
46
+ # Raised by `for_upsert` when no valid upsert_scopes are found
47
+ class UpsertNotFoundError < ProtobufActiveRecordError
48
+ end
41
49
  end
42
50
  end
@@ -111,6 +111,80 @@ module Protobuf
111
111
  def searchable_field_parsers
112
112
  @_searchable_field_parsers ||= {}
113
113
  end
114
+
115
+ # Defines a scope that is eligible for upsert. The scope will be
116
+ # used to initialize a record with first_or_initialize. An upsert scope
117
+ # declariation must specify one or more fields that are required to
118
+ # be present on the request and also must have a field_scope defined.
119
+ #
120
+ # If multiple upsert scopes are specified, they will be searched in
121
+ # the order they are declared for the first valid scope.
122
+ #
123
+ # Examples:
124
+ #
125
+ # class User < ActiveRecord::Base
126
+ # scope :by_guid, lambda { |*guids| where(:guid => guids) }
127
+ # scope :by_external_guid, lambda { |*external_guids|
128
+ # where(:external_guid => exteranl_guids)
129
+ # }
130
+ # scope :by_client_guid, lambda { |*client_guids|
131
+ # joins(:client).where(
132
+ # :clients => { :guid => client_guids }
133
+ # )
134
+ # }
135
+ #
136
+ # field_scope :guid
137
+ # field_scope :client_guid
138
+ # field_scope :external_guid
139
+ #
140
+ # upsert_scope :external_guid, :client_guid
141
+ # upsert_scope :guid
142
+ #
143
+ # end
144
+ #
145
+ def upsert_key(*fields)
146
+ fields = fields.flatten
147
+
148
+ fields.each do |field|
149
+ fail UpsertScopeError unless searchable_fields[field].present?
150
+ end
151
+
152
+ upsert_keys << fields
153
+ end
154
+
155
+ def upsert_keys
156
+ @_upsert_keys ||= []
157
+ end
158
+
159
+ def for_upsert(proto)
160
+ valid_upsert = upsert_keys.find do |upsert_key|
161
+ upsert_key.all? do |field|
162
+ proto.respond_to_and_has_and_present?(field)
163
+ end
164
+ end
165
+
166
+ fail UpsertNotFoundError unless valid_upsert.present?
167
+
168
+ upsert_scope = model_scope
169
+ valid_upsert.each do |field|
170
+ value = proto.__send__(field)
171
+ upsert_scope = upsert_scope.__send__(searchable_fields[field], value)
172
+ end
173
+
174
+ upsert_scope.first_or_initialize
175
+ end
176
+
177
+ def upsert(proto)
178
+ record = for_upsert(proto)
179
+ record.assign_attributes(proto)
180
+ record.save
181
+ end
182
+
183
+ def upsert!(proto)
184
+ record = for_upsert(proto)
185
+ record.assign_attributes(proto)
186
+ record.save!
187
+ end
114
188
  end
115
189
  end
116
190
  end
@@ -1,5 +1,5 @@
1
1
  module Protobuf
2
2
  module ActiveRecord
3
- VERSION = "3.4.4"
3
+ VERSION = "3.5.0"
4
4
  end
5
5
  end
@@ -9,6 +9,7 @@ describe Protobuf::ActiveRecord::Scope do
9
9
  after do
10
10
  User.instance_variable_set("@_searchable_field_parsers", @field_parsers)
11
11
  User.instance_variable_set("@_searchable_fields", @fields)
12
+ User.instance_variable_set("@_upsert_keys", [])
12
13
  end
13
14
 
14
15
 
@@ -78,7 +79,6 @@ describe Protobuf::ActiveRecord::Scope do
78
79
  end
79
80
 
80
81
  describe ".parse_search_values" do
81
-
82
82
  it "converts single values to collections" do
83
83
  proto = UserMessage.new(:email => "the.email@test.in")
84
84
 
@@ -116,4 +116,87 @@ describe Protobuf::ActiveRecord::Scope do
116
116
  end
117
117
  end
118
118
  end
119
+
120
+ describe ".upsert_key" do
121
+ it "adds the fields to the upsert_keys" do
122
+ ::User.field_scope(:guid)
123
+ ::User.upsert_key(:guid)
124
+ expect(::User.upsert_keys).to eq([[:guid]])
125
+ end
126
+
127
+ context "no field_scope defined" do
128
+ it "raises an error" do
129
+ expect { ::User.upsert_key(:foobar) }.to raise_error(::Protobuf::ActiveRecord::UpsertScopeError)
130
+ end
131
+ end
132
+ end
133
+
134
+ describe ".for_upsert" do
135
+ let(:guid) { "USR-1" }
136
+ let(:proto) { ::UserMessage.new(:guid => guid) }
137
+
138
+ before do
139
+ ::User.delete_all
140
+ ::User.field_scope(:guid)
141
+ ::User.upsert_key(:guid)
142
+ end
143
+
144
+ context "no matching upsert keys" do
145
+ let(:proto) { ::UserMessage.new }
146
+
147
+ it "raises an error" do
148
+ expect { ::User.for_upsert(proto) }.to raise_error(::Protobuf::ActiveRecord::UpsertNotFoundError)
149
+ end
150
+ end
151
+
152
+ context "no existing records" do
153
+ it "returns a new record" do
154
+ record = ::User.for_upsert(proto)
155
+ expect(record.new_record?).to be true
156
+ end
157
+ end
158
+
159
+ context "existing record" do
160
+ before { ::User.create(:guid => guid) }
161
+ after { ::User.delete_all }
162
+
163
+ it "returns the existing record" do
164
+ record = ::User.for_upsert(proto)
165
+ expect(record.new_record?).to be false
166
+ end
167
+ end
168
+ end
169
+
170
+ describe ".upsert" do
171
+ let(:guid) { "USR-1" }
172
+ let(:proto) { ::UserMessage.new(:guid => guid, :email => "bar") }
173
+
174
+ before do
175
+ ::User.delete_all
176
+ ::User.field_scope(:guid)
177
+ ::User.upsert_key(:guid)
178
+ end
179
+
180
+ context "no existing records" do
181
+ it "creates a new record" do
182
+ ::User.upsert(proto)
183
+ expect(::User.count).to eq(1)
184
+ end
185
+ end
186
+
187
+ context "existing record" do
188
+ before { ::User.create(:guid => guid, :email => "foo") }
189
+ after { ::User.delete_all }
190
+
191
+ it "updates the existing record" do
192
+ ::User.upsert(proto)
193
+ expect(::User.first.email).to eq("bar")
194
+ end
195
+
196
+ it "returns true when valid" do
197
+ result = ::User.upsert(proto)
198
+ expect(result).to be true
199
+ end
200
+ end
201
+ end
119
202
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protobuf-activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.4.4
4
+ version: 3.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Hutchison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-27 00:00:00.000000000 Z
11
+ date: 2018-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -250,7 +250,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
250
250
  version: '0'
251
251
  requirements: []
252
252
  rubyforge_project:
253
- rubygems_version: 2.6.13
253
+ rubygems_version: 2.6.10
254
254
  signing_key:
255
255
  specification_version: 4
256
256
  summary: Google Protocol Buffers integration for Active Record