From cb00a91607441553a9ed9c6854bf20555f5dbd77 Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Mon, 17 Nov 2014 12:23:13 -0700 Subject: [PATCH 1/4] Add support for Entity properties --- lib/gcloud/datastore/proto.rb | 4 +++ test/gcloud/datastore/proto/test_value.rb | 36 +++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/lib/gcloud/datastore/proto.rb b/lib/gcloud/datastore/proto.rb index 62514b134681..feacfa0d09f3 100644 --- a/lib/gcloud/datastore/proto.rb +++ b/lib/gcloud/datastore/proto.rb @@ -35,6 +35,8 @@ def self.from_proto_value proto_value self.time_from_microseconds microseconds elsif !proto_value.key_value.nil? Gcloud::Datastore::Key.from_proto(proto_value.key_value) + elsif !proto_value.entity_value.nil? + Gcloud::Datastore::Entity.from_proto(proto_value.entity_value) elsif !proto_value.boolean_value.nil? proto_value.boolean_value elsif !proto_value.double_value.nil? @@ -54,6 +56,8 @@ def self.to_proto_value value v.timestamp_microseconds_value = self.microseconds_from_time value elsif Gcloud::Datastore::Key === value v.key_value = value.to_proto + elsif Gcloud::Datastore::Entity === value + v.entity_value = value.to_proto elsif TrueClass === value v.boolean_value = true elsif FalseClass === value diff --git a/test/gcloud/datastore/proto/test_value.rb b/test/gcloud/datastore/proto/test_value.rb index 7e9b6e325d20..4221d9ead76c 100644 --- a/test/gcloud/datastore/proto/test_value.rb +++ b/test/gcloud/datastore/proto/test_value.rb @@ -32,6 +32,7 @@ value.string_value.must_equal raw value.timestamp_microseconds_value.must_be :nil? value.key_value.must_be :nil? + value.entity_value.must_be :nil? value.boolean_value.must_be :nil? value.double_value.must_be :nil? value.integer_value.must_be :nil? @@ -50,6 +51,7 @@ value.boolean_value.must_equal true value.timestamp_microseconds_value.must_be :nil? value.key_value.must_be :nil? + value.entity_value.must_be :nil? value.double_value.must_be :nil? value.integer_value.must_be :nil? value.string_value.must_be :nil? @@ -67,6 +69,7 @@ value.boolean_value.must_equal false value.timestamp_microseconds_value.must_be :nil? value.key_value.must_be :nil? + value.entity_value.must_be :nil? value.double_value.must_be :nil? value.integer_value.must_be :nil? value.string_value.must_be :nil? @@ -83,6 +86,7 @@ value = Gcloud::Datastore::Proto.to_proto_value time_obj value.timestamp_microseconds_value.must_equal time_num value.key_value.must_be :nil? + value.entity_value.must_be :nil? value.boolean_value.must_be :nil? value.double_value.must_be :nil? value.integer_value.must_be :nil? @@ -102,6 +106,7 @@ value.integer_value.must_equal raw value.timestamp_microseconds_value.must_be :nil? value.key_value.must_be :nil? + value.entity_value.must_be :nil? value.boolean_value.must_be :nil? value.double_value.must_be :nil? value.string_value.must_be :nil? @@ -121,6 +126,7 @@ value.double_value.must_equal raw value.timestamp_microseconds_value.must_be :nil? value.key_value.must_be :nil? + value.entity_value.must_be :nil? value.boolean_value.must_be :nil? value.integer_value.must_be :nil? value.string_value.must_be :nil? @@ -139,6 +145,7 @@ value = Gcloud::Datastore::Proto.to_proto_value key value.key_value.must_equal key.to_proto value.timestamp_microseconds_value.must_be :nil? + value.entity_value.must_be :nil? value.boolean_value.must_be :nil? value.double_value.must_be :nil? value.integer_value.must_be :nil? @@ -157,4 +164,33 @@ # (they are actually the same object, so this works...) raw.to_proto.must_equal key.to_proto end + + it "encodes Entity" do + entity = Gcloud::Datastore::Entity.new + entity.key = Gcloud::Datastore::Key.new "Thing", 123 + entity["name"] = "Thing 1" + value = Gcloud::Datastore::Proto.to_proto_value entity + value.key_value.must_be :nil? + value.entity_value.must_equal entity.to_proto + value.timestamp_microseconds_value.must_be :nil? + value.boolean_value.must_be :nil? + value.double_value.must_be :nil? + value.integer_value.must_be :nil? + value.string_value.must_be :nil? + end + + it "decodes Entity" do + entity = Gcloud::Datastore::Entity.new + entity.key = Gcloud::Datastore::Key.new "Thing", 123 + entity["name"] = "Thing 1" + value = Gcloud::Datastore::Proto::Value.new + value.entity_value = entity.to_proto + raw = Gcloud::Datastore::Proto.from_proto_value value + assert_kind_of Gcloud::Datastore::Entity, raw + refute_kind_of Gcloud::Datastore::Proto::Entity, raw + # We don't have equality on entity yet, + # so let's make sure the proto values are equal. + # (they are actually the same object, so this works...) + raw.to_proto.must_equal entity.to_proto + end end From d2b71705173fc5269bac2f509d314273eeee69c9 Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Mon, 17 Nov 2014 14:58:25 -0700 Subject: [PATCH 2/4] Add support for List properties --- lib/gcloud/datastore/proto.rb | 10 +++++-- test/gcloud/datastore/proto/test_value.rb | 36 +++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/gcloud/datastore/proto.rb b/lib/gcloud/datastore/proto.rb index feacfa0d09f3..2408e974a43f 100644 --- a/lib/gcloud/datastore/proto.rb +++ b/lib/gcloud/datastore/proto.rb @@ -45,9 +45,13 @@ def self.from_proto_value proto_value proto_value.integer_value elsif !proto_value.string_value.nil? return proto_value.string_value + elsif !proto_value.list_value.nil? + return Array(proto_value.list_value).map do |item| + from_proto_value item + end else nil - end # TODO: Entity, Array + end end def self.to_proto_value value @@ -70,7 +74,9 @@ def self.to_proto_value value v.integer_value = value elsif String === value v.string_value = value - end # TODO: entity, list_value + elsif Array === value + v.list_value = value.map { |item| to_proto_value item } + end v end diff --git a/test/gcloud/datastore/proto/test_value.rb b/test/gcloud/datastore/proto/test_value.rb index 4221d9ead76c..3585c0482e40 100644 --- a/test/gcloud/datastore/proto/test_value.rb +++ b/test/gcloud/datastore/proto/test_value.rb @@ -36,6 +36,7 @@ value.boolean_value.must_be :nil? value.double_value.must_be :nil? value.integer_value.must_be :nil? + value.list_value.must_be :nil? end it "decodes a string" do @@ -55,6 +56,7 @@ value.double_value.must_be :nil? value.integer_value.must_be :nil? value.string_value.must_be :nil? + value.list_value.must_be :nil? end it "decodes true" do @@ -73,6 +75,7 @@ value.double_value.must_be :nil? value.integer_value.must_be :nil? value.string_value.must_be :nil? + value.list_value.must_be :nil? end it "decodes false" do @@ -91,6 +94,7 @@ value.double_value.must_be :nil? value.integer_value.must_be :nil? value.string_value.must_be :nil? + value.list_value.must_be :nil? end it "decodes timestamp" do @@ -110,6 +114,7 @@ value.boolean_value.must_be :nil? value.double_value.must_be :nil? value.string_value.must_be :nil? + value.list_value.must_be :nil? end it "decodes integer" do @@ -130,6 +135,7 @@ value.boolean_value.must_be :nil? value.integer_value.must_be :nil? value.string_value.must_be :nil? + value.list_value.must_be :nil? end it "decodes float" do @@ -150,6 +156,7 @@ value.double_value.must_be :nil? value.integer_value.must_be :nil? value.string_value.must_be :nil? + value.list_value.must_be :nil? end it "decodes Key" do @@ -177,6 +184,7 @@ value.double_value.must_be :nil? value.integer_value.must_be :nil? value.string_value.must_be :nil? + value.list_value.must_be :nil? end it "decodes Entity" do @@ -193,4 +201,32 @@ # (they are actually the same object, so this works...) raw.to_proto.must_equal entity.to_proto end + + it "encodes Array" do + array = ["string", 123, true] + value = Gcloud::Datastore::Proto.to_proto_value array + value.list_value.wont_be :nil? + value.key_value.must_be :nil? + value.entity_value.must_be :nil? + value.timestamp_microseconds_value.must_be :nil? + value.boolean_value.must_be :nil? + value.double_value.must_be :nil? + value.integer_value.must_be :nil? + value.string_value.must_be :nil? + end + + it "decodes List" do + value = Gcloud::Datastore::Proto::Value.new + value.list_value = [ + Gcloud::Datastore::Proto::Value.new.tap { |v| v.string_value = "string" }, + Gcloud::Datastore::Proto::Value.new.tap { |v| v.integer_value = 123 }, + Gcloud::Datastore::Proto::Value.new.tap { |v| v.boolean_value = true }, + ] + raw = Gcloud::Datastore::Proto.from_proto_value value + assert_kind_of Array, raw + raw.count.must_equal 3 + raw[0].must_equal "string" + raw[1].must_equal 123 + raw[2].must_equal true + end end From c838d23db680156e0698d55d7b61d21ee539cb65 Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Mon, 17 Nov 2014 16:23:37 -0700 Subject: [PATCH 3/4] Add some additional Datastore Key and Entity tests --- test/gcloud/datastore/test_entity.rb | 29 +++++++++++++++++++++++ test/gcloud/datastore/test_key.rb | 35 ++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/test/gcloud/datastore/test_entity.rb b/test/gcloud/datastore/test_entity.rb index ca4f5a18bffb..d592c8bff192 100644 --- a/test/gcloud/datastore/test_entity.rb +++ b/test/gcloud/datastore/test_entity.rb @@ -68,4 +68,33 @@ entity_from_proto.properties.must_include ["name", "User McNumber"] entity_from_proto.properties.must_include ["email", "number@example.net"] end + + it "can store other entities as properties" do + task1 = Gcloud::Datastore::Entity.new.tap do |t| + t.key = Gcloud::Datastore::Key.new "Task", 1111 + t["description"] = "can persist entities" + t["completed"] = true + end + task2 = Gcloud::Datastore::Entity.new.tap do |t| + t.key = Gcloud::Datastore::Key.new "Task", 2222 + t["description"] = "can persist lists" + t["completed"] = true + end + entity["tasks"] = [task1, task2] + + proto = entity.to_proto + + task_property = proto.property.last + task_property.name.must_equal "tasks" + task_property.value.list_value.wont_be :nil? + task_property.value.list_value.count.must_equal 2 + proto_task_1 = task_property.value.list_value.first + proto_task_2 = task_property.value.list_value.last + proto_task_1.wont_be :nil? + proto_task_2.wont_be :nil? + proto_task_1.entity_value.wont_be :nil? + proto_task_2.entity_value.wont_be :nil? + proto_task_1.entity_value.property.find { |p| p.name == "description" }.value.string_value.must_equal "can persist entities" + proto_task_2.entity_value.property.find { |p| p.name == "description" }.value.string_value.must_equal "can persist lists" + end end diff --git a/test/gcloud/datastore/test_key.rb b/test/gcloud/datastore/test_key.rb index c27ba1d59396..c0b235edbc8c 100644 --- a/test/gcloud/datastore/test_key.rb +++ b/test/gcloud/datastore/test_key.rb @@ -52,6 +52,30 @@ key.parent.name.must_be :nil? end + it "can set a dataset_id" do + key = Gcloud::Datastore::Key.new "ThisThing", 1234 + key.kind.must_equal "ThisThing" + key.id.must_equal 1234 + key.name.must_be :nil? + + key.dataset_id.must_be :nil? + key.dataset_id = "custom-ds" + key.dataset_id.wont_be :nil? + key.dataset_id.must_equal "custom-ds" + end + + it "can set a namespace" do + key = Gcloud::Datastore::Key.new "ThisThing", 1234 + key.kind.must_equal "ThisThing" + key.id.must_equal 1234 + key.name.must_be :nil? + + key.namespace.must_be :nil? + key.namespace = "custom-ns" + key.namespace.wont_be :nil? + key.namespace.must_equal "custom-ns" + end + describe "path" do it "returns kind and id" do key = Gcloud::Datastore::Key.new "Task", 123456 @@ -125,9 +149,13 @@ proto.path_element.last.kind.must_equal "ThisThing" proto.path_element.last.id.must_equal 1234 proto.path_element.last.name.must_be :nil? + proto.partition_id.dataset_id.must_be :nil? + proto.partition_id.namespace.must_be :nil? key = Gcloud::Datastore::Key.new "ThisThing", "charlie" key.parent = Gcloud::Datastore::Key.new "ThatThing", "henry" + key.dataset_id = "custom-ds" + key.namespace = "custom-ns" proto = key.to_proto proto.path_element.count.must_equal 2 proto.path_element.first.kind.must_equal "ThatThing" @@ -136,6 +164,8 @@ proto.path_element.last.kind.must_equal "ThisThing" proto.path_element.last.id.must_be :nil? proto.path_element.last.name.must_equal "charlie" + proto.partition_id.dataset_id.must_equal "custom-ds" + proto.partition_id.namespace.must_equal "custom-ns" end it "can be created with a protocol buffer object" do @@ -143,11 +173,16 @@ proto.path_element = [Gcloud::Datastore::Proto::Key::PathElement.new] proto.path_element.first.kind = "AnotherThing" proto.path_element.first.id = 56789 + proto.partition_id = Gcloud::Datastore::Proto::PartitionId.new + proto.partition_id.dataset_id = "custom-ds" + proto.partition_id.namespace = "custom-ns" key = Gcloud::Datastore::Key.from_proto proto key.wont_be :nil? key.kind.must_equal "AnotherThing" key.id.must_equal 56789 key.name.must_be :nil? + key.dataset_id.must_equal "custom-ds" + key.namespace.must_equal "custom-ns" end end From 37c2c84a86fbdda66c165f9737dc016c907ca456 Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Fri, 21 Nov 2014 15:49:33 -0700 Subject: [PATCH 4/4] Raise error when setting an unsupported type --- lib/gcloud.rb | 2 +- lib/gcloud/datastore/errors.rb | 7 ++++++- lib/gcloud/datastore/proto.rb | 2 ++ test/gcloud/datastore/test_entity.rb | 7 +++++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/gcloud.rb b/lib/gcloud.rb index be1cc8421c2f..33bdbb0e439d 100644 --- a/lib/gcloud.rb +++ b/lib/gcloud.rb @@ -2,7 +2,7 @@ module Gcloud ## - # Standard Gcloud exception class. + # Base Gcloud exception class. class Error < StandardError end end diff --git a/lib/gcloud/datastore/errors.rb b/lib/gcloud/datastore/errors.rb index e4beebf48939..426f28670528 100644 --- a/lib/gcloud/datastore/errors.rb +++ b/lib/gcloud/datastore/errors.rb @@ -17,7 +17,7 @@ module Gcloud module Datastore ## - # Standard Datastore exception class. + # Base Datastore exception class. class Error < Gcloud::Error end @@ -39,6 +39,11 @@ def initialize method, response = nil end end + ## + # Raised when a property is not correct. + class PropertyError < Gcloud::Datastore::Error + end + ## # General error for Transaction problems. class TransactionError < Gcloud::Datastore::Error diff --git a/lib/gcloud/datastore/proto.rb b/lib/gcloud/datastore/proto.rb index 2408e974a43f..a8f897ba7910 100644 --- a/lib/gcloud/datastore/proto.rb +++ b/lib/gcloud/datastore/proto.rb @@ -76,6 +76,8 @@ def self.to_proto_value value v.string_value = value elsif Array === value v.list_value = value.map { |item| to_proto_value item } + else + fail PropertyError, "A property of type #{value.class} is not supported." end v end diff --git a/test/gcloud/datastore/test_entity.rb b/test/gcloud/datastore/test_entity.rb index d592c8bff192..9308542ff436 100644 --- a/test/gcloud/datastore/test_entity.rb +++ b/test/gcloud/datastore/test_entity.rb @@ -97,4 +97,11 @@ proto_task_1.entity_value.property.find { |p| p.name == "description" }.value.string_value.must_equal "can persist entities" proto_task_2.entity_value.property.find { |p| p.name == "description" }.value.string_value.must_equal "can persist lists" end + + it "raises when setting an unsupported property type" do + error = assert_raises Gcloud::Datastore::PropertyError do + entity["thing"] = Gcloud::Datastore::Credentials::Empty.new + end + error.message.must_equal "A property of type Gcloud::Datastore::Credentials::Empty is not supported." + end end