google-cloud-firestore 2.1.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,11 +14,14 @@
14
14
 
15
15
 
16
16
  require "google/cloud/firestore/watch/listener"
17
+ require "monitor"
17
18
 
18
19
  module Google
19
20
  module Cloud
20
21
  module Firestore
21
22
  ##
23
+ # # DocumentListener
24
+ #
22
25
  # An ongoing listen operation on a document reference. This is returned by
23
26
  # calling {DocumentReference#listen}.
24
27
  #
@@ -31,25 +34,28 @@ module Google
31
34
  # nyc_ref = firestore.doc "cities/NYC"
32
35
  #
33
36
  # listener = nyc_ref.listen do |snapshot|
34
- # puts "The population of #{snapshot[:name]} "
35
- # puts "is #{snapshot[:population]}."
37
+ # puts "The population of #{snapshot[:name]} is #{snapshot[:population]}."
36
38
  # end
37
39
  #
38
40
  # # When ready, stop the listen operation and close the stream.
39
41
  # listener.stop
40
42
  #
41
43
  class DocumentListener
44
+ include MonitorMixin
42
45
  ##
43
46
  # @private
44
47
  # Creates the watch stream and listener object.
45
48
  def initialize doc_ref, &callback
49
+ super() # to init MonitorMixin
50
+
46
51
  @doc_ref = doc_ref
47
52
  raise ArgumentError if @doc_ref.nil?
48
53
 
49
54
  @callback = callback
50
55
  raise ArgumentError if @callback.nil?
56
+ @error_callbacks = []
51
57
 
52
- @listener = Watch::Listener.for_doc_ref doc_ref do |query_snp|
58
+ @listener = Watch::Listener.for_doc_ref self, doc_ref do |query_snp|
53
59
  doc_snp = query_snp.docs.find { |doc| doc.path == @doc_ref.path }
54
60
 
55
61
  if doc_snp.nil?
@@ -80,8 +86,7 @@ module Google
80
86
  # nyc_ref = firestore.doc "cities/NYC"
81
87
  #
82
88
  # listener = nyc_ref.listen do |snapshot|
83
- # puts "The population of #{snapshot[:name]} "
84
- # puts "is #{snapshot[:population]}."
89
+ # puts "The population of #{snapshot[:name]} is #{snapshot[:population]}."
85
90
  # end
86
91
  #
87
92
  # # When ready, stop the listen operation and close the stream.
@@ -103,8 +108,7 @@ module Google
103
108
  # nyc_ref = firestore.doc "cities/NYC"
104
109
  #
105
110
  # listener = nyc_ref.listen do |snapshot|
106
- # puts "The population of #{snapshot[:name]} "
107
- # puts "is #{snapshot[:population]}."
111
+ # puts "The population of #{snapshot[:name]} is #{snapshot[:population]}."
108
112
  # end
109
113
  #
110
114
  # # Checks if the listener is stopped.
@@ -119,6 +123,81 @@ module Google
119
123
  def stopped?
120
124
  @listener.stopped?
121
125
  end
126
+
127
+ ##
128
+ # Register to be notified of errors when raised.
129
+ #
130
+ # If an unhandled error has occurred the listener will attempt to
131
+ # recover from the error and resume listening.
132
+ #
133
+ # Multiple error handlers can be added.
134
+ #
135
+ # @yield [callback] The block to be called when an error is raised.
136
+ # @yieldparam [Exception] error The error raised.
137
+ #
138
+ # @example
139
+ # require "google/cloud/firestore"
140
+ #
141
+ # firestore = Google::Cloud::Firestore.new
142
+ #
143
+ # # Get a document reference
144
+ # nyc_ref = firestore.doc "cities/NYC"
145
+ #
146
+ # listener = nyc_ref.listen do |snapshot|
147
+ # puts "The population of #{snapshot[:name]} is #{snapshot[:population]}."
148
+ # end
149
+ #
150
+ # # Register to be notified when unhandled errors occur.
151
+ # listener.on_error do |error|
152
+ # puts error
153
+ # end
154
+ #
155
+ # # When ready, stop the listen operation and close the stream.
156
+ # listener.stop
157
+ #
158
+ def on_error &block
159
+ raise ArgumentError, "on_error must be called with a block" unless block_given?
160
+ synchronize { @error_callbacks << block }
161
+ end
162
+
163
+ ##
164
+ # The most recent unhandled error to occur while listening for changes.
165
+ #
166
+ # If an unhandled error has occurred the listener will attempt to
167
+ # recover from the error and resume listening.
168
+ #
169
+ # @return [Exception, nil] error The most recent error raised.
170
+ #
171
+ # @example
172
+ # require "google/cloud/firestore"
173
+ #
174
+ # firestore = Google::Cloud::Firestore.new
175
+ #
176
+ # # Get a document reference
177
+ # nyc_ref = firestore.doc "cities/NYC"
178
+ #
179
+ # listener = nyc_ref.listen do |snapshot|
180
+ # puts "The population of #{snapshot[:name]} is #{snapshot[:population]}."
181
+ # end
182
+ #
183
+ # # If an error was raised, it can be retrieved here:
184
+ # listener.last_error #=> nil
185
+ #
186
+ # # When ready, stop the listen operation and close the stream.
187
+ # listener.stop
188
+ #
189
+ def last_error
190
+ synchronize { @last_error }
191
+ end
192
+
193
+ # @private Pass the error to user-provided error callbacks.
194
+ def error! error
195
+ error_callbacks = synchronize do
196
+ @last_error = error
197
+ @error_callbacks.dup
198
+ end
199
+ error_callbacks.each { |error_callback| error_callback.call error }
200
+ end
122
201
  end
