google-cloud-firestore 0.22.0 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/README.md +8 -8
  4. data/lib/google-cloud-firestore.rb +1 -1
  5. data/lib/google/cloud/firestore.rb +46 -0
  6. data/lib/google/cloud/firestore/batch.rb +1 -1
  7. data/lib/google/cloud/firestore/client.rb +18 -13
  8. data/lib/google/cloud/firestore/convert.rb +78 -35
  9. data/lib/google/cloud/firestore/credentials.rb +2 -12
  10. data/lib/google/cloud/firestore/document_change.rb +124 -0
  11. data/lib/google/cloud/firestore/document_listener.rb +125 -0
  12. data/lib/google/cloud/firestore/document_reference.rb +35 -0
  13. data/lib/google/cloud/firestore/document_snapshot.rb +91 -9
  14. data/lib/google/cloud/firestore/field_path.rb +23 -13
  15. data/lib/google/cloud/firestore/query.rb +513 -69
  16. data/lib/google/cloud/firestore/query_listener.rb +118 -0
  17. data/lib/google/cloud/firestore/query_snapshot.rb +121 -0
  18. data/lib/google/cloud/firestore/service.rb +8 -0
  19. data/lib/google/cloud/firestore/transaction.rb +2 -2
  20. data/lib/google/cloud/firestore/v1beta1.rb +62 -37
  21. data/lib/google/cloud/firestore/v1beta1/credentials.rb +41 -0
  22. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/common.rb +1 -1
  23. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/document.rb +5 -4
  24. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/firestore.rb +1 -12
  25. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/query.rb +4 -1
  26. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/write.rb +37 -8
  27. data/lib/google/cloud/firestore/v1beta1/doc/google/protobuf/any.rb +1 -1
  28. data/lib/google/cloud/firestore/v1beta1/doc/google/protobuf/empty.rb +28 -0
  29. data/lib/google/cloud/firestore/v1beta1/doc/google/protobuf/timestamp.rb +1 -1
  30. data/lib/google/cloud/firestore/v1beta1/doc/google/protobuf/wrappers.rb +1 -1
  31. data/lib/google/cloud/firestore/v1beta1/doc/google/rpc/status.rb +1 -1
  32. data/lib/google/cloud/firestore/v1beta1/firestore_client.rb +124 -56
  33. data/lib/google/cloud/firestore/v1beta1/firestore_client_config.json +2 -2
  34. data/lib/google/cloud/firestore/version.rb +1 -1
  35. data/lib/google/cloud/firestore/watch/enumerator_queue.rb +47 -0
  36. data/lib/google/cloud/firestore/watch/inventory.rb +280 -0
  37. data/lib/google/cloud/firestore/watch/listener.rb +298 -0
  38. data/lib/google/cloud/firestore/watch/order.rb +98 -0
  39. data/lib/google/firestore/v1beta1/firestore_services_pb.rb +2 -4
  40. data/lib/google/firestore/v1beta1/query_pb.rb +1 -0
  41. data/lib/google/firestore/v1beta1/write_pb.rb +2 -0
  42. metadata +40 -3
  43. data/lib/google/cloud/firestore/v1beta1/doc/overview.rb +0 -53
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96192127f7ee4dafde2959f7d1f463fb7fa24ab09055e7764e6f83fd0aeda8be
4
- data.tar.gz: 4848935ba22ddc1b1cb9a24825a03802651d9dfc206b3d93a71af91b4e06eb8d
3
+ metadata.gz: 6c249cbd2bcfd378c5ab77ce2b5e717f629e741ca8c30b259897cfcd87ca732a
4
+ data.tar.gz: 90d335ef83762a548eefdc4ed2a35ea5c5c830a1069bd7b969e67a704496cf3b
5
5
  SHA512:
6
- metadata.gz: 523e76c8d3829de28395b9fffd54dc2cd098e08cbc204836ae6836f85f29028b36721b2f66ebb455e5d1895b9d19c1cd1c23f5aee4fffae0842a9df897ce227b
7
- data.tar.gz: 5ec65a1d3ecce996ab3a1d344495df11f84e5cce44d1932c1aea44aa877e5acf7a5e4fec9f7661ff6eb9c49a99db7c2d17109d825a0f5ee2959efe9e6bf73b34
6
+ metadata.gz: 4f8c8534c873bf3b86e8bccce74529c69ffebb5ba14e377ad60e0b32ae319ae22d8b96ff3eece237786952d169f49210168f3243a220e963b363d740c84aa54f
7
+ data.tar.gz: e0ae957a20ae64ae3c26cc6f379c12080fb8d71cf6b30bc0e4717787e7fadf785dcd5694a5d2422477639cb971da33715f21dae5f20613f490dd8a3ab5d9b1ba
data/.yardopts CHANGED
@@ -2,6 +2,7 @@
2
2
  --title=Google Cloud Firestore API
