google-cloud-datastore 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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