123
202
  end
124
203
  end
@@ -90,11 +90,11 @@ module Google
90
90
  # puts col.collection_id
91
91
  # end
92
92
  #
93
- def cols
93
+ def cols &block
94
94
  ensure_service!
95
95
  grpc = service.list_collections path
96
96
  cols_enum = CollectionReferenceList.from_grpc(grpc, client, path).all
97
- cols_enum.each { |c| yield c } if block_given?
97
+ cols_enum.each(&block) if block_given?
98
98
  cols_enum
99
99
  end
100
100
  alias collections cols
@@ -168,8 +168,7 @@ module Google
168
168
  # nyc_ref = firestore.doc "cities/NYC"
169
169
  #
170
170
  # listener = nyc_ref.listen do |snapshot|
171
- # puts "The population of #{snapshot[:name]} "
172
- # puts "is #{snapshot[:population]}."
171
+ # puts "The population of #{snapshot[:name]} is #{snapshot[:population]}."
173
172
  # end
174
173
  #
175
174
  # # When ready, stop the listen operation and close the stream.
@@ -86,8 +86,7 @@ module Google
86
86
  def next
87
87
  return nil unless next?
88
88
  ensure_client!
89
- options = { token: token, max: @max }
90
- grpc = @client.service.list_documents @parent, @collection_id, options
89
+ grpc = @client.service.list_documents @parent, @collection_id, token: token, max: @max
91
90
  self.class.from_grpc grpc, @client, @parent, @collection_id, @max
92
91
  end
93
92
 
@@ -143,17 +142,17 @@ module Google
143
142
  # puts doc_ref.document_id
144
143
  # end
145
144
  #
146
- def all request_limit: nil
145
+ def all request_limit: nil, &block
147
146
  request_limit = request_limit.to_i if request_limit
148
147
  unless block_given?
149
148
  return enum_for :all, request_limit: request_limit
150
149
  end
151
150
  results = self
152
151
  loop do
153
- results.each { |r| yield r }
152
+ results.each(&block)
154
153
  if request_limit
155
154
  request_limit -= 1
156
- break if request_limit < 0
155
+ break if request_limit.negative?
157
156
  end
158
157
  break unless results.next?
159
158
  results = results.next
@@ -53,8 +53,7 @@ module Google
53
53
  # nyc_ref = firestore.doc "cities/NYC"
54
54
  #
55
55
  # listener = nyc_ref.listen do |snapshot|
56
- # puts "The population of #{snapshot[:name]} "
57
- # puts "is #{snapshot[:population]}."
56
+ # puts "The population of #{snapshot[:name]} is #{snapshot[:population]}."
58
57
  # end
59
58
  #
60
59
  # # When ready, stop the listen operation and close the stream.
@@ -210,14 +210,14 @@ module Google
210
210
  protected
211
211
 
212
212
  START_FIELD_PATH_CHARS = /\A[a-zA-Z_]/.freeze
213
- INVALID_FIELD_PATH_CHARS = %r{[\~\*\/\[\]]}.freeze
213
+ INVALID_FIELD_PATH_CHARS = %r{[~*/\[\]]}.freeze
214
214
 
215
215
  def escape_field_for_path field
216
216
  field = String field
217
217
 
218
218
  if INVALID_FIELD_PATH_CHARS.match(field) ||
219
219
  field["."] || field["`"] || field["\\"]
220
- escaped_field = field.gsub(/[\`\\]/, "`" => "\\\`", "\\" => "\\\\")
220
+ escaped_field = field.gsub(/[`\\]/, "`" => "\\\`", "\\" => "\\\\")
221
221
  return "`#{escaped_field}`"