3
3
  --exclude lib/google/firestore/v1beta1
4
4
  --markup markdown
5
+ --markup-provider redcarpet
5
6
 
6
7
  ./lib/**/*.rb
7
8
  -
data/README.md CHANGED
@@ -50,14 +50,14 @@ end
50
50
 
51
51
  ## Supported Ruby Versions
52
52
 
53
- This library is supported on Ruby 2.0+.
54
-
55
- However, Ruby 2.3 or later is strongly recommended, as earlier releases have
56
- reached or are nearing end-of-life. After June 1, 2018, Google will provide
57
- official support only for Ruby versions that are considered current and
58
- supported by Ruby Core (that is, Ruby versions that are either in normal
59
- maintenance or in security maintenance).
60
- See https://www.ruby-lang.org/en/downloads/branches/ for further details.
53
+ This library is supported on Ruby 2.3+.
54
+
55
+ Google provides official support for Ruby versions that are actively supported
56
+ by Ruby Core—that is, Ruby versions that are either in normal maintenance or
57
+ in security maintenance, and not end of life. Currently, this means Ruby 2.3
58
+ and later. Older versions of Ruby _may_ still work, but are unsupported and not
59
+ recommended. See https://www.ruby-lang.org/en/downloads/branches/ for details
60
+ about the Ruby support schedule.
61
61
 
62
62
  [Client Library Documentation]: https://googlecloudplatform.github.io/google-cloud-ruby/#/docs/google-cloud-firestore/latest/google/firestore/v1beta1
63
63
  [Product Documentation]: https://cloud.google.com/firestore
@@ -76,7 +76,7 @@ module Google
76
76
  # present, the default project for the credentials is used.
77
77
  # @param [String, Hash, Google::Auth::Credentials] credentials The path to
78
78
  # the keyfile as a String, the contents of the keyfile as a Hash, or a
79
- # Google::Auth::Credentials object. (See {Datastore::Credentials})
79
+ # Google::Auth::Credentials object. (See {Firestore::Credentials})
80
80
  # @param [String, Array<String>] scope The OAuth 2.0 scopes controlling the
81
81
  # set of resources and operations that the connection can access. See
82
82
  # [Using OAuth 2.0 to Access Google
@@ -390,6 +390,52 @@ module Google
390
390
  # user_ref.update({ nested_field_path => "Pasta" })
391
391
  # ```
392
392
  #
393
+ # ### Listening for changes
394
+ #
395
+ # You can listen to a document reference or a collection reference/query for
396
+ # changes. The current document snapshot or query results snapshot will be
397
+ # yielded first, and each time the contents change.
398
+ #
399
+ # You can use {Firestore::DocumentReference#listen} to be notified of
400
+ # changes to a single document:
401
+ #
402
+ # ```ruby
403
+ # require "google/cloud/firestore"
404
+ #
405
+ # firestore = Google::Cloud::Firestore.new
406
+ #
407
+ # # Get a document reference
408
+ # nyc_ref = firestore.doc "cities/NYC"
409
+ #
410
+ # listener = nyc_ref.listen do |snapshot|
411
+ # puts "The population of #{snapshot[:name]} "
412
+ # puts "is #{snapshot[:population]}."
413
+ # end
414
+ #
415
+ # # When ready, stop the listen operation and close the stream.
416
+ # listener.stop
417
+ # ```
418
+ #
419
+ # You can use {Firestore::Query#listen} to be notified of changes to any
420
+ # document contained in the query:
421
+ #
422
+ # ```ruby
423
+ # require "google/cloud/firestore"
424
+ #
425
+ # firestore = Google::Cloud::Firestore.new
426
+ #
427
+ # # Create a query
428
+ # query = firestore.col(:cities).order(:population, :desc)
429
+ #
430
+ # listener = query.listen do |snapshot|
431
+ # puts "The query snapshot has #{snapshot.docs.count} documents "
432
+ # puts "and has #{snapshot.changes.count} changes."
433
+ # end
434
+ #
435
+ # # When ready, stop the listen operation and close the stream.
436
+ # listener.stop
437
+ # ```
438
+ #
393
439
  # ## Using transactions and batched writes
