google-cloud-datastore 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.
@@ -0,0 +1,454 @@
1
+ # Copyright 2014 Google Inc. All rights reserved.
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
+ # http://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/datastore/key"
17
+ require "google/cloud/datastore/properties"
18
+
19
+ module Google
20
+ module Cloud
21
+ module Datastore
22
+ ##
23
+ # # Entity
24
+ #
25
+ # Entity represents a Datastore record.
26
+ # Every Entity has a {Key}, and a list of properties.
27
+ #
28
+ # Entities in Datastore form a hierarchically structured space similar to
29
+ # the directory structure of a file system. When you create an entity, you
30
+ # can optionally designate another entity as its parent; the new entity is
31
+ # a child of the parent entity.
32
+ #
33
+ # @see https://cloud.google.com/datastore/docs/concepts/entities Entities,
34
+ # Properties, and Keys
35
+ #
36
+ # @example Create a new entity using a block:
37
+ # task = datastore.entity "Task", "sampleTask" do |t|
38
+ # t["type"] = "Personal"
39
+ # t["created"] = Time.now
40
+ # t["done"] = false
41
+ # t["priority"] = 4
42
+ # t["percent_complete"] = 10.0
43
+ # t["description"] = "Learn Cloud Datastore"
44
+ # end
45
+ #
46
+ # @example Create a new entity belonging to an existing parent entity:
47
+ # task_key = datastore.key "Task", "sampleTask"
48
+ # task_key.parent = datastore.key "TaskList", "default"
49
+ #
50
+ # task = Google::Cloud::Datastore::Entity.new
51
+ # task.key = task_key
52
+ #
53
+ # task["type"] = "Personal"
54
+ # task["done"] = false
55
+ # task["priority"] = 4
56
+ # task["description"] = "Learn Cloud Datastore"
57
+ #
58
+ class Entity
59
+ ##
60
+ # The Key that identifies the entity.
61
+ attr_reader :key
62
+
63
+ ##
64
+ # Create a new Entity object.
65
+ def initialize
66
+ @properties = Properties.new
67
+ @key = Key.new
68
+ @_exclude_indexes = {}
69
+ end
70
+
71
+ ##
72
+ # Retrieve a property value by providing the name.
73
+ #
74
+ # Property values are converted from the Datastore value type
75
+ # automatically. Blob properties are returned as StringIO objects.
76
+ # Location properties are returned as a Hash with `:longitude` and
77
+ # `:latitude` keys.
78
+ #
79
+ # @param [String, Symbol] prop_name The name of the property.
80
+ #
81
+ # @return [Object, nil] Returns `nil` if the property doesn't exist
82
+ #
83
+ # @example Properties can be retrieved with a string name:
84
+ # require "google/cloud"
85
+ #
86
+ # gcloud = Google::Cloud.new
87
+ # datastore = gcloud.datastore
88
+ # task = datastore.find "Task", "sampleTask"
89
+ # task["description"] #=> "Learn Cloud Datastore"
90
+ #
91
+ # @example Or with a symbol name:
92
+ # require "google/cloud"
93
+ #
94
+ # gcloud = Google::Cloud.new
95
+ # datastore = gcloud.datastore
96
+ # task = datastore.find "Task", "sampleTask"
97
+ # task[:description] #=> "Learn Cloud Datastore"
98
+ #
99
+ # @example Getting a blob value returns a StringIO object:
100
+ # require "google/cloud"
101
+ #
102
+ # gcloud = Google::Cloud.new
103
+ # datastore = gcloud.datastore
104
+ # user = datastore.find "User", "alice"
105
+ # user["avatar"] #=> StringIO("\x89PNG\r\n\x1A...")
106
+ #
107
+ # @example Getting a geo point value returns a Hash:
108
+ # require "google/cloud"
109
+ #
110
+ # gcloud = Google::Cloud.new
111
+ # datastore = gcloud.datastore
112
+ # user = datastore.find "User", "alice"
113
+ # user["location"] #=> { longitude: -122.0862462,
114
+ # # latitude: 37.4220041 }
115
+ #
116
+ # @example Getting a blob value returns a StringIO object:
117
+ # require "google/cloud"
118
+ #
119
+ # gcloud = Google::Cloud.new
120
+ # datastore = gcloud.datastore
121
+ # user = datastore.find "User", "alice"
122
+ # user["avatar"] #=> StringIO("\x89PNG\r\n\x1A...")
123
+ #
124
+ def [] prop_name
125
+ properties[prop_name]
126
+ end
127
+
128
+ ##
129
+ # Set a property value by name.
130
+ #
131
+ # Property values are converted to use the proper Datastore value type
132
+ # automatically. Use an IO-compatible object (File, StringIO, Tempfile)
133
+ # to indicate the property value should be stored as a Datastore `blob`.
134
+ # IO-compatible objects are converted to StringIO objects when they are
135
+ # set. Use a Hash with `:longitude` and `:latitude` keys to indicate the
136
+ # property value should be stored as a Geo Point/LatLng.
137
+ #
138
+ # @param [String, Symbol] prop_name The name of the property.
139
+ # @param [Object] prop_value The value of the property.
140
+ #
141
+ # @example Properties can be set with a string name:
142
+ # require "google/cloud"
143
+ #
144
+ # gcloud = Google::Cloud.new
145
+ # datastore = gcloud.datastore
146
+ # task = datastore.find "Task", "sampleTask"
147
+ # task["description"] = "Learn Cloud Datastore"
148
+ # task["tags"] = ["fun", "programming"]
149
+ #
150
+ # @example Or with a symbol name:
151
+ # require "google/cloud"
152
+ #
153
+ # gcloud = Google::Cloud.new
154
+ # datastore = gcloud.datastore
155
+ # task = datastore.find "Task", "sampleTask"
156
+ # task[:description] = "Learn Cloud Datastore"
157
+ # task[:tags] = ["fun", "programming"]
158
+ #
159
+ # @example Setting a blob value using an IO:
160
+ # require "google/cloud"
161
+ #
162
+ # gcloud = Google::Cloud.new
163
+ # datastore = gcloud.datastore
164
+ # user = datastore.find "User", "alice"
165
+ # user["avatar"] = File.open "/avatars/alice.png"
166
+ # user["avatar"] #=> StringIO("\x89PNG\r\n\x1A...")
167
+ #
168
+ # @example Setting a geo point value using a Hash:
169
+ # require "google/cloud"
170
+ #
171
+ # gcloud = Google::Cloud.new
172
+ # datastore = gcloud.datastore
173
+ # user = datastore.find "User", "alice"
174
+ # user["location"] = { longitude: -122.0862462, latitude: 37.4220041 }
175
+ #
176
+ # @example Setting a blob value using an IO:
177
+ # require "google/cloud"
178
+ #
179
+ # gcloud = Google::Cloud.new
180
+ # datastore = gcloud.datastore
181
+ # user = datastore.find "User", "alice"
182
+ # user["avatar"] = File.open "/avatars/alice.png"
183
+ # user["avatar"] #=> StringIO("\x89PNG\r\n\x1A...")
184
+ #
185
+ def []= prop_name, prop_value
186
+ properties[prop_name] = prop_value
187
+ end
188
+
189
+ ##
190
+ # Retrieve properties in a hash-like structure.
191
+ # Properties can be accessed or set by string or symbol.
192
+ #
193
+ # @return [Google::Cloud::Datastore::Properties]
194
+ #
195
+ # @example
196
+ # task.properties[:description] = "Learn Cloud Datastore"
197
+ # task.properties["description"] #=> "Learn Cloud Datastore"
198
+ #
199
+ # task.properties.each do |name, value|
200
+ # puts "property #{name} has a value of #{value}"
201
+ # end
202
+ #
203
+ # @example A property's existence can be determined by calling `exist?`:
204
+ # task.properties.exist? :description #=> true
205
+ # task.properties.exist? "description" #=> true
206
+ # task.properties.exist? :expiration #=> false
207
+ #
208
+ # @example A property can be removed from the entity:
209
+ # task.properties.delete :description
210
+ # task.save
211
+ #
212
+ # @example The properties can be converted to a hash:
213
+ # prop_hash = task.properties.to_h
214
+ #
215
+ attr_reader :properties
216
+
217
+ ##
218
+ # Sets the {Google::Cloud::Datastore::Key} that identifies the entity.
219
+ #
220
+ # Once the entity is saved, the key is frozen and immutable. Trying to
221
+ # set a key when immutable will raise a `RuntimeError`.
222
+ #
223
+ # @example The key can be set before the entity is saved:
224
+ # require "google/cloud"
225
+ #
226
+ # gcloud = Google::Cloud.new
227
+ # datastore = gcloud.datastore
228
+ # task = Google::Cloud::Datastore::Entity.new
229
+ # task.key = datastore.key "Task"
230
+ # datastore.save task
231
+ #
232
+ # @example Once the entity is saved, the key is frozen and immutable:
233
+ # require "google/cloud"
234
+ #
235
+ # gcloud = Google::Cloud.new
236
+ # datastore = gcloud.datastore
237
+ # task = datastore.find "Task", "sampleTask"
238
+ # task.persisted? #=> true
239
+ # task.key = datastore.key "Task" #=> RuntimeError
240
+ # task.key.frozen? #=> true
241
+ # task.key.id = 9876543221 #=> RuntimeError
242
+ #
243
+ def key= new_key
244
+ fail "This entity's key is immutable." if persisted?
245
+ @key = new_key
246
+ end
247
+
248
+ ##
249
+ # Indicates if the record is persisted. Default is false.
250
+ #
251
+ # @example
252
+ # require "google/cloud"
253
+ #
254
+ # gcloud = Google::Cloud.new
255
+ # datastore = gcloud.datastore
256
+ #
257
+ # task = Google::Cloud::Datastore::Entity.new
258
+ # task.persisted? #=> false
259
+ #
260
+ # task = datastore.find "Task", "sampleTask"
261
+ # task.persisted? #=> true
262
+ #
263
+ def persisted?
264
+ @key && @key.frozen?
265
+ end
266
+
267
+ ##
268
+ # Indicates if a property is flagged to be excluded from the Datastore
269
+ # indexes. The default value is `false`. This is another way of saying
270
+ # that values are indexed by default.
271
+ #
272
+ # If the property is multi-valued, each value in the list can be managed
273
+ # separately for exclusion from indexing. Calling this method for a
274
+ # multi-valued property will return an array that contains the
275
+ # `excluded` boolean value for each corresponding value in the property.
276
+ # For example, if a multi-valued property contains `["a", "b"]`, and
277
+ # only the value `"b"` is indexed (meaning that `"a"`' is excluded), the
278
+ # return value for this method will be `[true, false]`.
279
+ #
280
+ # @see https://cloud.google.com/datastore/docs/concepts/indexes#Datastore_Unindexed_properties
281
+ # Unindexed properties
282
+ #
283
+ # @example Single property values will return a single flag setting:
284
+ # task["priority"] = 4
285
+ # task.exclude_from_indexes? "priority" #=> false
286
+ #
287
+ # @example A multi-valued property will return array of flag settings:
288
+ # task["tags"] = ["fun", "programming"]
289
+ # task.exclude_from_indexes! "tags", [true, false]
290
+ #
291
+ # task.exclude_from_indexes? "tags" #=> [true, false]
292
+ #
293
+ def exclude_from_indexes? name
294
+ value = self[name]
295
+ flag = @_exclude_indexes[name.to_s]
296
+ map_exclude_flag_to_value flag, value
297
+ end
298
+
299
+ ##
300
+ # Sets whether a property should be excluded from the Datastore indexes.
301
+ # Setting `true` will exclude the property from the indexes. Setting
302
+ # `false` will include the property on any applicable indexes. The
303
+ # default value is `false`. This is another way of saying that values
304
+ # are indexed by default.
305
+ #
306
+ # If the property is multi-valued, each value in the list can be managed
307
+ # separately for exclusion from indexing. When you call this method for
308
+ # a multi-valued property, you can pass either a single boolean argument
309
+ # to be applied to all of the values, or an array that contains the
310
+ # boolean argument for each corresponding value in the property. For
311
+ # example, if a multi-valued property contains `["a", "b"]`, and only
312
+ # the value `"b"` should be indexed (meaning that `"a"`' should be
313
+ # excluded), you should pass the array: `[true, false]`.
314
+ #
315
+ # @param [String] name the property name
316
+ # @param [Boolean, Array<Boolean>, nil] flag whether the value or values
317
+ # should be excluded from indexing
318
+ # @yield [value] a block yielding each value of the property
319
+ # @yieldparam [Object] value a value of the property
320
+ # @yieldreturn [Boolean] `true` if the value should be excluded from
321
+ # indexing
322
+ #
323
+ # @see https://cloud.google.com/datastore/docs/concepts/indexes#Datastore_Unindexed_properties
324
+ # Unindexed properties
325
+ #
326
+ # @example
327
+ # entity["priority"] = 4
328
+ # entity.exclude_from_indexes! "priority", true
329
+ #
330
+ # @example Multi-valued properties can be given multiple exclude flags:
331
+ # entity["tags"] = ["fun", "programming"]
332
+ # entity.exclude_from_indexes! "tags", [true, false]
333
+ #
334
+ # @example Or, a single flag can be applied to all values in a property:
335
+ # entity["tags"] = ["fun", "programming"]
336
+ # entity.exclude_from_indexes! "tags", true
337
+ #
338
+ # @example Flags can also be set with a block:
339
+ # entity["priority"] = 4
340
+ # entity.exclude_from_indexes! "priority" do |priority|
341
+ # priority > 4
342
+ # end
343
+ #
344
+ def exclude_from_indexes! name, flag = nil, &block
345
+ name = name.to_s
346
+ flag = block if block_given?
347
+ if flag.nil?
348
+ @_exclude_indexes.delete name
349
+ else
350
+ @_exclude_indexes[name] = flag
351
+ end
352
+ end
353
+
354
+ ##
355
+ # The number of bytes the Entity will take to serialize during API
356
+ # calls.
357
+ def serialized_size
358
+ to_grpc.to_proto.length
359
+ end
360
+
361
+ ##
362
+ # @private Convert the Entity to a Google::Datastore::V1::Entity
363
+ # object.
364
+ def to_grpc
365
+ grpc = Google::Datastore::V1::Entity.new(
366
+ properties: @properties.to_grpc
367
+ )
368
+ grpc.key = @key.to_grpc unless @key.nil?
369
+ update_properties_indexed! grpc.properties
370
+ grpc
371
+ end
372
+
373
+ ##
374
+ # @private Create a new Entity from a Google::Datastore::V1::Key
375
+ # object.
376
+ def self.from_grpc grpc
377
+ entity = Entity.new
378
+ entity.key = Key.from_grpc grpc.key
379
+ entity.send :properties=, Properties.from_grpc(grpc.properties)
380
+ entity.send :update_exclude_indexes!, grpc.properties
381
+ entity
382
+ end
383
+
384
+ protected
385
+
386
+ ##
387
+ # @private Allow friendly objects to set Properties object.
388
+ attr_writer :properties
389
+
390
+ # rubocop:disable all
391
+ # Disabled rubocop because this is intentionally complex.
392
+
393
+ ##
394
+ # @private Map the exclude flag object to value.
395
+ # The flag object can be a boolean, Proc, or Array.
396
+ # Procs will be called and passed in the value.
397
+ # This will return an array of flags for an array value.
398
+ def map_exclude_flag_to_value flag, value
399
+ if value.is_a? Array
400
+ if flag.is_a? Proc
401
+ value.map { |v| !!flag.call(v) }
402
+ elsif flag.is_a? Array
403
+ (flag + Array.new(value.size)).slice(0, value.size).map do |v|
404
+ !!v
405
+ end
406
+ else
407
+ value.map { |_| !!flag }
408
+ end
409
+ else
410
+ if flag.is_a? Proc
411
+ !!flag.call(value)
412
+ elsif flag.is_a? Array
413
+ !!flag.first
414
+ else
415
+ !!flag
416
+ end
417
+ end
418
+ end
419
+
420
+ ##
421
+ # @private Update the exclude data after a new object is created.
422
+ def update_exclude_indexes! grpc_map
423
+ @_exclude_indexes = {}
424
+ grpc_map.each do |name, value|
425
+ next if value.nil?
426
+ @_exclude_indexes[name] = value.exclude_from_indexes
427
+ unless value.array_value.nil?
428
+ exclude = value.array_value.values.map(&:exclude_from_indexes)
429
+ @_exclude_indexes[name] = exclude
430
+ end
431
+ end
432
+ end
433
+
434
+ ##
435
+ # @private Update the indexed values before the object is saved.
436
+ def update_properties_indexed! grpc_map
437
+ grpc_map.each do |name, value|
438
+ next if value.nil?
439
+ excluded = exclude_from_indexes? name
440
+ if excluded.is_a? Array
441
+ value.array_value.values.each_with_index do |v, i|
442
+ v.exclude_from_indexes = excluded[i]
443
+ end
444
+ else
445
+ value.exclude_from_indexes = excluded
446
+ end
447
+ end
448
+ end
449
+
450
+ # rubocop:enable all
451
+ end
452
+ end
453
+ end
454
+ end