222
222
  end
223
223
 
@@ -216,6 +216,9 @@ module Google
216
216
  # * greater than: `>`, `gt`
217
217
  # * greater than or equal: `>=`, `gte`
218
218
  # * equal: `=`, `==`, `eq`, `eql`, `is`
219
+ # * not equal: `!=`
220
+ # * in: `in`
221
+ # * not in: `not-in`, `not_in`
219
222
  # * array contains: `array-contains`, `array_contains`
220
223
  # @param [Object] value A value the field is compared to.
221
224
  #
@@ -993,28 +996,25 @@ module Google
993
996
  "eq" => :EQUAL,
994
997
  "eql" => :EQUAL,
995
998
  "is" => :EQUAL,
999
+ "!=" => :NOT_EQUAL,
996
1000
  "array_contains" => :ARRAY_CONTAINS,
997
1001
  "array-contains" => :ARRAY_CONTAINS,
998
1002
  "include" => :ARRAY_CONTAINS,
999
1003
  "include?" => :ARRAY_CONTAINS,
1000
1004
  "has" => :ARRAY_CONTAINS,
1001
1005
  "in" => :IN,
1006
+ "not_in" => :NOT_IN,
1007
+ "not-in" => :NOT_IN,
1002
1008
  "array_contains_any" => :ARRAY_CONTAINS_ANY,
1003
1009
  "array-contains-any" => :ARRAY_CONTAINS_ANY
1004
1010
  }.freeze
1005
1011
  ##
1006
1012
  # @private