394
440
  #
395
441
  # Cloud Firestore supports atomic operations for reading and writing data.
@@ -30,7 +30,7 @@ module Google
30
30
  # atomically at a single logical point in time in a database.
31
31
  #
32
32
  # All changes are accumulated in memory until the block passed to
33
- # {Database#batch} completes. Unlike transactions, batches don't lock on
33
+ # {Client#batch} completes. Unlike transactions, batches don't lock on
34
34
  # document reads, should only fail if users provide preconditions, and are
35
35
  # not automatically retried.
36
36
  #
@@ -212,7 +212,7 @@ module Google
212
212
  ##
213
213
  # Creates a field path object representing the sentinel ID of a
214
214
  # document. It can be used in queries to sort or filter by the document
215
- # ID. See {Client#document_id}.
215
+ # ID. See {FieldPath#document_id}.
216
216
  #
217
217
  # @return [FieldPath] The field path object.
218
218
  #
@@ -225,9 +225,8 @@ module Google
225
225
  # cities_col = firestore.col "cities"
226
226
  #
227
227
  # # Create a query
228
- # query = cities_col.start_at("NYC").order(
229
- # Google::Cloud::Firestore::FieldPath.document_id
230
- # )
228
+ # query = cities_col.order(firestore.document_id)
229
+ # .start_at("NYC")
231
230
  #
232
231
  # query.get do |city|
233
232
  # puts "#{city.document_id} has #{city[:population]} residents."
@@ -383,31 +382,37 @@ module Google
383
382
  #
384
383
  def transaction max_retries: nil
385
384
  max_retries = 5 unless max_retries.is_a? Integer
386
- retries = 0
387
- backoff = 1.0
385
+ backoff = { current: 0, delay: 1.0, max: max_retries, mod: 1.3 }
388
386
 
389
387
  transaction = Transaction.from_client self
390
388
  begin
391
389
  yield transaction
392
390
  transaction.commit
393
391
  rescue Google::Cloud::UnavailableError => err
394
- # Re-raise if deadline has passed
395
- raise err if retries >= max_retries
396
- # Sleep with incremental backoff
397
- sleep(backoff *= 1.3)
392
+ # Re-raise if retried more than the max
393
+ raise err if backoff[:current] > backoff[:max]
394
+
395
+ # Sleep with incremental backoff before restarting
396
+ sleep backoff[:delay]
397
+
398
+ # Update increment backoff delay and retry counter
399
+ backoff[:delay] *= backoff[:mod]
400
+ backoff[:current] += 1
401
+
398
402
  # Create new transaction and retry
399
403
  transaction = Transaction.from_client \
400
404
  self, previous_transaction: transaction.transaction_id
401
- retries += 1
402
405
  retry
403
406
  rescue Google::Cloud::InvalidArgumentError => err
404
- # Return if a previous call has succeeded
405
- return nil if retries > 0
407
+ # Return if a previous call was retried but ultimately succeeded
408
+ return nil if backoff[:current] > 0
409
+
406
410
  # Re-raise error.
407
411
  raise err
408
412
  rescue StandardError => err
409
413
  # Rollback transaction when handling unexpected error
410
414
  transaction.rollback rescue nil
415
+
411
416
  # Re-raise error.
412
417
  raise err
413
418
  end
@@ -43,9 +43,9 @@ module Google
43
43
  Time.at timestamp.seconds, Rational(timestamp.nanos, 1000)
44
44
  end
45
45
 
46
- def fields_to_hash fields, context
46
+ def fields_to_hash fields, client
47
47
  Hash[fields.map do |key, value|
48
- [key.to_sym, value_to_raw(value, context)]
48
+ [key.to_sym, value_to_raw(value, client)]
49
49
  end]
50
50
  end
51
51
 
@@ -55,7 +55,7 @@ module Google
55
55
  end]
56
56
  end
57
57
 
58
- def value_to_raw value, context
58
+ def value_to_raw value, client
59
59
  case value.value_type
60
60
  when :null_value
61
61
  nil
@@ -73,13 +73,13 @@ module Google
73
73
  StringIO.new value.bytes_value
74
74
  when :reference_value
75
75
  Google::Cloud::Firestore::DocumentReference.from_path \
76
- value.reference_value, context
76
+ value.reference_value, client
77
77
  when :geo_point_value
78
78
  value.geo_point_value.to_hash
79
79
  when :array_value
