google-cloud-firestore 2.3.0 → 2.6.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
  SHA256:
3
- metadata.gz: 12a9c7f721487eef84a1d703f77f0b2f56aea624fc9c6e1052be059c49437a6f
4
- data.tar.gz: 76d354bb9933a7a83ef6163fef5bd37da82808786f05d06a83f54dc605ca1154
3
+ metadata.gz: 3141b004673c1db562f162d9218ac680c0693e4d91a8eb48b7d17f444646e74a
4
+ data.tar.gz: 4ab2bb6f69da5f8ad2e53464ae3712647b3642d49a465dc5f75d1dbc923979f5
5
5
  SHA512:
6
- metadata.gz: b8e403df8684221a0521432017c511f8b0c4d174c97c95129f7952e6751914093fe692585127d2f0a267f9776b9a464062bd10e3f3a7d9aa7c772edcddde8d2e
7
- data.tar.gz: bd858f3f1c199b835ce22f567068af585f9ce41d2cd9da6811f5d06bbcc45ab1aadaa40575d1684bb2245d691c90589df420343a0233039f860030513037de75
6
+ metadata.gz: fd5dccbe067d6fea81556aae40b9fc9de2ca75fc389c919335217371d84578b68ad7c9854c84831bb368dd58982849c06f044cef0c864be586cdace0ea48994a
7
+ data.tar.gz: 7a7f0ee2a3259f2a65119b0b31dbf781d7336f7c530301f4c7f0a7fdf95fea8a7773caa4ed172c9ce8c5ff9f9fe80dab830f37ef1e6d204ae7d770fdf9c066c0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Release History
2
2
 
3
+ ### 2.6.0 / 2021-06-15
4
+
5
+ #### Features
6
+
7
+ * Add support for Query Partitions
8
+ * Add CollectionGroup
9
+ * Update Client#col_group to return CollectionGroup (subclass of Query)
10
+ * Add QueryPartition
11
+ * Add QueryPartition::List
12
+ * Add Query#to_json and Query.from_json
13
+
14
+ ### 2.5.1 / 2021-04-19
15
+
16
+ #### Bug Fixes
17
+
18
+ * Add GRPC::Unknown to retryable errors in Watch::Listener
19
+
20
+ ### 2.5.0 / 2021-03-10
21
+
22
+ #### Features
23
+
24
+ * Drop support for Ruby 2.4 and add support for Ruby 3.0
25
+
26
+ ### 2.4.1 / 2021-01-06
27
+
28
+ #### Bug Fixes
29
+
30
+ * Replace usage of Write.transform with Write.update_transforms
31
+
32
+ ### 2.4.0 / 2020-11-19
33
+
34
+ #### Features
35
+
36
+ * add support for != and NOT_IN queries
37
+
3
38
  ### 2.3.0 / 2020-09-30
4
39
 
5
40
  #### Features
data/CONTRIBUTING.md CHANGED
@@ -24,7 +24,7 @@ be able to accept your pull requests.
24
24
  In order to use the google-cloud-firestore console and run the project's tests,
25
25
  there is a small amount of setup:
26
26
 
