protobuf-activerecord 3.4.4 → 3.5.0

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
  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