80
- value.array_value.values.map { |v| value_to_raw v, context }
80
+ value.array_value.values.map { |v| value_to_raw v, client }
81
81
  when :map_value
82
- fields_to_hash value.map_value.fields, context
82
+ fields_to_hash value.map_value.fields, client
83
83
  end
84
84
  end
85
85
 
@@ -104,9 +104,12 @@ module Google
104
104
  Google::Firestore::V1beta1::Value.new(array_value:
105
105
  Google::Firestore::V1beta1::ArrayValue.new(values: values))
106
106
  elsif Hash === obj
107
- if obj.keys.sort == [:latitude, :longitude]
108
- Google::Firestore::V1beta1::Value.new(geo_point_value:
109
- Google::Type::LatLng.new(obj))
107
+ # keys have been changed to strings before the hash gets here
108
+ geo_pairs = hash_is_geo_point? obj
109
+ if geo_pairs
110
+ Google::Firestore::V1beta1::Value.new(
111
+ geo_point_value: hash_to_geo_point(obj, geo_pairs)
112
+ )
110
113
  else
111
114
  fields = hash_to_fields obj
112
115
  Google::Firestore::V1beta1::Value.new(map_value:
@@ -118,10 +121,30 @@ module Google
118
121
  Google::Firestore::V1beta1::Value.new bytes_value: content
119
122
  else
120
123
  raise ArgumentError,
121
- "A value of type #{obj.class} is not supported."
124
+ "A value of type #{obj.class} is not supported."
122
125
  end
123
126
  end
124
127
 
128
+ def hash_is_geo_point? hash
129
+ return false unless hash.keys.count == 2
130
+
131
+ pairs = hash.map { |k, v| [String(k), v] }.sort
132
+ if pairs.map(&:first) == ["latitude".freeze, "longitude".freeze]
133
+ pairs
134
+ end
135
+ end
136
+
137
+ def hash_to_geo_point hash, pairs = nil
138
+ pairs ||= hash_is_geo_point? hash
139
+
140
+ raise ArgumentError, "value is not a geo point" unless pairs
141
+
142
+ Google::Type::LatLng.new(
143
+ latitude: pairs.first.last,
144
+ longitude: pairs.last.last,
145
+ )
146
+ end
147
+
125
148
  def writes_for_create doc_path, data
126
149
  writes = []
127
150
 
@@ -164,13 +187,15 @@ module Google
164
187
  if merge == true
165
188
  # extract the leaf node field paths from data
166
189
  field_paths = identify_leaf_nodes data
190
+ allow_empty = true
167
191
  else
168
192
  field_paths = Array(merge).map do |field_path|
169
193
  field_path = FieldPath.parse field_path unless field_path.is_a? FieldPath
170
194
  field_path
171
195
  end
196
+ allow_empty = false
172
197
  end
173
- return writes_for_set_merge doc_path, data, field_paths
198
+ return writes_for_set_merge doc_path, data, field_paths, allow_empty
174
199
  end
175
200
 
176
201
  writes = []
@@ -195,9 +220,11 @@ module Google
195
220
  writes
196
221
  end
197
222
 
198
- def writes_for_set_merge doc_path, data, field_paths
223
+ def writes_for_set_merge doc_path, data, field_paths, allow_empty
199
224
  raise ArgumentError, "data is required" unless data.is_a? Hash
200
225
 
226
+ validate_field_paths! field_paths
227
+
201
228
  writes = []
202
229
 
203
230
  # Ensure provided field paths are valid.
@@ -231,22 +258,32 @@ module Google
231
258
  delete_valid_check = delete_valid_check.include? false
232
259
  raise ArgumentError, "deleted field not included in merge" if delete_valid_check
233
260
 
261
+ server_time_paths.select! do |server_time_path|
262
+ field_paths.any? do |field_path|
263
+ server_time_path.formatted_string.start_with? field_path.formatted_string
264
+ end
265
+ end
266
+
234
267
  # Choose only the data there are field paths for
235
268
  field_paths -= delete_paths
236
269
  field_paths -= server_time_paths
237
270
  data = select_by_field_paths data, field_paths
271
+ # Restore delete paths
272
+ field_paths += delete_paths
238
273
 
239
- if data.empty?
240
- if server_time_paths.empty?
274
+ if data.empty? && !allow_empty
275
+ if server_time_paths.empty? && delete_paths.empty?
241
276
  raise ArgumentError, "data required for set with merge"
242
277
  end
243
- else
278
+ end
279
+
280
+ if data.any? || field_paths.any? || (allow_empty && server_time_paths.empty?)
244
281
  writes << Google::Firestore::V1beta1::Write.new(
245
282
  update: Google::Firestore::V1beta1::Document.new(
246
283
  name: doc_path,
247
284
  fields: hash_to_fields(data)),
248
285
  update_mask: Google::Firestore::V1beta1::DocumentMask.new(
249
- field_paths: field_paths.map(&:formatted_string))
286
+ field_paths: field_paths.map(&:formatted_string).sort)
250
287
  )
