google-cloud-firestore 0.20.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +8 -0
  3. data/LICENSE +201 -0
  4. data/README.md +30 -0
  5. data/lib/google-cloud-firestore.rb +106 -0
  6. data/lib/google/cloud/firestore.rb +514 -0
  7. data/lib/google/cloud/firestore/batch.rb +462 -0
  8. data/lib/google/cloud/firestore/client.rb +449 -0
  9. data/lib/google/cloud/firestore/collection_reference.rb +249 -0
  10. data/lib/google/cloud/firestore/commit_response.rb +145 -0
  11. data/lib/google/cloud/firestore/convert.rb +561 -0
  12. data/lib/google/cloud/firestore/credentials.rb +35 -0
  13. data/lib/google/cloud/firestore/document_reference.rb +468 -0
  14. data/lib/google/cloud/firestore/document_snapshot.rb +324 -0
  15. data/lib/google/cloud/firestore/field_path.rb +216 -0
  16. data/lib/google/cloud/firestore/field_value.rb +113 -0
  17. data/lib/google/cloud/firestore/generate.rb +35 -0
  18. data/lib/google/cloud/firestore/query.rb +651 -0
  19. data/lib/google/cloud/firestore/service.rb +176 -0
  20. data/lib/google/cloud/firestore/transaction.rb +726 -0
  21. data/lib/google/cloud/firestore/v1beta1.rb +121 -0
  22. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/common.rb +63 -0
  23. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/document.rb +134 -0
  24. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/firestore.rb +584 -0
  25. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/query.rb +215 -0
  26. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/write.rb +167 -0
  27. data/lib/google/cloud/firestore/v1beta1/doc/google/protobuf/any.rb +124 -0
  28. data/lib/google/cloud/firestore/v1beta1/doc/google/protobuf/timestamp.rb +106 -0
  29. data/lib/google/cloud/firestore/v1beta1/doc/google/protobuf/wrappers.rb +89 -0
  30. data/lib/google/cloud/firestore/v1beta1/doc/google/rpc/status.rb +83 -0
  31. data/lib/google/cloud/firestore/v1beta1/doc/overview.rb +53 -0
  32. data/lib/google/cloud/firestore/v1beta1/firestore_client.rb +974 -0
  33. data/lib/google/cloud/firestore/v1beta1/firestore_client_config.json +100 -0
  34. data/lib/google/cloud/firestore/version.rb +22 -0
  35. data/lib/google/firestore/v1beta1/common_pb.rb +44 -0
  36. data/lib/google/firestore/v1beta1/document_pb.rb +49 -0
  37. data/lib/google/firestore/v1beta1/firestore_pb.rb +219 -0
  38. data/lib/google/firestore/v1beta1/firestore_services_pb.rb +87 -0
  39. data/lib/google/firestore/v1beta1/query_pb.rb +103 -0
  40. data/lib/google/firestore/v1beta1/write_pb.rb +73 -0
  41. metadata +251 -0