27
- 1. Install Ruby. google-cloud-firestore requires Ruby 2.4+. You may choose to
27
+ 1. Install Ruby. google-cloud-firestore requires Ruby 2.5+. You may choose to
28
28
  manage your Ruby and gem installations with [RVM](https://rvm.io/),
29
29
  [rbenv](https://github.com/rbenv/rbenv), or
30
30
  [chruby](https://github.com/postmodern/chruby).
@@ -45,7 +45,7 @@ there is a small amount of setup:
45
45
 
46
46
  ```sh
47
47
  $ cd google-cloud-firestore/
48
- $ bundle exec rake bundleupdate
48
+ $ bundle install
49
49
  ```
50
50
 
51
51
  ## Console
@@ -119,15 +119,14 @@ If you alter an example's title, you may encounter breaking tests.
119
119
  ### Firestore Acceptance Tests
120
120
 
121
121
  The Firestore acceptance tests interact with the live service API. Follow the
122
- instructions in the {file:AUTHENTICATION.md Authentication guide} for enabling
122
+ instructions in the {file:AUTHENTICATION.md Authentication Guide} for enabling
123
123
  the Firestore API. Occasionally, some API features may not yet be generally
124
124
  available, making it difficult for some contributors to successfully run the
125
125
  entire acceptance test suite. However, please ensure that you do successfully
126
126
  run acceptance tests for any code areas covered by your pull request.
127
127
 
128
128
  To run the acceptance tests, first create and configure a project in the Google
129
- Developers Console, as described in the {file:AUTHENTICATION.md Authentication
130
- guide}. Be sure to download the JSON KEY file. Make note of the PROJECT_ID and
129
+ Developers Console, as described in the {file:AUTHENTICATION.md Authentication Guide}. Be sure to download the JSON KEY file. Make note of the PROJECT_ID and
131
130
  the KEYFILE location on your system.
132
131
 
133
132
  Before you can run the Firestore acceptance tests, you must first create indexes
data/LOGGING.md CHANGED
@@ -3,7 +3,7 @@
3
3
  To enable logging for this library, set the logger for the underlying
4
4
  [gRPC](https://github.com/grpc/grpc/tree/master/src/ruby) library. The logger
5
5
  that you set may be a Ruby stdlib
6
- [`Logger`](https://ruby-doc.org/stdlib-2.5.0/libdoc/logger/rdoc/Logger.html) as
6
+ [`Logger`](https://ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger.html) as
7
7
  shown below, or a
8
8
  [`Google::Cloud::Logging::Logger`](https://googleapis.dev/ruby/google-cloud-logging/latest)
9
9
  that will write logs to [Stackdriver
@@ -119,7 +119,7 @@ module Google
119
119
 
120
120
  doc_path = coalesce_doc_path_argument doc
121
121
 
122
- @writes << Convert.writes_for_create(doc_path, data)
122
+ @writes << Convert.write_for_create(doc_path, data)
123
123
 
124
124
  nil
125
125
  end
@@ -218,7 +218,7 @@ module Google
218
218
 
219
219
  doc_path = coalesce_doc_path_argument doc
220
220
 
221
- @writes << Convert.writes_for_set(doc_path, data, merge: merge)
221
+ @writes << Convert.write_for_set(doc_path, data, merge: merge)
222
222
 
223
223
  nil
224
224
  end
@@ -322,8 +322,7 @@ module Google
322
322
 
323
323
  doc_path = coalesce_doc_path_argument doc
324
324
 
325
- @writes << Convert.writes_for_update(doc_path, data,
326
- update_time: update_time)
325
+ @writes << Convert.write_for_update(doc_path, data, update_time: update_time)
327
326
 
328
327
  nil
329
328
  end
@@ -20,6 +20,7 @@ require "google/cloud/firestore/field_value"
20
20
  require "google/cloud/firestore/collection_reference"
21
21
  require "google/cloud/firestore/document_reference"
22
22
  require "google/cloud/firestore/document_snapshot"
23
+ require "google/cloud/firestore/collection_group"
23
24
  require "google/cloud/firestore/batch"
24
25
  require "google/cloud/firestore/transaction"
25
26
 
@@ -100,11 +101,11 @@ module Google
100
101
  # puts col.collection_id
101
102
  # end
102
103
  #
103
- def cols
104
+ def cols &block
104
105
  ensure_service!
105
106
  grpc = service.list_collections "#{path}/documents"
106
107
  cols_enum = CollectionReferenceList.from_grpc(grpc, self, "#{path}/documents").all
107
- cols_enum.each { |c| yield c } if block_given?
108
+ cols_enum.each(&block) if block_given?
108
109
  cols_enum
109
110
  end
110
111
  alias collections cols
@@ -139,7 +140,7 @@ module Google
139
140
  alias collection col
140
141
 
141
142
  ##
142
- # Creates and returns a new Query that includes all documents in the
143
+ # Creates and returns a new collection group that includes all documents in the
143
144
  # database that are contained in a collection or subcollection with the
144
145
  # given collection_id.
145
146
  #
@@ -147,7 +148,7 @@ module Google
147
148
  # over. Every collection or subcollection with this ID as the last
148
149
  # segment of its path will be included. Cannot contain a slash (`/`).
149
150
  #
150
- # @return [Query] The created Query.
151
+ # @return [CollectionGroup] The created collection group.
151
152
  #
152
153
  # @example
153
154
  # require "google/cloud/firestore"
@@ -155,9 +156,9 @@ module Google
155
156
  # firestore = Google::Cloud::Firestore.new
156
157
  #
157
158
  # # Get the cities collection group query
158
- # query = firestore.col_group "cities"
159
+ # col_group = firestore.col_group "cities"
159
160
  #
160
- # query.get do |city|
161
+ # col_group.get do |city|
161
162
  # puts "#{city.document_id} has #{city[:population]} residents."
162
163
  # end
163
164
  #
@@ -166,15 +167,8 @@ module Google
166
167
  raise ArgumentError, "Invalid collection_id: '#{collection_id}', " \
167
168
  "must not contain '/'."
168
169
  end
169
- query = Google::Cloud::Firestore::V1::StructuredQuery.new(
170
- from: [
171
- Google::Cloud::Firestore::V1::StructuredQuery::CollectionSelector.new(
172
- collection_id: collection_id, all_descendants: true
173
- )
174
- ]
175
- )
176
-
177
- Query.start query, service.documents_path, self
170
+
171
+ CollectionGroup.from_collection_id service.documents_path, collection_id, self
178
172
  end
179
173
  alias collection_group col_group
180
174
 
@@ -256,7 +250,7 @@ module Google
256
250
  ensure_service!
257
251
 
258
252
  unless block_given?
259
- return enum_for :get_all, docs, field_mask: field_mask
253
+ return enum_for :get_all, *docs, field_mask: field_mask
260
254
  end
261
255
 
262
256
  doc_paths = Array(docs).flatten.map do |doc_path|
@@ -634,9 +628,9 @@ module Google
634
628
  commit_return = transaction.commit
635
629
  # Conditional return value, depending on truthy commit_response
636
630
  commit_response ? commit_return : transaction_return
637
- rescue Google::Cloud::UnavailableError => err
631
+ rescue Google::Cloud::UnavailableError => e
638
632
  # Re-raise if retried more than the max
639
- raise err if backoff[:current] > backoff[:max]
633
+ raise e if backoff[:current] > backoff[:max]
640
634
 
641
635
  # Sleep with incremental backoff before restarting
642
636
  sleep backoff[:delay]
@@ -649,18 +643,18 @@ module Google
649
643
  transaction = Transaction.from_client \
650
644
  self, previous_transaction: transaction.transaction_id
651
645
  retry
652
- rescue Google::Cloud::InvalidArgumentError => err
646
+ rescue Google::Cloud::InvalidArgumentError => e
653
647
  # Return if a previous call was retried but ultimately succeeded
654
- return nil if backoff[:current] > 0
648
+ return nil if backoff[:current].positive?
655
649
 
656
650
  # Re-raise error.
657
- raise err
658
- rescue StandardError => err
651
+ raise e
652
+ rescue StandardError => e
659
653
  # Rollback transaction when handling unexpected error
660
654
  transaction.rollback rescue nil
661
655
 
662
656
  # Re-raise error.
663
- raise err
657
+ raise e
664
658
  end
665
659
  end
666
660
 
@@ -0,0 +1,136 @@
1
+ # Copyright 2021 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/v1"
17
+ require "google/cloud/firestore/query"
18
+ require "google/cloud/firestore/query_partition"
19
+
20
+ module Google
21
+ module Cloud
22
+ module Firestore
23
+ ##
24
+ # # CollectionGroup
25
+ #
26
+ # A collection group object is used for adding documents, getting
27
+ # document references, and querying for documents, including with partitions.
28
+ #
29
+ # See {Client#col_group} and {Query}.
30
+ #
31
+ # @example
32
+ # require "google/cloud/firestore"
33
+ #
34
+ # firestore = Google::Cloud::Firestore.new
35
+ #
36
+ # # Get a collection group
37
+ # col_group = firestore.col_group "cities"
38
+ #
39
+ # # Get and print all city documents
40
+ # col_group.get do |city|
41
+ # puts "#{city.document_id} has #{city[:population]} residents."
42
+ # end
43
+ #
44
+ class CollectionGroup < Query
45
+ ##
46
+ # Partitions a query by returning partition cursors that can be used to run the query in parallel. The returned
47
+ # partition cursors are split points that can be used as starting/end points for the query results.
48
+ #
49
+ # @param [Integer] partition_count The desired maximum number of partition points. The number must be strictly
50
+ # positive. The actual number of partitions returned may be fewer.
51
+ #
52
+ # @return [Array<QueryPartition>] An ordered array of query partitions.
53
+ #
54
+ # @example
55
+ # require "google/cloud/firestore"
56
+ #
57
+ # firestore = Google::Cloud::Firestore.new
58
+ #
59
+ # col_group = firestore.col_group "cities"
60
+ #
61
+ # partitions = col_group.partitions 3
62
+ #
63
+ # queries = partitions.map(&:to_query)
64
+ #
65
+ def partitions partition_count
66
+ ensure_service!
67
+
68
+ raise ArgumentError, "partition_count must be > 0" unless partition_count.positive?
69
+
70
+ # Partition queries require explicit ordering by __name__.
71
+ query_with_default_order = order "__name__"
72
+ # Since we are always returning an extra partition (with en empty endBefore cursor), we reduce the desired
73
+ # partition count by one.
74
+ partition_count -= 1
75
+
76
+ grpc_partitions = if partition_count.positive?
77
+ # Retrieve all pages, since cursor order is not guaranteed and they must be sorted.
78
+ list_all partition_count, query_with_default_order
79
+ else
80
+ [] # Ensure that a single, empty QueryPartition is returned.
81
+ end
82
+ cursor_values = grpc_partitions.map do |cursor|
83
+ # Convert each cursor to a (single-element) array of Google::Cloud::Firestore::DocumentReference.
84
+ cursor.values.map do |value|
85
+ Convert.value_to_raw value, client
86
+ end
87
+ end
88
+ # Sort the values of the returned cursor, which right now should only contain a single reference value (which
89
+ # needs to be sorted one component at a time).
90
+ cursor_values.sort! do |a, b|
91
+ a.first <=> b.first
92
+ end
93
+
94
+ start_at = nil
95
+ results = cursor_values.map do |end_before|
96
+ partition = QueryPartition.new query_with_default_order, start_at, end_before
97
+ start_at = end_before
98
+ partition
99
+ end
100
+ # Always add a final QueryPartition with an empty end_before value.
101
+ results << QueryPartition.new(query_with_default_order, start_at, nil)
102
+ results
103
+ end
104
+
105
+ ##
106
+ # @private New Collection group object from a path.
107
+ def self.from_collection_id parent_path, collection_id, client
108
+ query = Google::Cloud::Firestore::V1::StructuredQuery.new(
109
+ from: [
110
+ Google::Cloud::Firestore::V1::StructuredQuery::CollectionSelector.new(
111
+ collection_id: collection_id,
112
+ all_descendants: true
113
+ )
114
+ ]
115
+ )
116
+ CollectionGroup.new query, parent_path, client
117
+ end
118
+
119
+ protected
120
+
121
+ def list_all partition_count, query_with_default_order
122
+ grpc_partitions = []
123
+ token = nil
124
+ loop do
125
+ grpc = service.partition_query parent_path, query_with_default_order.query, partition_count, token: token
126
+ grpc_partitions += Array(grpc.partitions)
127
+ token = grpc.next_page_token
128
+ token = nil if token == ""
129
+ break unless token
130
+ end
131
+ grpc_partitions
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -14,9 +14,9 @@
14
14
 
15
15
 
16
16
  require "google/cloud/firestore/v1"
17
+ require "google/cloud/firestore/query"
17
18
  require "google/cloud/firestore/document_reference"
18
19
  require "google/cloud/firestore/document_snapshot"
19
- require "google/cloud/firestore/query"
20
20
  require "google/cloud/firestore/generate"
21
21
  require "google/cloud/firestore/collection_reference_list"
22
22
 
@@ -47,6 +47,13 @@ module Google
47
47
  # @private The firestore client object.
48
48
  attr_accessor :client
49
49
 
50
+ ##
51
+ # @private Creates a new CollectionReference.
52
+ def initialize query, path, client
53
+ super query, nil, client # Pass nil parent_path arg since this class implements #parent_path
54
+ @path = path
55
+ end
56
+
50
57
  ##
51
58
  # The collection identifier for the collection resource.
52
59
  #
@@ -257,11 +264,7 @@ module Google
257
264
  ]
258
265
  )
259
266
 
260
- new.tap do |c|
261
- c.client = client
262
- c.instance_variable_set :@path, path
263
- c.instance_variable_set :@query, query
264
- end
267
+ CollectionReference.new query, path, client
265
268
  end
266
269
 
267
270
  protected
@@ -90,17 +90,17 @@ module Google
90
90
  # collection_reference.collection_id
91
91
  # end
92
92
  #
93
- def all request_limit: nil
93
+ def all request_limit: nil, &block
94
94
  request_limit = request_limit.to_i if request_limit
95
95
  unless block_given?
96
96
  return enum_for :all, request_limit: request_limit
97
97
  end
98
98
  results = self
99
99
  loop do
100
- results.each { |r| yield r }
100
+ results.each(&block)
101
101
  if request_limit
102
102
  request_limit -= 1
103
- break if request_limit < 0
103
+ break if request_limit.negative?
104
104
  end
105
105
  break unless results.next?
106
106
  results = results.next
@@ -24,7 +24,12 @@ module Google
24
24
  ##
25
25
  # @private Helper module for converting Protobuf values.
26
26
  module Convert
27
- # rubocop:disable all
27
+ # rubocop:disable Metrics/AbcSize
28
+ # rubocop:disable Metrics/BlockLength
29
+ # rubocop:disable Metrics/CyclomaticComplexity
30
+ # rubocop:disable Metrics/MethodLength
31
+ # rubocop:disable Metrics/ModuleLength
32
+ # rubocop:disable Metrics/PerceivedComplexity
28
33
  module ClassMethods
29
34
  def time_to_timestamp time
30
35
  return nil if time.nil?
@@ -32,9 +37,10 @@ module Google
32
37
  # Force the object to be a Time object.
33
38
  time = time.to_time
34
39
 
35
- Google::Protobuf::Timestamp.new \
40
+ Google::Protobuf::Timestamp.new(
36
41
  seconds: time.to_i,
37
- nanos: time.nsec
42
+ nanos: time.nsec
43
+ )
38
44
  end
39
45
 
40
46
  def timestamp_to_time timestamp
@@ -100,9 +106,10 @@ module Google
100
106
  elsif Google::Cloud::Firestore::DocumentReference === obj
101
107
  Google::Cloud::Firestore::V1::Value.new reference_value: obj.path
102
108
  elsif Array === obj
103
- values = obj.map { |o| raw_to_value(o) }
104
- Google::Cloud::Firestore::V1::Value.new(array_value:
105
- Google::Cloud::Firestore::V1::ArrayValue.new(values: values))
109
+ values = obj.map { |o| raw_to_value o }
110
+ Google::Cloud::Firestore::V1::Value.new(
111
+ array_value: Google::Cloud::Firestore::V1::ArrayValue.new(values: values)
112
+ )
106
113
  elsif Hash === obj
107
114
  # keys have been changed to strings before the hash gets here
108
115
  geo_pairs = hash_is_geo_point? obj
@@ -112,8 +119,9 @@ module Google
112
119
  )
113
120
  else
114
121
  fields = hash_to_fields obj
115
- Google::Cloud::Firestore::V1::Value.new(map_value:
116
- Google::Cloud::Firestore::V1::MapValue.new(fields: fields))
122
+ Google::Cloud::Firestore::V1::Value.new(
123
+ map_value: Google::Cloud::Firestore::V1::MapValue.new(fields: fields)
124
+ )
117
125
  end
118
126
  elsif obj.respond_to?(:read) && obj.respond_to?(:rewind)
119
127
  obj.rewind
@@ -129,9 +137,7 @@ module Google
129
137
  return false unless hash.keys.count == 2
130
138
 
131
139
  pairs = hash.map { |k, v| [String(k), v] }.sort
132
- if pairs.map(&:first) == ["latitude", "longitude"]
133
- pairs
134
- end
140
+ pairs if pairs.map(&:first) == ["latitude", "longitude"]
135
141
  end
136
142
 
137
143
  def hash_to_geo_point hash, pairs = nil
@@ -140,47 +146,39 @@ module Google
140
146
  raise ArgumentError, "value is not a geo point" unless pairs
141
147
 
142
148
  Google::Type::LatLng.new(
143
- latitude: pairs.first.last,
144
- longitude: pairs.last.last,
149
+ latitude: pairs.first.last,
150
+ longitude: pairs.last.last
145
151
  )
146
152
  end
147
153
 
148
- def writes_for_create doc_path, data
149
- writes = []
150
-
151
- if is_field_value_nested data, :delete
154
+ def write_for_create doc_path, data
155
+ if field_value_nested? data, :delete
152
156
  raise ArgumentError, "DELETE not allowed on create"
153
157
  end
154
158
  raise ArgumentError, "data is required" unless data.is_a? Hash
155
159
 
156
160
  data, field_paths_and_values = remove_field_value_from data
157
161
 
158
- if data.any? || field_paths_and_values.empty?
159
- write = Google::Cloud::Firestore::V1::Write.new(
160
- update: Google::Cloud::Firestore::V1::Document.new(
161
- name: doc_path,
162
- fields: hash_to_fields(data)),
163
- current_document: Google::Cloud::Firestore::V1::Precondition.new(
164
- exists: false)
165
- )
166
- writes << write
167
- end
168
-
169
- if field_paths_and_values.any?
170
- transform_write = transform_write doc_path, field_paths_and_values
171
-
172
- if data.empty?
173
- transform_write.current_document = \
174
- Google::Cloud::Firestore::V1::Precondition.new(exists: false)
175
- end
176
-
177
- writes << transform_write
178
- end
162
+ doc = Google::Cloud::Firestore::V1::Document.new(
163
+ name: doc_path,
164
+ fields: hash_to_fields(data)
165
+ )
166
+ precondition = Google::Cloud::Firestore::V1::Precondition.new exists: false
167
+ Google::Cloud::Firestore::V1::Write.new(
168
+ update: doc,
169
+ current_document: precondition,
170
+ update_transforms: field_transforms(field_paths_and_values)
171
+ )
172
+ end
179
173
 
180
- writes
174
+ def field_transforms paths
175
+ return nil if paths.empty?
176
+ paths.map do |field_path, field_value|
177
+ to_field_transform field_path, field_value
178
+ end.to_a
181
179
  end
182
180
 
183
- def writes_for_set doc_path, data, merge: nil
181
+ def write_for_set doc_path, data, merge: nil
184
182
  raise ArgumentError, "data is required" unless data.is_a? Hash
185
183
 
186
184
  if merge
@@ -195,11 +193,9 @@ module Google
195
193
  end
196
194
  allow_empty = false
197
195
  end
198
- return writes_for_set_merge doc_path, data, field_paths, allow_empty
196
+ return write_for_set_merge doc_path, data, field_paths, allow_empty
199
197
  end
200
198
 
201
- writes = []
202
-
203
199
  data, delete_paths = remove_field_value_from data, :delete
204
200
  if delete_paths.any?
205
201
  raise ArgumentError, "DELETE not allowed on set"
@@ -207,30 +203,25 @@ module Google
207
203
 
208
204
  data, field_paths_and_values = remove_field_value_from data
209
205
 
210
- writes << Google::Cloud::Firestore::V1::Write.new(
211
- update: Google::Cloud::Firestore::V1::Document.new(
212
- name: doc_path,
213
- fields: hash_to_fields(data))
206
+ doc = Google::Cloud::Firestore::V1::Document.new(
207
+ name: doc_path,
208
+ fields: hash_to_fields(data)
209
+ )
210
+ Google::Cloud::Firestore::V1::Write.new(
211
+ update: doc,
212
+ update_transforms: field_transforms(field_paths_and_values)
214
213
  )
215
-
216
- if field_paths_and_values.any?
217
- writes << transform_write(doc_path, field_paths_and_values)
218
- end
219
-
220
- writes
221
214
  end
222
215
 
223
- def writes_for_set_merge doc_path, data, field_paths, allow_empty
216
+ def write_for_set_merge doc_path, data, field_paths, allow_empty
224
217
  raise ArgumentError, "data is required" unless data.is_a? Hash
225
218
 
226
219
  validate_field_paths! field_paths
227
220
 
228
- writes = []
229
-
230
221
  # Ensure provided field paths are valid.
231
222
  all_valid = identify_leaf_nodes data
232
223
  all_valid_check = field_paths.map do |verify_path|
233
- if all_valid.include?(verify_path)
224
+ if all_valid.include? verify_path
234
225
  true
235
226
  else
236
227
  found_in_all_valid = all_valid.select do |fp|
@@ -271,32 +262,25 @@ module Google
271
262
  # Restore delete paths
272
263
  field_paths += delete_field_paths_and_values.keys
273
264
 
274
- if data.empty? && !allow_empty
275
- if field_paths_and_values.empty? && delete_field_paths_and_values.empty?
276
- raise ArgumentError, "data required for set with merge"
277
- end
265
+ if data.empty? && !allow_empty && field_paths_and_values.empty? && delete_field_paths_and_values.empty?
266
+ raise ArgumentError, "data required for set with merge"
278
267
  end
279
268
 
280
- if data.any? || field_paths.any? || (allow_empty && field_paths_and_values.empty?)
281
- writes << Google::Cloud::Firestore::V1::Write.new(
282
- update: Google::Cloud::Firestore::V1::Document.new(
283
- name: doc_path,
284
- fields: hash_to_fields(data)),
285
- update_mask: Google::Cloud::Firestore::V1::DocumentMask.new(
286
- field_paths: field_paths.map(&:formatted_string).sort)
287
- )
288
- end
289
-
290
- if field_paths_and_values.any?
291
- writes << transform_write(doc_path, field_paths_and_values)
292
- end
293
-
294
- writes
269
+ doc = Google::Cloud::Firestore::V1::Document.new(
270
+ name: doc_path,
271
+ fields: hash_to_fields(data)
272
+ )
273
+ doc_mask = Google::Cloud::Firestore::V1::DocumentMask.new(
274
+ field_paths: field_paths.map(&:formatted_string).sort
275
+ )
276
+ Google::Cloud::Firestore::V1::Write.new(
277
+ update: doc,
278
+ update_mask: doc_mask,
279
+ update_transforms: field_transforms(field_paths_and_values)
280
+ )
295
281
  end
296
282
 
297
- def writes_for_update doc_path, data, update_time: nil
298
- writes = []
299
-
283
+ def write_for_update doc_path, data, update_time: nil
300
284
  raise ArgumentError, "data is required" unless data.is_a? Hash
301
285
 
302
286
  # Convert data to use FieldPath
@@ -308,11 +292,11 @@ module Google
308
292
  # Duplicate field paths check
309
293
  validate_field_paths! new_data_pairs.map(&:first)
310
294
 
311
- delete_paths, new_data_pairs = new_data_pairs.partition do |field_path, value|
295
+ delete_paths, new_data_pairs = new_data_pairs.partition do |_field_path, value|
312
296
  value.is_a?(FieldValue) && value.type == :delete
313
297
  end
314
298
 
315
- root_field_paths_and_values, new_data_pairs = new_data_pairs.partition do |field_path, value|
299
+ root_field_paths_and_values, new_data_pairs = new_data_pairs.partition do |_field_path, value|
316
300
  value.is_a? FieldValue
317
301
  end
318
302
 
@@ -325,7 +309,7 @@ module Google
325
309
  data, nested_deletes = remove_field_value_from data, :delete
326
310
  raise ArgumentError, "DELETE cannot be nested" if nested_deletes.any?
327
311
 
328
- data, nested_field_paths_and_values = remove_field_value_from data
312
+ data, nested_field_paths_and_values = remove_field_value_from data
329
313
 
330
314
  field_paths_and_values = root_field_paths_and_values.merge nested_field_paths_and_values
331
315
 
@@ -338,34 +322,31 @@ module Google
338
322
  raise ArgumentError, "data is required"
339
323
  end
340
324
 
325
+ write = Google::Cloud::Firestore::V1::Write.new(
326
+ update: Google::Cloud::Firestore::V1::Document.new(name: doc_path),
327
+ update_mask: Google::Cloud::Firestore::V1::DocumentMask.new,
328
+ current_document: Google::Cloud::Firestore::V1::Precondition.new(exists: true)
329
+ )
330
+
341
331
  if data.any? || delete_paths.any?
342
- write = Google::Cloud::Firestore::V1::Write.new(
343
- update: Google::Cloud::Firestore::V1::Document.new(
344
- name: doc_path,
345
- fields: hash_to_fields(data)),
346
- update_mask: Google::Cloud::Firestore::V1::DocumentMask.new(
347
- field_paths: (field_paths).map(&:formatted_string).sort),
348
- current_document: Google::Cloud::Firestore::V1::Precondition.new(
349
- exists: true)
350
- )
332
+ htf = hash_to_fields data
333
+ htf.each_pair do |k, v|
334
+ write.update.fields[k] = v
335
+ end
336
+ write.update_mask.field_paths += field_paths.map(&:formatted_string).sort
337
+
351
338
  if update_time
352
- write.current_document = \
353
- Google::Cloud::Firestore::V1::Precondition.new(
354
- update_time: time_to_timestamp(update_time))
339
+ write.current_document = Google::Cloud::Firestore::V1::Precondition.new(
340
+ update_time: time_to_timestamp(update_time)
341
+ )
355
342
  end
356
- writes << write
357
343
  end
358
344
 
359
345
  if field_paths_and_values.any?
360
- transform_write = transform_write doc_path, field_paths_and_values
361
- if data.empty?
362
- transform_write.current_document = \
363
- Google::Cloud::Firestore::V1::Precondition.new(exists: true)
364
- end
365
- writes << transform_write
346
+ write.update_transforms += field_transforms field_paths_and_values
366
347
  end
367
348
 
368
- writes
349
+ write
369
350
  end
370
351
 
371
352
  def write_for_delete doc_path, exists: nil, update_time: nil
@@ -387,13 +368,20 @@ module Google
387
368
  write
388
369
  end
389
370
 
390
- def is_field_value_nested obj, field_value_type = nil
371
+ def field_value_nested? obj, field_value_type = nil
391
372
  return obj if obj.is_a?(FieldValue) && (field_value_type.nil? || obj.type == field_value_type)
392
373
 
393
- if obj.is_a? Array
394
- obj.each { |o| val = is_field_value_nested o, field_value_type; return val if val }
395
- elsif obj.is_a? Hash
396
- obj.each { |_k, v| val = is_field_value_nested v, field_value_type; return val if val }
374
+ case obj
375
+ when Array
376
+ obj.each do |o|
377
+ val = field_value_nested? o, field_value_type
378
+ return val if val
379
+ end
380
+ when Hash
381
+ obj.each do |_k, v|
382
+ val = field_value_nested? v, field_value_type
383
+ return val if val
384
+ end
397
385
  end
398
386
  nil
399
387
  end
@@ -406,35 +394,33 @@ module Google
406
394
  if value.is_a?(FieldValue) && (field_value_type.nil? || value.type == field_value_type)
407
395
  paths << [FieldPath.new(*key), value]
408
396
  nil # will be removed by calling compact
409
- else
410
- if value.is_a? Hash
411
- unless value.empty?
412
- nested_hash, nested_paths = remove_field_value_from value, field_value_type
413
- if nested_paths.any?
414
- nested_paths.each do |nested_field_path, nested_field_value|
415
- updated_field_paths = ([key] + nested_field_path.fields).flatten
416
- updated_field_path = FieldPath.new *updated_field_paths
417
- paths << [updated_field_path, nested_field_value]
418
- end
419
- end
420
- if nested_hash.empty?
421
- nil # will be removed by calling compact
422
- else
423
- [String(key), nested_hash]
397
+ elsif value.is_a? Hash
398
+ if value.empty?
399
+ [String(key), value]
400
+ else
401
+ nested_hash, nested_paths = remove_field_value_from value, field_value_type
402
+ if nested_paths.any?
403
+ nested_paths.each do |nested_field_path, nested_field_value|
404
+ updated_field_paths = ([key] + nested_field_path.fields).flatten
405
+ updated_field_path = FieldPath.new(*updated_field_paths)
406
+ paths << [updated_field_path, nested_field_value]
424
407
  end
408
+ end
409
+ if nested_hash.empty?
410
+ nil # will be removed by calling compact
425
411
  else
426
- [String(key), value]
412
+ [String(key), nested_hash]
427
413
  end
428
- else
429
- if value.is_a? Array
430
- nested_field_value = is_field_value_nested value, field_value_type
431
- if nested_field_value
432
- raise ArgumentError, "cannot nest #{nested_field_value.type} under arrays"
433
- end
414
+ end
415
+ else
416
+ if value.is_a? Array
417
+ nested_field_value = field_value_nested? value, field_value_type
418
+ if nested_field_value
419
+ raise ArgumentError, "cannot nest #{nested_field_value.type} under arrays"
434
420
  end
435
-
436
- [String(key), value]
437
421
  end
422
+
423
+ [String(key), value]
438
424
  end
439
425
  end
440
426
 
@@ -449,14 +435,14 @@ module Google
449
435
  if value.is_a? Hash
450
436
  nested_paths = identify_leaf_nodes value
451
437
  nested_paths.each do |nested_path|
452
- paths << (([key] + nested_path.fields).flatten)
438
+ paths << ([key] + nested_path.fields).flatten
453
439
  end
454
440
  else
455
441
  paths << [key]
456
442
  end
457
443
  end
458
444
 
459
- paths.map { |path| FieldPath.new *path }
445
+ paths.map { |path| FieldPath.new(*path) }
460
446
  end
461
447
 
462
448
  def identify_all_file_paths hash
@@ -465,15 +451,14 @@ module Google
465
451
  hash.map do |key, value|
466
452
  paths << [key]
467
453
 
468
- if value.is_a? Hash
469
- nested_paths = identify_all_file_paths value
470
- nested_paths.each do |nested_path|
471
- paths << (([key] + nested_path.fields).flatten)
472
- end
454
+ next unless value.is_a? Hash
455
+ nested_paths = identify_all_file_paths value
456
+ nested_paths.each do |nested_path|
457
+ paths << ([key] + nested_path.fields).flatten
473
458
  end
474
459
  end
475
460
 
476
- paths.map { |path| FieldPath.new *path }
461
+ paths.map { |path| FieldPath.new(*path) }
477
462
  end
478
463
 
479
464
  def select_by_field_paths hash, field_paths
@@ -533,19 +518,19 @@ module Google
533
518
  right_hash.each_pair do |key, right_value|
534
519
  left_value = left_hash[key]
535
520
 
536
- if left_value.is_a?(Hash) && right_value.is_a?(Hash)
537
- left_hash[key] = deep_merge_hashes left_value, right_value
538
- else
539
- left_hash[key] = right_value
540
- end
521
+ left_hash[key] = if left_value.is_a?(Hash) && right_value.is_a?(Hash)
522
+ deep_merge_hashes left_value, right_value
523
+ else
524
+ right_value
525
+ end
541
526
  end
542
527
 
543
528
  left_hash
544
529
  end
545
530
 
546
- START_FIELD_PATH_CHARS = /\A[a-zA-Z_]/
547
- INVALID_FIELD_PATH_CHARS = /[\~\*\/\[\]]/
548
- ESCAPED_FIELD_PATH = /\A\`(.*)\`\z/
531
+ START_FIELD_PATH_CHARS = /\A[a-zA-Z_]/.freeze
532
+ INVALID_FIELD_PATH_CHARS = %r{[~*/\[\]]}.freeze
533
+ ESCAPED_FIELD_PATH = /\A`(.*)`\z/.freeze
549
534
 
550
535
  def build_hash_from_field_paths_and_values pairs
551
536
  pairs.each do |field_path, _value|
@@ -579,59 +564,52 @@ module Google
579
564
  "`#{str}`"
580
565
  end
581
566
 
582
- def transform_write doc_path, paths
583
- field_transforms = paths.map do |field_path, field_value|
584
- to_field_transform field_path, field_value
585
- end
586
-
587
- Google::Cloud::Firestore::V1::Write.new(
588
- transform: Google::Cloud::Firestore::V1::DocumentTransform.new(
589
- document: doc_path,
590
- field_transforms: field_transforms
591
- )
592
- )
593
- end
594
-
595
567
  def to_field_transform field_path, field_value
596
- if field_value.type == :server_time
568
+ case field_value.type
569
+ when :server_time
597
570
  Google::Cloud::Firestore::V1::DocumentTransform::FieldTransform.new(
598
- field_path: field_path.formatted_string,
571
+ field_path: field_path.formatted_string,
599
572
  set_to_server_value: :REQUEST_TIME
600
573
  )
601
- elsif field_value.type == :array_union
574
+ when :array_union
602
575
  Google::Cloud::Firestore::V1::DocumentTransform::FieldTransform.new(
603
- field_path: field_path.formatted_string,
576
+ field_path: field_path.formatted_string,
604
577
  append_missing_elements: raw_to_value(Array(field_value.value)).array_value
605
578
  )
606
- elsif field_value.type == :array_delete
579
+ when :array_delete
607
580
  Google::Cloud::Firestore::V1::DocumentTransform::FieldTransform.new(
608
- field_path: field_path.formatted_string,
581
+ field_path: field_path.formatted_string,
609
582
  remove_all_from_array: raw_to_value(Array(field_value.value)).array_value
610
583
  )
611
- elsif field_value.type == :increment
584
+ when :increment
612
585
  Google::Cloud::Firestore::V1::DocumentTransform::FieldTransform.new(
613
586
  field_path: field_path.formatted_string,
614
- increment: raw_to_value(field_value.value)
587
+ increment: raw_to_value(field_value.value)
615
588
  )
616
- elsif field_value.type == :maximum
589
+ when :maximum
617
590
  Google::Cloud::Firestore::V1::DocumentTransform::FieldTransform.new(
618
591
  field_path: field_path.formatted_string,
619
- maximum: raw_to_value(field_value.value)
592
+ maximum: raw_to_value(field_value.value)
620
593
  )
621
- elsif field_value.type == :minimum
594
+ when :minimum
622
595
  Google::Cloud::Firestore::V1::DocumentTransform::FieldTransform.new(
623
596
  field_path: field_path.formatted_string,
624
- minimum: raw_to_value(field_value.value)
597
+ minimum: raw_to_value(field_value.value)
625
598
  )
626
599
  else
627
600
  raise ArgumentError, "unknown field transform #{field_value.type}"
628
601
  end
629
602
  end
630
603
  end
631
- # rubocop:enable all
632
604
 
633
605
  extend ClassMethods
634
606
  end
607
+ # rubocop:enable Metrics/AbcSize
608
+ # rubocop:enable Metrics/BlockLength
609
+ # rubocop:enable Metrics/CyclomaticComplexity
610
+ # rubocop:enable Metrics/MethodLength
611
+ # rubocop:enable Metrics/ModuleLength
612
+ # rubocop:enable Metrics/PerceivedComplexity
635
613
  end
636
614
  end
637
615
  end