251
288
  end
252
289
 
@@ -269,18 +306,7 @@ module Google
269
306
  end
270
307
 
271
308
  # Duplicate field paths check
272
- dup_keys = new_data_pairs.map(&:first).map(&:formatted_string)
273
- if dup_keys.size != dup_keys.uniq.size
274
- raise ArgumentError, "duplicate field paths"
275
- end
276
- dup_keys.each do |field_path|
277
- prefix_check = dup_keys.select do |this_path|
278
- this_path.start_with? "#{field_path}."
279
- end
280
- if prefix_check.any?
281
- raise ArgumentError, "one field cannot be a prefix of another"
282
- end
283
- end
309
+ validate_field_paths! new_data_pairs.map(&:first)
284
310
 
285
311
  delete_paths, new_data_pairs = new_data_pairs.partition do |field_path, value|
286
312
  value.is_a?(FieldValue) && value.type == :delete
@@ -301,10 +327,9 @@ module Google
301
327
 
302
328
  data, nested_server_time_paths = remove_field_value_from data, :server_time
303
329
 
304
- server_time_paths = root_server_time_paths + nested_server_time_paths
305
330
  server_time_paths = root_server_time_paths + nested_server_time_paths
306
331
 
307
- field_paths = (field_paths - (field_paths - identify_all_file_paths(data)) + delete_paths).uniq
332
+ field_paths = (field_paths + delete_paths).uniq
308
333
  field_paths.each do |field_path|
309
334
  raise ArgumentError, "empty paths not allowed" if field_path.fields.empty?
310
335
  end
@@ -319,7 +344,7 @@ module Google
319
344
  name: doc_path,
320
345
  fields: hash_to_fields(data)),
321
346
  update_mask: Google::Firestore::V1beta1::DocumentMask.new(
322
- field_paths: field_paths.map(&:formatted_string)),
347
+ field_paths: (field_paths).map(&:formatted_string).sort),
323
348
  current_document: Google::Firestore::V1beta1::Precondition.new(
324
349
  exists: true)
325
350
  )
@@ -464,13 +489,15 @@ module Google
464
489
  tmp_hash = ret_hash
465
490
  prev_hash = ret_hash
466
491
  dup_hash = hash.dup
467
- fields = field_path.fields
492
+ fields = field_path.fields.dup
468
493
  last_field = nil
469
494
 
470
495
  # squash fields until the key exists?
471
- until dup_hash.key? fields.first
472
- fields.unshift "#{fields.shift}.#{fields.shift}"
473
- break if fields.count <= 1
496
+ if fields.count > 1
497
+ until dup_hash.key? fields.first
498
+ fields.unshift "#{fields.shift}.#{fields.shift}"
499
+ break if fields.count <= 1
500
+ end
474
501
  end
475
502
 
476
503
  fields.each do |field|
@@ -482,9 +509,25 @@ module Google
482
509
  dup_hash = dup_hash[field]
483
510
  end
484
511
  prev_hash[last_field] = dup_hash
512
+ prev_hash.delete_if { |_k, v| v.nil? }
485
513
  ret_hash
486
514
  end
487
515
 
516
+ def validate_field_paths! field_paths
517
+ field_paths_strings = field_paths.map(&:formatted_string)
518
+ if field_paths_strings.size != field_paths_strings.uniq.size
519
+ raise ArgumentError, "duplicate field paths"
520
+ end
521
+ field_paths_strings.each do |field_path|
522
+ prefix_check = field_paths_strings.select do |this_path|
523
+ this_path.start_with? "#{field_path}."
524
+ end
525
+ if prefix_check.any?
526
+ raise ArgumentError, "one field cannot be a prefix of another"
527
+ end
528
+ end
529
+ end
530
+
488
531
  def deep_merge_hashes left_hash, right_hash
489
532
  right_hash.each_pair do |key, right_value|
490
533
  left_value = left_hash[key]
@@ -13,7 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
 