@@ -0,0 +1,176 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/env"
17
+ require "google/cloud/errors"
18
+ require "google/cloud/firestore/credentials"
19
+ require "google/cloud/firestore/version"
20
+ require "google/cloud/firestore/v1beta1"
21
+
22
+ module Google
23
+ module Cloud
24
+ module Firestore
25
+ ##
26
+ # @private Represents the gRPC Firestore service, including all the API
27
+ # methods.
28
+ class Service
29
+ attr_accessor :project, :credentials, :timeout, :client_config
30
+
31
+ ##
32
+ # @private Default project.
33
+ def self.default_project_id
34
+ ENV["FIRESTORE_PROJECT"] ||
35
+ ENV["GOOGLE_CLOUD_PROJECT"] ||
36
+ ENV["GCLOUD_PROJECT"] ||
37
+ Google::Cloud.env.project_id
38
+ end
39
+
40
+ ##
41
+ # Creates a new Service instance.
42
+ def initialize project, credentials, timeout: nil, client_config: nil
43
+ @project = project
44
+ @credentials = credentials
45
+ @timeout = timeout
46
+ @client_config = client_config || {}
47
+ end
48
+
49
+ def firestore
50
+ @firestore ||= \
51
+ V1beta1::FirestoreClient.new(
52
+ credentials: credentials,
53
+ timeout: timeout,
54
+ client_config: client_config,
55
+ lib_name: "gccl",
56
+ lib_version: Google::Cloud::Firestore::VERSION)
57
+ end
58
+
59
+ def get_documents document_paths, mask: nil, transaction: nil
60
+ batch_get_args = { mask: document_mask(mask) }
61
+ if transaction.is_a? String
62
+ batch_get_args[:transaction] = transaction
63
+ elsif transaction
64
+ batch_get_args[:new_transaction] = transaction
65
+ end
66
+ batch_get_args[:options] = call_options parent: database_path
67
+
68
+ execute do
69
+ firestore.batch_get_documents database_path, document_paths,
70
+ batch_get_args
71
+ end
72
+ end
73
+
74
+ def list_collections parent, transaction: nil
75
+ list_args = {}
76
+ if transaction.is_a? String
77
+ list_args[:transaction] = transaction
78
+ elsif transaction
79
+ list_args[:new_transaction] = transaction
80
+ end
81
+ list_args[:options] = call_options parent: database_path
82
+
83
+ execute do
84
+ firestore.list_collection_ids parent, list_args
85
+ end
86
+ end
87
+
88
+ def run_query path, query_grpc, transaction: nil
89
+ run_query_args = { structured_query: query_grpc }
90
+ if transaction.is_a? String
91
+ run_query_args[:transaction] = transaction
92
+ elsif transaction
93
+ run_query_args[:new_transaction] = transaction
94
+ end
95
+ run_query_args[:options] = call_options parent: database_path
96
+
97
+ execute do
98
+ firestore.run_query path, run_query_args
99
+ end
100
+ end
101
+
102
+ def begin_transaction transaction_opt
103
+ options = call_options parent: database_path
104
+
105
+ execute do
106
+ firestore.begin_transaction database_path,
107
+ options_: transaction_opt,
108
+ options: options
109
+ end
110
+ end
111
+
112
+ def commit writes, transaction: nil
113
+ commit_args = {}
114
+ commit_args[:transaction] = transaction if transaction
115
+ commit_args[:options] = call_options parent: database_path
116
+
117
+ execute do
118
+ firestore.commit database_path, writes, commit_args
119
+ end
120
+ end
121
+
122
+ def rollback transaction
123
+ options = call_options parent: database_path
124
+
125
+ execute do
126
+ firestore.rollback database_path, transaction, options: options
127
+ end
128
+ end
129
+
130
+ def database_path project_id: project, database_id: "(default)"
131
+ V1beta1::FirestoreClient.database_root_path project_id, database_id
132
+ end
133
+
134
+ def documents_path project_id: project, database_id: "(default)"
135
+ V1beta1::FirestoreClient.document_root_path project_id, database_id
136
+ end
137
+
138
+ def inspect
139
+ "#{self.class}(#{@project})"
140
+ end
141
+
142
+ protected
143
+
144
+ def default_headers parent = nil
145
+ parent ||= database_path
146
+ { "google-cloud-resource-prefix" => parent }
147
+ end
148
+
149
+ def call_options parent: nil, token: nil
150
+ Google::Gax::CallOptions.new({
151
+ kwargs: default_headers(parent),
152
+ page_token: token
153
+ }.delete_if { |_, v| v.nil? })
154
+ end
155
+
156
+ def document_mask mask
157
+ return nil if mask.nil?
158
+
159
+ mask = Array(mask).map(&:to_s).reject(&:nil?).reject(&:empty?)
160
+ return nil if mask.empty?
161
+
162
+ Google::Firestore::V1beta1::DocumentMask.new field_paths: mask
163
+ end
164
+
165
+ def execute
166
+ yield
167
+ rescue Google::Gax::GaxError => e
168
+ # GaxError wraps BadStatus, but exposes it as #cause
169
+ raise Google::Cloud::Error.from_error(e.cause)
170
+ rescue GRPC::BadStatus => e
171
+ raise Google::Cloud::Error.from_error(e)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,726 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/firestore/collection_reference"
17
+ require "google/cloud/firestore/document_reference"
18
+ require "google/cloud/firestore/document_snapshot"
19
+ require "google/cloud/firestore/commit_response"
20
+ require "google/cloud/firestore/convert"
21
+
22
+ module Google
23
+ module Cloud
24
+ module Firestore
25
+ ##
26
+ # # Transaction
27
+ #
28
+ # A transaction in Cloud Firestore is a set of reads and writes that
29
+ # execute atomically at a single logical point in time.
30
+ #
31
+ # All changes are accumulated in memory until the block passed to
32
+ # {Database#transaction} completes. Transactions will be automatically
33
+ # retried when documents change before the transaction is committed. See
34
+ # {Database#transaction}.
35
+ #
36
+ # @example
37
+ # require "google/cloud/firestore"
38
+ #
39
+ # firestore = Google::Cloud::Firestore.new
40
+ #
41
+ # city = firestore.col("cities").doc("SF")
42
+ # city.set({ name: "San Francisco",
43
+ # state: "CA",
44
+ # country: "USA",
45
+ # capital: false,
46
+ # population: 860000 })
47
+ #
48
+ # firestore.transaction do |tx|
49
+ # new_population = tx.get(city).data[:population] + 1
50
+ # tx.update(city, { population: new_population })
51
+ # end
52
+ #
53
+ class Transaction
54
+ ##
55
+ # @private New Transaction object.
56
+ def initialize
57
+ @writes = []
58
+ @transaction_id = nil
59
+ @previous_transaction = nil
60
+ end
61
+
62
+ ##
63
+ # The transaction identifier.
64
+ #
65
+ # @return [String] transaction identifier.
66
+ def transaction_id
67
+ @transaction_id
68
+ end
69
+
70
+ ##
71
+ # The client the Cloud Firestore transaction belongs to.
72
+ #
73
+ # @return [Client] firestore client.
74
+ def firestore
75
+ @client
76
+ end
77
+ alias_method :client, :firestore
78
+
79
+ # @!group Access
80
+
81
+ ##
82
+ # Retrieves a list of document snapshots.
83
+ #
84
+ # @param [String, DocumentReference] docs One or more strings
85
+ # representing the path of the document, or document reference
86
+ # objects.
87
+ #
88
+ # @yield [documents] The block for accessing the document snapshots.
89
+ # @yieldparam [DocumentSnapshot] document A document snapshot.
90
+ #
91
+ # @return [Enumerator<DocumentSnapshot>] document snapshots list.
92
+ #
93
+ # @example
94
+ # require "google/cloud/firestore"
95
+ #
96
+ # firestore = Google::Cloud::Firestore.new
97
+ #
98
+ # firestore.transaction do |tx|
99
+ # # Get and print city documents
100
+ # tx.get_all("cities/NYC", "cities/SF", "cities/LA").each do |city|
101
+ # puts "#{city.document_id} has #{city[:population]} residents."
102
+ # end
103
+ # end
104
+ #
105
+ def get_all *docs
106
+ ensure_not_closed!
107
+ ensure_service!
108
+
109
+ return enum_for(:get_all, docs) unless block_given?
110
+
111
+ doc_paths = Array(docs).flatten.map do |doc_path|
112
+ coalesce_doc_path_argument doc_path
113
+ end
114
+
115
+ results = service.get_documents \
116
+ doc_paths, transaction: transaction_or_create
117
+ results.each do |result|
118
+ extract_transaction_from_result! result
119
+ next if result.result.nil?
120
+ yield DocumentSnapshot.from_batch_result(result, self)
121
+ end
122
+ end
123
+ alias_method :get_docs, :get_all
124
+ alias_method :get_documents, :get_all
125
+ alias_method :find, :get_all
126
+
127
+ ##
128
+ # Retrieves document snapshots for the given value. Valid values can be
129
+ # a string representing either a document or a collection of documents,
130
+ # a document reference object, a collection reference object, or a query
131
+ # to be run.
132
+ #
133
+ # @param [String, DocumentReference, CollectionReference, Query] obj
134
+ # A string representing the path of a document or collection, a
135
+ # document reference object, a collection reference object, or a query
136
+ # to run.
137
+ #
138
+ # @yield [documents] The block for accessing the document snapshots.
139
+ # @yieldparam [DocumentReference] document A document snapshot.
140
+ #
141
+ # @return [DocumentReference, Enumerator<DocumentReference>] A
142
+ # single document snapshot when passed a document path a document
143
+ # reference, or a list of document snapshots when passed other valid
144
+ # values.
145
+ #
146
+ # @example Get a document snapshot given a document path:
147
+ # require "google/cloud/firestore"
148
+ #
149
+ # firestore = Google::Cloud::Firestore.new
150
+ #
151
+ # firestore.transaction do |tx|
152
+ # # Get a document snapshot
153
+ # nyc_snap = tx.get "cities/NYC"
154
+ # end
155
+ #
156
+ # @example Get a document snapshot given a document reference:
157
+ # require "google/cloud/firestore"
158
+ #
159
+ # firestore = Google::Cloud::Firestore.new
160
+ #
161
+ # # Get a document reference
162
+ # nyc_ref = firestore.doc "cities/NYC"
163
+ #
164
+ # firestore.transaction do |tx|
165
+ # # Get a document snapshot
166
+ # nyc_snap = tx.get nyc_ref
167
+ # end
168
+ #
169
+ # @example Get document snapshots given a collection path:
170
+ # require "google/cloud/firestore"
171
+ #
172
+ # firestore = Google::Cloud::Firestore.new
173
+ #
174
+ # firestore.transaction do |tx|
175
+ # # Get documents for a collection path
176
+ # tx.get("cities").each do |city|
177
+ # # Update the city population by 1
178
+ # tx.update(city, { population: city[:population] + 1})
179
+ # end
180
+ # end
181
+ #
182
+ # @example Get document snapshots given a collection reference:
183
+ # require "google/cloud/firestore"
184
+ #
185
+ # firestore = Google::Cloud::Firestore.new
186
+ #
187
+ # # Get a collection reference
188
+ # cities_col = firestore.col :cities
189
+ #
190
+ # firestore.transaction do |tx|
191
+ # # Get documents for a collection
192
+ # tx.get(cities_col).each do |city|
193
+ # # Update the city population by 1
194
+ # tx.update(city, { population: city[:population] + 1})
195
+ # end
196
+ # end
197
+ #
198
+ # @example Get document snapshots given a query:
199
+ # require "google/cloud/firestore"
200
+ #
201
+ # firestore = Google::Cloud::Firestore.new
202
+ #
203
+ # # Create a query
204
+ # query = firestore.col(:cities).select(:population)
205
+ #
206
+ # firestore.transaction do |tx|
207
+ # # Get/run a query
208
+ # tx.get(query).each do |city|
209
+ # # Update the city population by 1
210
+ # tx.update(city, { population: city[:population] + 1})
211
+ # end
212
+ # end
213
+ #
214
+ def get obj
215
+ ensure_not_closed!
216
+ ensure_service!
217
+
218
+ obj = coalesce_get_argument obj
219
+
220
+ if obj.is_a?(DocumentReference)
221
+ doc = get_all([obj]).first
222
+ yield doc if block_given?
223
+ return doc
224
+ end
225
+
226
+ return enum_for(:get, obj) unless block_given?
227
+
228
+ results = service.run_query obj.parent_path, obj.query,
229
+ transaction: transaction_or_create
230
+ results.each do |result|
231
+ extract_transaction_from_result! result
232
+ next if result.document.nil?
233
+ yield DocumentSnapshot.from_query_result(result, self)
234
+ end
235
+ end
236
+ alias_method :run, :get
237
+
238
+ # @!endgroup
239
+
240
+ # @!group Modifications
241
+
242
+ ##
243
+ # Creates a document with the provided data (fields and values).
244
+ #
245
+ # The operation will fail if the document already exists.
246
+ #
247
+ # @param [String, DocumentReference] doc A string representing the
248
+ # path of the document, or a document reference object.
249
+ # @param [Hash] data The document's fields and values.
250
+ #
251
+ # @example Create a document using a document path:
252
+ # require "google/cloud/firestore"
253
+ #
254
+ # firestore = Google::Cloud::Firestore.new
255
+ #
256
+ # firestore.transaction do |tx|
257
+ # tx.create("cities/NYC", { name: "New York City" })
258
+ # end
259
+ #
260
+ # @example Create a document using a document reference:
261
+ # require "google/cloud/firestore"
262
+ #
263
+ # firestore = Google::Cloud::Firestore.new
264
+ #
265
+ # # Get a document reference
266
+ # nyc_ref = firestore.doc "cities/NYC"
267
+ #
268
+ # firestore.transaction do |tx|
269
+ # tx.create(nyc_ref, { name: "New York City" })
270
+ # end
271
+ #
272
+ # @example Create a document and set a field to server_time:
273
+ # require "google/cloud/firestore"
274
+ #
275
+ # firestore = Google::Cloud::Firestore.new
276
+ #
277
+ # # Get a document reference
278
+ # nyc_ref = firestore.doc "cities/NYC"
279
+ #
280
+ # firestore.transaction do |tx|
281
+ # tx.create(nyc_ref, { name: "New York City",
282
+ # updated_at: firestore.field_server_time })
283
+ # end
284
+ #
285
+ def create doc, data
286
+ ensure_not_closed!
287
+
288
+ doc_path = coalesce_doc_path_argument doc
289
+
290
+ @writes << Convert.writes_for_create(doc_path, data)
291
+
292
+ nil
293
+ end
294
+
295
+ ##
296
+ # Writes the provided data (fields and values) to the provided document.
297
+ # If the document does not exist, it will be created. By default, the
298
+ # provided data overwrites existing data, but the provided data can be
299
+ # merged into the existing document using the `merge` argument.
300
+ #
301
+ # If you're not sure whether the document exists, use the `merge`
302
+ # argument to merge the new data with any existing document data to
303
+ # avoid overwriting entire documents.
304
+ #
305
+ # @param [String, DocumentReference] doc A string representing the
306
+ # path of the document, or a document reference object.
307
+ # @param [Hash] data The document's fields and values.
308
+ # @param [Boolean, FieldPath, String, Symbol] merge When
309
+ # `true`, all provided data is merged with the existing document data.
310
+ # When the argument is one or more field path, only the data for
311
+ # fields in this argument is merged with the existing document data.
312
+ # The default is to not merge, but to instead overwrite the existing
313
+ # document data.
314
+ #
315
+ # @example Set a document using a document path:
316
+ # require "google/cloud/firestore"
317
+ #
318
+ # firestore = Google::Cloud::Firestore.new
319
+ #
320
+ # firestore.transaction do |tx|
321
+ # # Update a document
322
+ # tx.set("cities/NYC", { name: "New York City" })
323
+ # end
324
+ #
325
+ # @example Create a document using a document reference:
326
+ # require "google/cloud/firestore"
327
+ #
328
+ # firestore = Google::Cloud::Firestore.new
329
+ #
330
+ # # Get a document reference
331
+ # nyc_ref = firestore.doc "cities/NYC"
332
+ #
333
+ # firestore.transaction do |tx|
334
+ # # Update a document
335
+ # tx.set(nyc_ref, { name: "New York City" })
336
+ # end
337
+ #
338
+ # @example Set a document and merge all data:
339
+ # require "google/cloud/firestore"
340
+ #
341
+ # firestore = Google::Cloud::Firestore.new
342
+ #
343
+ # firestore.transaction do |tx|
344
+ # tx.set("cities/NYC", { name: "New York City" }, merge: true)
345
+ # end
346
+ #
347
+ # @example Set a document and merge only name:
348
+ # require "google/cloud/firestore"
349
+ #
350
+ # firestore = Google::Cloud::Firestore.new
351
+ #
352
+ # firestore.transaction do |tx|
353
+ # tx.set("cities/NYC", { name: "New York City" }, merge: :name)
354
+ # end
355
+ #
356
+ # @example Set a document and deleting a field using merge:
357
+ # require "google/cloud/firestore"
358
+ #
359
+ # firestore = Google::Cloud::Firestore.new
360
+ #
361
+ # # Get a document reference
362
+ # nyc_ref = firestore.doc "cities/NYC"
363
+ #
364
+ # nyc_data = { name: "New York City",
365
+ # trash: firestore.field_delete }
366
+ #
367
+ # firestore.transaction do |tx|
368
+ # tx.set(nyc_ref, nyc_data, merge: true)
369
+ # end
370
+ #
371
+ # @example Set a document and set a field to server_time:
372
+ # require "google/cloud/firestore"
373
+ #
374
+ # firestore = Google::Cloud::Firestore.new
375
+ #
376
+ # # Get a document reference
377
+ # nyc_ref = firestore.doc "cities/NYC"
378
+ #
379
+ # nyc_data = { name: "New York City",
380
+ # updated_at: firestore.field_server_time }
381
+ #
382
+ # firestore.transaction do |tx|
383
+ # tx.set(nyc_ref, nyc_data, merge: true)
384
+ # end
385
+ #
386
+ def set doc, data, merge: nil
387
+ ensure_not_closed!
388
+
389
+ doc_path = coalesce_doc_path_argument doc
390
+
391
+ @writes << Convert.writes_for_set(doc_path, data, merge: merge)
392
+
393
+ nil
394
+ end
395
+
396
+ ##
397
+ # Updates the document with the provided data (fields and values). The
398
+ # provided data is merged into the existing document data.
399
+ #
400
+ # The operation will fail if the document does not exist.
401
+ #
402
+ # @param [String, DocumentReference] doc A string representing the
403
+ # path of the document, or a document reference object.
404
+ # @param [Hash<FieldPath|String|Symbol, Object>] data The document's
405
+ # fields and values.
406
+ #
407
+ # The top-level keys in the data hash are considered field paths, and
408
+ # can either be a FieldPath object, or a string representing the
409
+ # nested fields. In other words the string represents individual
410
+ # fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
411
+ # `.` cannot be in a dotted string, and should provided using a
412
+ # {FieldPath} object instead.
413
+ # @param [Time] update_time When set, the document must have been last
414
+ # updated at that time. Optional.
415
+ #
416
+ # @example Update a document using a document path:
417
+ # require "google/cloud/firestore"
418
+ #
419
+ # firestore = Google::Cloud::Firestore.new
420
+ #
421
+ # firestore.transaction do |tx|
422
+ # tx.update("cities/NYC", { name: "New York City" })
423
+ # end
424
+ #
425
+ # @example Directly update a deeply-nested field with a `FieldPath`:
426
+ # require "google/cloud/firestore"
427
+ #
428
+ # firestore = Google::Cloud::Firestore.new
429
+ #
430
+ # nested_field_path = Google::Cloud::Firestore::FieldPath.new(
431
+ # :favorites, :food
432
+ # )
433
+ #
434
+ # firestore.transaction do |tx|
435
+ # tx.update("users/frank", { nested_field_path: "Pasta" })
436
+ # end
437
+ #
438
+ # @example Update a document using a document reference:
439
+ # require "google/cloud/firestore"
440
+ #
441
+ # firestore = Google::Cloud::Firestore.new
442
+ #
443
+ # # Get a document reference
444
+ # nyc_ref = firestore.doc "cities/NYC"
445
+ #
446
+ # firestore.transaction do |tx|
447
+ # tx.update(nyc_ref, { name: "New York City" })
448
+ # end
449
+ #
450
+ # @example Update a document using the `update_time` precondition:
451
+ # require "google/cloud/firestore"
452
+ #
453
+ # firestore = Google::Cloud::Firestore.new
454
+ #
455
+ # last_updated_at = Time.now - 42 # 42 seconds ago
456
+ #
457
+ # firestore.transaction do |tx|
458
+ # tx.update("cities/NYC", { name: "New York City" },
459
+ # update_time: last_updated_at)
460
+ # end
461
+ #
462
+ # @example Update a document and deleting a field:
463
+ # require "google/cloud/firestore"
464
+ #
465
+ # firestore = Google::Cloud::Firestore.new
466
+ #
467
+ # # Get a document reference
468
+ # nyc_ref = firestore.doc "cities/NYC"
469
+ #
470
+ # nyc_data = { name: "New York City",
471
+ # trash: firestore.field_delete }
472
+ #
473
+ # firestore.transaction do |tx|
474
+ # tx.update(nyc_ref, nyc_data)
475
+ # end
476
+ #
477
+ # @example Update a document and set a field to server_time:
478
+ # require "google/cloud/firestore"
479
+ #
480
+ # firestore = Google::Cloud::Firestore.new
481
+ #
482
+ # # Get a document reference
483
+ # nyc_ref = firestore.doc "cities/NYC"
484
+ #
485
+ # nyc_data = { name: "New York City",
486
+ # updated_at: firestore.field_server_time }
487
+ #
488
+ # firestore.transaction do |tx|
489
+ # tx.update(nyc_ref, nyc_data)
490
+ # end
491
+ #
492
+ def update doc, data, update_time: nil
493
+ ensure_not_closed!
494
+
495
+ doc_path = coalesce_doc_path_argument doc
496
+
497
+ @writes << Convert.writes_for_update(doc_path, data,
498
+ update_time: update_time)
499
+
500
+ nil
501
+ end
502
+
503
+ ##
504
+ # Deletes a document from the database.
505
+ #
506
+ # @param [String, DocumentReference] doc A string representing the
507
+ # path of the document, or a document reference object.
508
+ # @param [Boolean] exists Whether the document must exist. When `true`,
509
+ # the document must exist or an error is raised. Default is `false`.
510
+ # Optional.
511
+ # @param [Time] update_time When set, the document must have been last
512
+ # updated at that time. Optional.
513
+ #
514
+ # @example Delete a document using a document path:
515
+ # require "google/cloud/firestore"
516
+ #
517
+ # firestore = Google::Cloud::Firestore.new
518
+ #
519
+ # firestore.transaction do |tx|
520
+ # # Delete a document
521
+ # tx.delete "cities/NYC"
522
+ # end
523
+ #
524
+ # @example Delete a document using a document reference:
525
+ # require "google/cloud/firestore"
526
+ #
527
+ # firestore = Google::Cloud::Firestore.new
528
+ #
529
+ # # Get a document reference
530
+ # nyc_ref = firestore.doc "cities/NYC"
531
+ #
532
+ # firestore.transaction do |tx|
533
+ # # Delete a document
534
+ # tx.delete nyc_ref
535
+ # end
536
+ #
537
+ # @example Delete a document using `exists`:
538
+ # require "google/cloud/firestore"
539
+ #
540
+ # firestore = Google::Cloud::Firestore.new
541
+ #
542
+ # firestore.transaction do |tx|
543
+ # # Delete a document
544
+ # tx.delete "cities/NYC", exists: true
545
+ # end
546
+ #
547
+ # @example Delete a document using the `update_time` precondition:
548
+ # require "google/cloud/firestore"
549
+ #
550
+ # firestore = Google::Cloud::Firestore.new
551
+ #
552
+ # last_updated_at = Time.now - 42 # 42 seconds ago
553
+ #
554
+ # firestore.transaction do |tx|
555
+ # # Delete a document
556
+ # tx.delete "cities/NYC", update_time: last_updated_at
557
+ # end
558
+ #
559
+ def delete doc, exists: nil, update_time: nil
560
+ ensure_not_closed!
561
+
562
+ doc_path = coalesce_doc_path_argument doc
563
+
564
+ @writes << Convert.write_for_delete(
565
+ doc_path, exists: exists, update_time: update_time)
566
+
567
+ nil
568
+ end
569
+
570
+ # @!endgroup
571
+
572
+ ##
573
+ # @private commit the transaction
574
+ def commit
575
+ ensure_not_closed!
576
+
577
+ if @transaction_id.nil? && @writes.empty?
578
+ @closed = true
579
+ return CommitResponse.from_grpc nil, @writes
580
+ end
581
+
582
+ ensure_transaction_id!
583
+
584
+ resp = service.commit @writes.flatten, transaction: transaction_id
585
+ @closed = true
586
+ CommitResponse.from_grpc resp, @writes
587
+ end
588
+
589
+ ##
590
+ # @private rollback and close the transaction
591
+ def rollback
592
+ ensure_not_closed!
593
+
594
+ if @transaction_id.nil? && @writes.empty?
595
+ @closed = true
596
+ return
597
+ end
598
+
599
+ service.rollback @transaction_id
600
+ @closed = true
601
+ nil
602
+ end
603
+
604
+ ##
605
+ # @private the transaction is complete and closed
606
+ def closed?
607
+ @closed
608
+ end
609
+
610
+ ##
611
+ # @private New Transaction reference object from a path.
612
+ def self.from_client client, previous_transaction: nil
613
+ new.tap do |s|
614
+ s.instance_variable_set :@client, client
615
+ s.instance_variable_set :@previous_transaction, previous_transaction
616
+ end
617
+ end
618
+
619
+ ##
620
+ # @private The Service object.
621
+ def service
622
+ ensure_client!
623
+
624
+ firestore.service
625
+ end
626
+
627
+ protected
628
+
629
+ ##
630
+ # @private The full Database path for the Cloud Firestore transaction.
631
+ #
632
+ # @return [String] database resource path.
633
+ def path
634
+ @client.path
635
+ end
636
+
637
+ ##
638
+ # @private
639
+ def coalesce_get_argument obj
640
+ if obj.is_a?(String) || obj.is_a?(Symbol)
641
+ if obj.to_s.split("/").count.even?
642
+ return client.doc obj # Convert a DocumentReference
643
+ else
644
+ return client.col obj # Convert to CollectionReference
645
+ end
646
+ end
647
+
648
+ return obj.ref if obj.is_a? DocumentSnapshot
649
+
650
+ obj
651
+ end
652
+
653
+ ##
654
+ # @private
655
+ def coalesce_doc_path_argument doc_path
656
+ return doc_path.path if doc_path.respond_to? :path
657
+
658
+ client.doc(doc_path).path
659
+ end
660
+
661
+ ##
662
+ # @private
663
+ def transaction_or_create
664
+ return @transaction_id if @transaction_id
665
+
666
+ transaction_opt
667
+ end
668
+
669
+ ##
670
+ # @private
671
+ def transaction_opt
672
+ read_write = \
673
+ Google::Firestore::V1beta1::TransactionOptions::ReadWrite.new
674
+
675
+ if @previous_transaction
676
+ read_write.retry_transaction = @previous_transaction
677
+ @previous_transaction = nil
678
+ end
679
+
680
+ Google::Firestore::V1beta1::TransactionOptions.new(
681
+ read_write: read_write
682
+ )
683
+ end
684
+
685
+ ##
686
+ # @private
687
+ def extract_transaction_from_result! result
688
+ return if @transaction_id
689
+ return if result.transaction.nil?
690
+ return if result.transaction.empty?
691
+
692
+ @transaction_id = result.transaction
693
+ end
694
+
695
+ ##
696
+ # @private
697
+ def ensure_not_closed!
698
+ fail "transaction is closed" if closed?
699
+ end
700
+
701
+ ##
702
+ # @private Raise an error unless an database available.
703
+ def ensure_transaction_id!
704
+ ensure_service!
705
+
706
+ return unless @transaction_id.nil?
707
+ resp = service.begin_transaction transaction_opt
708
+ @transaction_id = resp.transaction
709
+ end
710
+
711
+ ##
712
+ # @private Raise an error unless an database available.
713
+ def ensure_client!
714
+ fail "Must have active connection to service" unless firestore
715
+ end
716
+
717
+ ##
718
+ # @private Raise an error unless an active connection to the service
719
+ # is available.
720
+ def ensure_service!
721
+ fail "Must have active connection to service" unless service
722
+ end
723
+ end
724
+ end
725
+ end
726
+ end