1007
- EQUALITY_FILTERS = %i[
1008
- EQUAL
1009
- ARRAY_CONTAINS
1010
- ].freeze
1011
- ##
1012
- # @private
1013
- INEQUALITY_FILTERS = %i[
1014
- LESS_THAN
1015
- LESS_THAN_OR_EQUAL
1016
- GREATER_THAN
1017
- GREATER_THAN_OR_EQUAL
1013
+ INEQUALITY_FILTERS = [
1014
+ :LESS_THAN,
1015
+ :LESS_THAN_OR_EQUAL,
1016
+ :GREATER_THAN,
1017
+ :GREATER_THAN_OR_EQUAL
1018
1018
  ].freeze
1019
1019
 
1020
1020
  def value_nil? value
@@ -1031,18 +1031,20 @@ module Google
1031
1031
  value_nil?(value) || value_nan?(value)
1032
1032
  end
1033
1033
 
1034
- def filter name, op, value
1034
+ def filter name, op_key, value
1035
1035
  field = StructuredQuery::FieldReference.new field_path: name.to_s
1036
- operator = FILTER_OPS[op.to_s.downcase]
1037
- raise ArgumentError, "unknown operator #{op}" if operator.nil?
1036
+ operator = FILTER_OPS[op_key.to_s.downcase]
1037
+ raise ArgumentError, "unknown operator #{op_key}" if operator.nil?
1038
1038
 
1039
1039
  if value_unary? value
1040
- if operator != :EQUAL
1041
- raise ArgumentError,
1042
- "can only check equality for #{value} values"
1043
- end
1044
-
1045
- operator = value_nan?(value) ? :IS_NAN : :IS_NULL
1040
+ operator = case operator
1041
+ when :EQUAL
1042
+ value_nan?(value) ? :IS_NAN : :IS_NULL
1043
+ when :NOT_EQUAL
1044
+ value_nan?(value) ? :IS_NOT_NAN : :IS_NOT_NULL
1045
+ else
1046
+ raise ArgumentError, "can only perform '==' and '!=' comparisons on #{value} values"
1047
+ end
1046
1048
 
1047
1049
  return StructuredQuery::Filter.new(
1048
1050
  unary_filter: StructuredQuery::UnaryFilter.new(
@@ -41,17 +41,21 @@ module Google
41
41
  # listener.stop
42
42
  #
43
43
  class QueryListener
44
+ include MonitorMixin
44
45
  ##
45
46
  # @private
46
47
  # Creates the watch stream and listener object.
47
48
  def initialize query, &callback
49
+ super() # to init MonitorMixin
50
+
48
51
  @query = query
49
52
  raise ArgumentError if @query.nil?
50
53
 
51
54
  @callback = callback
52
55
  raise ArgumentError if @callback.nil?
56
+ @error_callbacks = []
53
57
 
54
- @listener = Watch::Listener.for_query query, &callback
58
+ @listener = Watch::Listener.for_query self, query, &callback
55
59
  end
56
60
 
57
61
  ##
@@ -112,6 +116,83 @@ module Google
112
116
  def stopped?
113
117
  @listener.stopped?
114
118
  end
119
+
120
+ ##
121
+ # Register to be notified of errors when raised.
122
+ #
123
+ # If an unhandled error has occurred the listener will attempt to
124
+ # recover from the error and resume listening.
125
+ #
126
+ # Multiple error handlers can be added.
127
+ #
128
+ # @yield [callback] The block to be called when an error is raised.
129
+ # @yieldparam [Exception] error The error raised.
130
+ #
131
+ # @example
132
+ # require "google/cloud/firestore"
133
+ #
134
+ # firestore = Google::Cloud::Firestore.new
135
+ #
136
+ # # Create a query
137
+ # query = firestore.col(:cities).order(:population, :desc)
138
+ #
139
+ # listener = query.listen do |snapshot|
140
+ # puts "The query snapshot has #{snapshot.docs.count} documents "
141
+ # puts "and has #{snapshot.changes.count} changes."
142
+ # end
143
+ #
144
+ # # Register to be notified when unhandled errors occur.
145
+ # listener.on_error do |error|
146
+ # puts error
147
+ # end
148
+ #
149
+ # # When ready, stop the listen operation and close the stream.
150
+ # listener.stop
151
+ #
152
+ def on_error &block
153
+ raise ArgumentError, "on_error must be called with a block" unless block_given?
154
+ synchronize { @error_callbacks << block }
155
+ end
156
+
157
+ ##
158
+ # The most recent unhandled error to occur while listening for changes.
159
+ #
160
+ # If an unhandled error has occurred the listener will attempt to
161
+ # recover from the error and resume listening.
162
+ #
163
+ # @return [Exception, nil] error The most recent error raised.
164
+ #
165
+ # @example
166
+ # require "google/cloud/firestore"
167
+ #
168
+ # firestore = Google::Cloud::Firestore.new
169
+ #
170
+ # # Create a query
171
+ # query = firestore.col(:cities).order(:population, :desc)
172
+ #
173
+ # listener = query.listen do |snapshot|
174
+ # puts "The query snapshot has #{snapshot.docs.count} documents "
175
+ # puts "and has #{snapshot.changes.count} changes."
176
+ # end
177
+ #
178
+ # # If an error was raised, it can be retrieved here:
179
+ # listener.last_error #=> nil
180
+ #
181
+ # # When ready, stop the listen operation and close the stream.
182
+ # listener.stop
183
+ #
184
+ def last_error
185
+ synchronize { @last_error }
186
+ end
187
+
188
+ # @private Pass the error to user-provided error callbacks.
189
+ def error! error
190
+ error_callbacks = synchronize do
191
+ @last_error = error
192
+ @error_callbacks.dup
193
+ end
194
+ error_callbacks.each { |error_callback| error_callback.call error }
195
+ end
115
196
  end
116
197
  end
117
198
  end
@@ -26,7 +26,10 @@ module Google
26
26
  # @private Represents the gRPC Firestore service, including all the API
27
27
  # methods.
28
28
  class Service
29
- attr_accessor :project, :credentials, :timeout, :host
29
+ attr_accessor :project
30
+ attr_accessor :credentials
31
+ attr_accessor :timeout
32
+ attr_accessor :host
30
33
 
31
34
  ##
32
35
  # Creates a new Service instance.
@@ -131,7 +131,7 @@ module Google
131
131
  ensure_service!
132
132
 
133
133
  unless block_given?
134
- return enum_for :get_all, docs, field_mask: field_mask
134
+ return enum_for :get_all, *docs, field_mask: field_mask
135
135
  end
136
136
 
137
137
  doc_paths = Array(docs).flatten.map do |doc_path|
@@ -321,7 +321,7 @@ module Google
321
321
 
322
322
  doc_path = coalesce_doc_path_argument doc
323
323
 
324
- @writes << Convert.writes_for_create(doc_path, data)
324
+ @writes << Convert.write_for_create(doc_path, data)
325
325
 
326
326
  nil
327
327
  end
@@ -422,7 +422,7 @@ module Google
422
422
 
423
423
  doc_path = coalesce_doc_path_argument doc
424
424
 
425
- @writes << Convert.writes_for_set(doc_path, data, merge: merge)
425
+ @writes << Convert.write_for_set(doc_path, data, merge: merge)
426
426
 
427
427
  nil
428
428
  end
@@ -526,8 +526,8 @@ module Google
526
526
 
527
527
  doc_path = coalesce_doc_path_argument doc
528
528
 
529
- @writes << Convert.writes_for_update(doc_path, data,
530
- update_time: update_time)
529
+ @writes << Convert.write_for_update(doc_path, data,
530
+ update_time: update_time)
531
531
 
532
532
  nil
533
533
  end