16
- require "googleauth"
16
+ require "google/cloud/firestore/v1beta1/credentials"
17
17
 
18
18
  module Google
19
19
  module Cloud
@@ -37,17 +37,7 @@ module Google
37
37
  #
38
38
  # firestore.project_id #=> "my-project"
39
39
  #
40
- class Credentials < Google::Auth::Credentials
41
- SCOPE = ["https://www.googleapis.com/auth/datastore"].freeze
42
- PATH_ENV_VARS = %w[FIRESTORE_CREDENTIALS FIRESTORE_KEYFILE
43
- GOOGLE_CLOUD_CREDENTIALS GOOGLE_CLOUD_KEYFILE
44
- GCLOUD_KEYFILE].freeze
45
- JSON_ENV_VARS = %w[FIRESTORE_CREDENTIALS_JSON FIRESTORE_KEYFILE_JSON
46
- GOOGLE_CLOUD_CREDENTIALS_JSON
47
- GOOGLE_CLOUD_KEYFILE_JSON
48
- GCLOUD_KEYFILE_JSON].freeze
49
- DEFAULT_PATHS = \
50
- ["~/.config/gcloud/application_default_credentials.json"].freeze
40
+ class Credentials < Google::Cloud::Firestore::V1beta1::Credentials
51
41
  end
52
42
  end
53
43
  end
@@ -0,0 +1,124 @@
1
+ # Copyright 2018 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
+ module Google
17
+ module Cloud
18
+ module Firestore
19
+ ##
20
+ # # DocumentChange
21
+ #
22
+ # A DocumentChange object represents a change to the document matching a
23
+ # query. It contains the document affected and the type of change that
24
+ # occurred (added, modifed, or removed).
25
+ #
26
+ # See {Query#listen} and {QuerySnapshot#changes}.
27
+ #
28
+ # @example
29
+ # require "google/cloud/firestore"
30
+ #
31
+ # firestore = Google::Cloud::Firestore.new
32
+ #
33
+ # # Create a query
34
+ # query = firestore.col(:cities).order(:population, :desc)
35
+ #
36
+ # listener = query.listen do |snapshot|
37
+ # puts "The query snapshot has #{snapshot.docs.count} documents "
38
+ # puts "and has #{snapshot.changes.count} changes."
39
+ # end
40
+ #
41
+ # # When ready, stop the listen operation and close the stream.
42
+ # listener.stop
43
+ #
44
+ class DocumentChange
45
+ ##
46
+ # The document snapshot object for the data.
47
+ #
48
+ # @return [DocumentSnapshot] document snapshot.
49
+ #
50
+ def doc
51
+ @doc
52
+ end
53
+ alias document doc
54
+
55
+ ##
56
+ # The type of change (':added', ':modified', or ':removed').
57
+ #
58
+ # @return [Symbol] The type of change.
59
+ #
60
+ def type
61
+ return :removed if @new_index.nil?
62
+ return :added if @old_index.nil?
63
+ :modified
64
+ end
65
+
66
+ ##
67
+ # Determines whether the document was added.
68
+ #
69
+ # @return [Boolean] Whether the document was added.
70
+ #
71
+ def added?
72
+ type == :added
73
+ end
74
+
75
+ ##
76
+ # Determines whether the document was modified.
77
+ #
78
+ # @return [Boolean] Whether the document was modified.
79
+ #
80
+ def modified?
81
+ type == :modified
82
+ end
83
+
84
+ ##
85
+ # Determines whether the document was removed.
86
+ #
87
+ # @return [Boolean] Whether the document was removed.
88
+ #
89
+ def removed?
90
+ type == :removed
91
+ end
92
+
93
+ ##
94
+ # The index in the documents array prior to the change.
95
+ #
96
+ # @return [Integer, nil] The old index
97
+ #
98
+ def old_index
99
+ @old_index
100
+ end
101
+
102
+ ##
103
+ # The index in the documents array after the change.
104
+ #
105
+ # @return [Integer, nil] The new index
106
+ #
107
+ def new_index
108
+ @new_index
109
+ end
110
+
111
+ ##
112
+ # @private New DocumentChange from a
113
+ # Google::Cloud::Firestore::DocumentSnapshot object.
114
+ def self.from_doc doc, old_index, new_index
115
+ new.tap do |s|
116
+ s.instance_variable_set :@doc, doc
117
+ s.instance_variable_set :@old_index, old_index
118
+ s.instance_variable_set :@new_index, new_index
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end