waz-storage 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -9
- data/CHANGELOG.rdoc +72 -72
- data/Gemfile +4 -4
- data/Gemfile.lock +46 -40
- data/LICENSE +18 -18
- data/README.rdoc +310 -310
- data/lib/waz-blobs.rb +4 -4
- data/lib/waz-queues.rb +6 -6
- data/lib/waz-storage.rb +39 -39
- data/lib/waz-tables.rb +4 -4
- data/lib/waz/blobs/blob_object.rb +122 -122
- data/lib/waz/blobs/container.rb +172 -161
- data/lib/waz/blobs/exceptions.rb +10 -10
- data/lib/waz/blobs/service.rb +181 -156
- data/lib/waz/queues/exceptions.rb +28 -28
- data/lib/waz/queues/message.rb +64 -64
- data/lib/waz/queues/queue.rb +164 -164
- data/lib/waz/queues/service.rb +105 -105
- data/lib/waz/storage/base.rb +70 -70
- data/lib/waz/storage/exceptions.rb +33 -33
- data/lib/waz/storage/validation_rules.rb +25 -25
- data/lib/waz/tables/edm_type_helper.rb +44 -44
- data/lib/waz/tables/exceptions.rb +44 -44
- data/lib/waz/tables/service.rb +178 -178
- data/lib/waz/tables/table.rb +74 -74
- data/lib/waz/tables/table_array.rb +10 -10
- data/rakefile +8 -21
- data/{tests → spec}/configuration.rb +22 -22
- data/{tests/waz/blobs/blob_object_test.rb → spec/waz/blobs/blob_object_spec.rb} +80 -80
- data/{tests/waz/blobs/container_test.rb → spec/waz/blobs/container_spec.rb} +175 -162
- data/{tests/waz/blobs/service_test.rb → spec/waz/blobs/service_spec.rb} +336 -282
- data/{tests/waz/queues/message_test.rb → spec/waz/queues/message_spec.rb} +32 -32
- data/{tests/waz/queues/queue_test.rb → spec/waz/queues/queue_spec.rb} +205 -205
- data/{tests/waz/queues/service_test.rb → spec/waz/queues/service_spec.rb} +298 -298
- data/{tests → spec}/waz/storage/base_tests.rb +81 -81
- data/{tests/waz/storage/shared_key_core_service_test.rb → spec/waz/storage/shared_key_core_service_spec.rb} +141 -141
- data/{tests/waz/tables/service_test.rb → spec/waz/tables/service_spec.rb} +613 -613
- data/{tests/waz/tables/table_test.rb → spec/waz/tables/table_spec.rb} +97 -97
- data/waz-storage.gemspec +29 -27
- metadata +47 -26
data/lib/waz/storage/base.rb
CHANGED
@@ -1,70 +1,70 @@
|
|
1
|
-
module WAZ
|
2
|
-
module Storage
|
3
|
-
# This class is used to handle a general connection with Windows Azure Storage Account and it
|
4
|
-
# should be used at least once on the application bootstrap or configuration file.
|
5
|
-
#
|
6
|
-
# The usage is pretty simple as it's depicted on the following sample
|
7
|
-
# WAZ::Storage::establish_connection!(:account_name => "my_account_name",
|
8
|
-
# :access_key => "your_base64_key",
|
9
|
-
# :use_ssl => false)
|
10
|
-
#
|
11
|
-
class Base
|
12
|
-
class << self
|
13
|
-
# Sets the basic configuration parameters to use the API on the current context
|
14
|
-
# required parameters are :account_name, :access_key.
|
15
|
-
#
|
16
|
-
# All other parameters are optional.
|
17
|
-
def establish_connection!(options = {})
|
18
|
-
raise InvalidOption, :account_name unless options.keys.include? :account_name
|
19
|
-
raise InvalidOption, :access_key if !options.keys.include? :use_sas_auth_only unless options.keys.include? :access_key
|
20
|
-
raise InvalidOption, :use_sas_auth_only if !options.keys.include? :access_key unless options.keys.include? :use_sas_auth_only
|
21
|
-
raise InvalidOption, :sharedaccesssignature if !options.keys.include? :access_key unless options.keys.include? :sharedaccesssignature and options.keys.include? :use_sas_auth_only
|
22
|
-
options[:use_ssl] = false unless options.keys.include? :use_ssl
|
23
|
-
(@connections ||= []) << options
|
24
|
-
end
|
25
|
-
|
26
|
-
# Block Syntax
|
27
|
-
#
|
28
|
-
# Pushes the named repository onto the context-stack,
|
29
|
-
# yields a new session, and pops the context-stack.
|
30
|
-
#
|
31
|
-
# This helps you set contextual operations like in the following sample
|
32
|
-
#
|
33
|
-
# Base.establish_connection(options) do
|
34
|
-
# # do some operations on the given context
|
35
|
-
# end
|
36
|
-
#
|
37
|
-
# The context is restored to the previous one (what you configured on establish_connection!)
|
38
|
-
# or nil.
|
39
|
-
#
|
40
|
-
# Non-Block Syntax
|
41
|
-
#
|
42
|
-
# Behaves exactly as establish_connection!
|
43
|
-
def establish_connection(options = {}) # :yields: current_context
|
44
|
-
establish_connection!(options)
|
45
|
-
if (block_given?)
|
46
|
-
begin
|
47
|
-
return yield
|
48
|
-
ensure
|
49
|
-
@connections.pop() if connected?
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Returns the default connection (last set) parameters. It will raise an exception WAZ::Storage::NotConnected
|
55
|
-
# when there's no connection information registered.
|
56
|
-
def default_connection
|
57
|
-
raise NotConnected unless connected?
|
58
|
-
return @connections.last
|
59
|
-
end
|
60
|
-
|
61
|
-
# Returns a value indicating whether the current connection information has been set or not.
|
62
|
-
def connected?
|
63
|
-
return false if (@connections.nil?)
|
64
|
-
return false if (@connections.empty?)
|
65
|
-
return true
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
1
|
+
module WAZ
|
2
|
+
module Storage
|
3
|
+
# This class is used to handle a general connection with Windows Azure Storage Account and it
|
4
|
+
# should be used at least once on the application bootstrap or configuration file.
|
5
|
+
#
|
6
|
+
# The usage is pretty simple as it's depicted on the following sample
|
7
|
+
# WAZ::Storage::establish_connection!(:account_name => "my_account_name",
|
8
|
+
# :access_key => "your_base64_key",
|
9
|
+
# :use_ssl => false)
|
10
|
+
#
|
11
|
+
class Base
|
12
|
+
class << self
|
13
|
+
# Sets the basic configuration parameters to use the API on the current context
|
14
|
+
# required parameters are :account_name, :access_key.
|
15
|
+
#
|
16
|
+
# All other parameters are optional.
|
17
|
+
def establish_connection!(options = {})
|
18
|
+
raise InvalidOption, :account_name unless options.keys.include? :account_name
|
19
|
+
raise InvalidOption, :access_key if !options.keys.include? :use_sas_auth_only unless options.keys.include? :access_key
|
20
|
+
raise InvalidOption, :use_sas_auth_only if !options.keys.include? :access_key unless options.keys.include? :use_sas_auth_only
|
21
|
+
raise InvalidOption, :sharedaccesssignature if !options.keys.include? :access_key unless options.keys.include? :sharedaccesssignature and options.keys.include? :use_sas_auth_only
|
22
|
+
options[:use_ssl] = false unless options.keys.include? :use_ssl
|
23
|
+
(@connections ||= []) << options
|
24
|
+
end
|
25
|
+
|
26
|
+
# Block Syntax
|
27
|
+
#
|
28
|
+
# Pushes the named repository onto the context-stack,
|
29
|
+
# yields a new session, and pops the context-stack.
|
30
|
+
#
|
31
|
+
# This helps you set contextual operations like in the following sample
|
32
|
+
#
|
33
|
+
# Base.establish_connection(options) do
|
34
|
+
# # do some operations on the given context
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# The context is restored to the previous one (what you configured on establish_connection!)
|
38
|
+
# or nil.
|
39
|
+
#
|
40
|
+
# Non-Block Syntax
|
41
|
+
#
|
42
|
+
# Behaves exactly as establish_connection!
|
43
|
+
def establish_connection(options = {}) # :yields: current_context
|
44
|
+
establish_connection!(options)
|
45
|
+
if (block_given?)
|
46
|
+
begin
|
47
|
+
return yield
|
48
|
+
ensure
|
49
|
+
@connections.pop() if connected?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the default connection (last set) parameters. It will raise an exception WAZ::Storage::NotConnected
|
55
|
+
# when there's no connection information registered.
|
56
|
+
def default_connection
|
57
|
+
raise NotConnected unless connected?
|
58
|
+
return @connections.last
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns a value indicating whether the current connection information has been set or not.
|
62
|
+
def connected?
|
63
|
+
return false if (@connections.nil?)
|
64
|
+
return false if (@connections.empty?)
|
65
|
+
return true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -1,33 +1,33 @@
|
|
1
|
-
module WAZ
|
2
|
-
module Storage
|
3
|
-
# This class is the base exception from where all the exceptions raised from this API
|
4
|
-
# inherit from. If you want to handle an exception that your code may throw and you don't
|
5
|
-
# know which specific type you should handle, handle this type of exception.
|
6
|
-
class StorageException < StandardError
|
7
|
-
end
|
8
|
-
|
9
|
-
# This exception raises whenever a required parameter for initializing any class isn't provided. From
|
10
|
-
# WAZ::Storage::Base up to WAZ::Queues::Queue all of them use this exception.
|
11
|
-
class InvalidOption < StorageException
|
12
|
-
def initialize(missing_option)
|
13
|
-
super("You did not provide one of the required parameters. Please provide the #{missing_option}.")
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
# This exception is raised when the user tries to perform an operation on any Storage API class
|
18
|
-
# without previously specificying the connection options.
|
19
|
-
class NotConnected < StorageException
|
20
|
-
def initialize
|
21
|
-
super("You should establish connection before using the services, the connection configuration is required.")
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
# This exception if raised when the value given for an argument doesn't fall into the permitted values. For example
|
26
|
-
# if values on the blocklisttype aren't [all|uncommitted|committed]
|
27
|
-
class InvalidParameterValue < StorageException
|
28
|
-
def initialize(args = {})
|
29
|
-
super("The value supplied for the parameter #{args[:name]} is invalid. Permitted values are #{args[:values].join(',')}")
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
1
|
+
module WAZ
|
2
|
+
module Storage
|
3
|
+
# This class is the base exception from where all the exceptions raised from this API
|
4
|
+
# inherit from. If you want to handle an exception that your code may throw and you don't
|
5
|
+
# know which specific type you should handle, handle this type of exception.
|
6
|
+
class StorageException < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
# This exception raises whenever a required parameter for initializing any class isn't provided. From
|
10
|
+
# WAZ::Storage::Base up to WAZ::Queues::Queue all of them use this exception.
|
11
|
+
class InvalidOption < StorageException
|
12
|
+
def initialize(missing_option)
|
13
|
+
super("You did not provide one of the required parameters. Please provide the #{missing_option}.")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# This exception is raised when the user tries to perform an operation on any Storage API class
|
18
|
+
# without previously specificying the connection options.
|
19
|
+
class NotConnected < StorageException
|
20
|
+
def initialize
|
21
|
+
super("You should establish connection before using the services, the connection configuration is required.")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# This exception if raised when the value given for an argument doesn't fall into the permitted values. For example
|
26
|
+
# if values on the blocklisttype aren't [all|uncommitted|committed]
|
27
|
+
class InvalidParameterValue < StorageException
|
28
|
+
def initialize(args = {})
|
29
|
+
super("The value supplied for the parameter #{args[:name]} is invalid. Permitted values are #{args[:values].join(',')}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,26 +1,26 @@
|
|
1
|
-
module WAZ
|
2
|
-
module Storage
|
3
|
-
class ValidationRules
|
4
|
-
class << self
|
5
|
-
# Validates that the Container/Queue name given matches with the requirements of Windows Azure.
|
6
|
-
#
|
7
|
-
# -Container/Queue names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character.
|
8
|
-
# -Every dash (-) character must be immediately preceded and followed by a letter or number.
|
9
|
-
# -All letters in a container name must be lowercase.
|
10
|
-
# -Container/Queue names must be from 3 through 63 characters long.
|
11
|
-
def valid_name?(name)
|
12
|
-
name =~ /^[a-z0-9][a-z0-9\-]{1,}[^-]$/ && name.length < 64
|
13
|
-
end
|
14
|
-
|
15
|
-
# Validates that the Table name given matches with the requirements of Windows Azure.
|
16
|
-
#
|
17
|
-
# -Table names must start with at least one lower / upper character.
|
18
|
-
# -Table names can have character or any digit starting from the second position.
|
19
|
-
# -Table names must be from 3 through 63 characters long.
|
20
|
-
def valid_table_name?(name)
|
21
|
-
name =~ /^([a-z]|[A-Z]){1}([a-z]|[A-Z]|\d){2,62}$/
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
1
|
+
module WAZ
|
2
|
+
module Storage
|
3
|
+
class ValidationRules
|
4
|
+
class << self
|
5
|
+
# Validates that the Container/Queue name given matches with the requirements of Windows Azure.
|
6
|
+
#
|
7
|
+
# -Container/Queue names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character.
|
8
|
+
# -Every dash (-) character must be immediately preceded and followed by a letter or number.
|
9
|
+
# -All letters in a container name must be lowercase.
|
10
|
+
# -Container/Queue names must be from 3 through 63 characters long.
|
11
|
+
def valid_name?(name)
|
12
|
+
name =~ /^[a-z0-9][a-z0-9\-]{1,}[^-]$/ && name.length < 64
|
13
|
+
end
|
14
|
+
|
15
|
+
# Validates that the Table name given matches with the requirements of Windows Azure.
|
16
|
+
#
|
17
|
+
# -Table names must start with at least one lower / upper character.
|
18
|
+
# -Table names can have character or any digit starting from the second position.
|
19
|
+
# -Table names must be from 3 through 63 characters long.
|
20
|
+
def valid_table_name?(name)
|
21
|
+
name =~ /^([a-z]|[A-Z]){1}([a-z]|[A-Z]|\d){2,62}$/
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
26
|
end
|
@@ -1,45 +1,45 @@
|
|
1
|
-
module WAZ
|
2
|
-
module Tables
|
3
|
-
class EdmTypeHelper
|
4
|
-
class << self
|
5
|
-
def parse_from(item)
|
6
|
-
return nil if !item.attributes['m:null'].nil? and item.attributes['m:null'] == 'true'
|
7
|
-
case item.attributes['m:type']
|
8
|
-
when 'Edm.Int16', 'Edm.Int32', 'Edm.Int64'
|
9
|
-
item.text.to_i
|
10
|
-
when 'Edm.Single', 'Edm.Double'
|
11
|
-
item.text.to_f
|
12
|
-
when 'Edm.Boolean'
|
13
|
-
item.text == 'true'
|
14
|
-
when 'Edm.DateTime'
|
15
|
-
Time.parse(item.text)
|
16
|
-
when 'Edm.Binary'
|
17
|
-
StringIO.new(Base64.decode64(item.text))
|
18
|
-
else
|
19
|
-
item.text
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def parse_to(item)
|
24
|
-
case item.class.name
|
25
|
-
when 'String'
|
26
|
-
[item, 'Edm.String']
|
27
|
-
when 'Fixnum'
|
28
|
-
[item, 'Edm.Int32']
|
29
|
-
when 'Float'
|
30
|
-
[item, 'Edm.Double']
|
31
|
-
when 'TrueClass', 'FalseClass'
|
32
|
-
[item, 'Edm.Boolean']
|
33
|
-
when 'Time'
|
34
|
-
[item.iso8601, 'Edm.DateTime']
|
35
|
-
when 'File', 'StringIO'
|
36
|
-
item.pos = 0
|
37
|
-
[Base64.encode64(item.read), 'Edm.Binary']
|
38
|
-
else
|
39
|
-
[item, 'Edm.String']
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
1
|
+
module WAZ
|
2
|
+
module Tables
|
3
|
+
class EdmTypeHelper
|
4
|
+
class << self
|
5
|
+
def parse_from(item)
|
6
|
+
return nil if !item.attributes['m:null'].nil? and item.attributes['m:null'] == 'true'
|
7
|
+
case item.attributes['m:type']
|
8
|
+
when 'Edm.Int16', 'Edm.Int32', 'Edm.Int64'
|
9
|
+
item.text.to_i
|
10
|
+
when 'Edm.Single', 'Edm.Double'
|
11
|
+
item.text.to_f
|
12
|
+
when 'Edm.Boolean'
|
13
|
+
item.text == 'true'
|
14
|
+
when 'Edm.DateTime'
|
15
|
+
Time.parse(item.text)
|
16
|
+
when 'Edm.Binary'
|
17
|
+
StringIO.new(Base64.decode64(item.text))
|
18
|
+
else
|
19
|
+
item.text
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_to(item)
|
24
|
+
case item.class.name
|
25
|
+
when 'String'
|
26
|
+
[item, 'Edm.String']
|
27
|
+
when 'Fixnum'
|
28
|
+
[item, 'Edm.Int32']
|
29
|
+
when 'Float'
|
30
|
+
[item, 'Edm.Double']
|
31
|
+
when 'TrueClass', 'FalseClass'
|
32
|
+
[item, 'Edm.Boolean']
|
33
|
+
when 'Time'
|
34
|
+
[item.iso8601, 'Edm.DateTime']
|
35
|
+
when 'File', 'StringIO'
|
36
|
+
item.pos = 0
|
37
|
+
[Base64.encode64(item.read), 'Edm.Binary']
|
38
|
+
else
|
39
|
+
[item, 'Edm.String']
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
45
|
end
|
@@ -1,45 +1,45 @@
|
|
1
|
-
module WAZ
|
2
|
-
module Tables
|
3
|
-
# This exception is raised while trying to create table that already exists.
|
4
|
-
class TableAlreadyExists < WAZ::Storage::StorageException
|
5
|
-
def initialize(name)
|
6
|
-
super("The table #{name} already exists on your account.")
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
# This exception is raised while trying to delete an unexisting table.
|
11
|
-
class TableDoesNotExist < WAZ::Storage::StorageException
|
12
|
-
def initialize(name)
|
13
|
-
super("The specified table #{name} does not exist.")
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
# This exception is raised when an invalid table name is provided.
|
18
|
-
class InvalidTableName < WAZ::Storage::StorageException
|
19
|
-
def initialize(name)
|
20
|
-
super("The table name #{name} is invalid, it must start with at least one lower/upper characted, must be from 3 through 63 characters long and can have character or any digit starting from the second position")
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
# This exception is raised when provided more than the 252 properties allowed by the Rest API.
|
25
|
-
class TooManyProperties < WAZ::Storage::StorageException
|
26
|
-
def initialize(total)
|
27
|
-
super("The entity contains more properties than allowed (252). The entity has #{total} properties.")
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# This exception is raised when the specified entity already exists.
|
32
|
-
class EntityAlreadyExists < WAZ::Storage::StorageException
|
33
|
-
def initialize(row_key)
|
34
|
-
super("The specified entity already exists. RowKey: #{row_key}")
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# This exception is raised while trying to delete an unexisting entity.
|
39
|
-
class EntityDoesNotExist < WAZ::Storage::StorageException
|
40
|
-
def initialize(key)
|
41
|
-
super("The specified entity with #{key} does not exist.")
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
1
|
+
module WAZ
|
2
|
+
module Tables
|
3
|
+
# This exception is raised while trying to create table that already exists.
|
4
|
+
class TableAlreadyExists < WAZ::Storage::StorageException
|
5
|
+
def initialize(name)
|
6
|
+
super("The table #{name} already exists on your account.")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# This exception is raised while trying to delete an unexisting table.
|
11
|
+
class TableDoesNotExist < WAZ::Storage::StorageException
|
12
|
+
def initialize(name)
|
13
|
+
super("The specified table #{name} does not exist.")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# This exception is raised when an invalid table name is provided.
|
18
|
+
class InvalidTableName < WAZ::Storage::StorageException
|
19
|
+
def initialize(name)
|
20
|
+
super("The table name #{name} is invalid, it must start with at least one lower/upper characted, must be from 3 through 63 characters long and can have character or any digit starting from the second position")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# This exception is raised when provided more than the 252 properties allowed by the Rest API.
|
25
|
+
class TooManyProperties < WAZ::Storage::StorageException
|
26
|
+
def initialize(total)
|
27
|
+
super("The entity contains more properties than allowed (252). The entity has #{total} properties.")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# This exception is raised when the specified entity already exists.
|
32
|
+
class EntityAlreadyExists < WAZ::Storage::StorageException
|
33
|
+
def initialize(row_key)
|
34
|
+
super("The specified entity already exists. RowKey: #{row_key}")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# This exception is raised while trying to delete an unexisting entity.
|
39
|
+
class EntityDoesNotExist < WAZ::Storage::StorageException
|
40
|
+
def initialize(key)
|
41
|
+
super("The specified entity with #{key} does not exist.")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
45
|
end
|
data/lib/waz/tables/service.rb
CHANGED
@@ -1,178 +1,178 @@
|
|
1
|
-
module WAZ
|
2
|
-
module Tables
|
3
|
-
# This is internally used by the waz-tables part of the gem and it exposes the Windows Azure Blob API REST methods
|
4
|
-
# implementation. You can use this class to perform an specific operation that isn't provided by the current API.
|
5
|
-
class Service
|
6
|
-
include WAZ::Storage::SharedKeyCoreService
|
7
|
-
|
8
|
-
DATASERVICES_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices"
|
9
|
-
DATASERVICES_METADATA_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
|
10
|
-
|
11
|
-
# Creates a table on the current Windows Azure Storage account.
|
12
|
-
def create_table(table_name)
|
13
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
14
|
-
|
15
|
-
payload = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" \
|
16
|
-
"<entry xmlns:d=\"#{DATASERVICES_NAMESPACE}\" xmlns:m=\"#{DATASERVICES_METADATA_NAMESPACE}\" xmlns=\"http://www.w3.org/2005/Atom\">" \
|
17
|
-
"<title /><updated>#{Time.now.utc.iso8601}</updated><author><name/></author><id/>" \
|
18
|
-
"<content type=\"application/xml\"><m:properties><d:TableName>#{table_name}</d:TableName></m:properties></content></entry>"
|
19
|
-
|
20
|
-
begin
|
21
|
-
execute :post, 'Tables', {}, default_headers, payload
|
22
|
-
return {:name => table_name, :url => "#{self.base_url}/Tables('#{table_name}"}
|
23
|
-
rescue RestClient::RequestFailed
|
24
|
-
raise WAZ::Tables::TableAlreadyExists, table_name if $!.http_code == 409
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# Delete a table on the current Windows Azure Storage account.
|
29
|
-
def delete_table(table_name)
|
30
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
31
|
-
begin
|
32
|
-
execute :delete, "Tables('#{table_name}')", {}, default_headers
|
33
|
-
rescue RestClient::ResourceNotFound
|
34
|
-
raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# Lists all existing tables on the current storage account.
|
39
|
-
# remove Content-Type if it's not working
|
40
|
-
def list_tables(next_table_name = nil)
|
41
|
-
query = { 'NextTableName' => next_table_name } unless next_table_name.nil?
|
42
|
-
content = execute :get, "Tables", query ||= {}, default_headers
|
43
|
-
|
44
|
-
doc = REXML::Document.new(content)
|
45
|
-
tables = REXML::XPath.each(doc, '/feed/entry').map do |item|
|
46
|
-
{ :name => REXML::XPath.first(item.elements['content'], "m:properties/d:TableName", {"m" => DATASERVICES_METADATA_NAMESPACE, "d" => DATASERVICES_NAMESPACE}).text,
|
47
|
-
:url => REXML::XPath.first(item, "id").text }
|
48
|
-
end
|
49
|
-
|
50
|
-
return tables, content.headers[:x_ms_continuation_nexttablename]
|
51
|
-
end
|
52
|
-
|
53
|
-
# Retrieves an existing table on the current storage account.
|
54
|
-
# remove Content-Type if it's not working
|
55
|
-
def get_table(table_name)
|
56
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
57
|
-
|
58
|
-
begin
|
59
|
-
content = execute :get, "Tables('#{table_name}')", {}, default_headers
|
60
|
-
doc = REXML::Document.new(content)
|
61
|
-
item = REXML::XPath.first(doc, "entry")
|
62
|
-
return { :name => REXML::XPath.first(item.elements['content'], "m:properties/d:TableName", {"m" => DATASERVICES_METADATA_NAMESPACE, "d" => DATASERVICES_NAMESPACE}).text,
|
63
|
-
:url => REXML::XPath.first(item, "id").text }
|
64
|
-
rescue RestClient::ResourceNotFound
|
65
|
-
raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# Insert a new entity on the provided table for the current storage account
|
70
|
-
# TODO: catch all api errors as described on Table Service Error Codes on MSDN (http://msdn.microsoft.com/en-us/library/dd179438.aspx)
|
71
|
-
def insert_entity(table_name, entity)
|
72
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
73
|
-
raise WAZ::Tables::TooManyProperties, entity.length if entity.length > 252
|
74
|
-
|
75
|
-
begin
|
76
|
-
response = execute(:post, table_name, {}, default_headers, generate_payload(table_name, entity))
|
77
|
-
return parse_response(response)
|
78
|
-
rescue RestClient::RequestFailed
|
79
|
-
raise WAZ::Tables::EntityAlreadyExists, entity[:row_key] if $!.http_code == 409 and $!.response.body.include?('EntityAlreadyExists')
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# Update an existing entity on the current storage account.
|
84
|
-
# TODO: handle specific errors
|
85
|
-
def update_entity(table_name, entity)
|
86
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
87
|
-
response = execute(:put, "#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')", {}, default_headers.merge({'If-Match' => '*'}) , generate_payload(table_name, entity))
|
88
|
-
return parse_response(response)
|
89
|
-
end
|
90
|
-
|
91
|
-
# Merge an existing entity on the current storage account.
|
92
|
-
# The Merge Entity operation updates an existing entity by updating the entity's properties.
|
93
|
-
# This operation does not replace the existing entity, as the Update Entity operation does
|
94
|
-
# TODO: handle specific errors
|
95
|
-
def merge_entity(table_name, entity)
|
96
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
97
|
-
response = execute(:merge, "#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')", {}, default_headers.merge({'If-Match' => '*'}), generate_payload(table_name, entity))
|
98
|
-
return parse_response(response)
|
99
|
-
end
|
100
|
-
|
101
|
-
# Delete an existing entity in a table.
|
102
|
-
def delete_entity(table_name, partition_key, row_key)
|
103
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
104
|
-
|
105
|
-
begin
|
106
|
-
execute :delete, "#{table_name}(PartitionKey='#{partition_key}',RowKey='#{row_key}')", {}, default_headers.merge({'If-Match' => '*'})
|
107
|
-
rescue RestClient::ResourceNotFound
|
108
|
-
raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404 and $!.response.body.include?('TableNotFound')
|
109
|
-
raise WAZ::Tables::EntityDoesNotExist, "(PartitionKey='#{partition_key}',RowKey='#{row_key}')" if $!.http_code == 404
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
# Retrieves an existing entity on the current storage account.
|
114
|
-
# TODO: handle specific errors
|
115
|
-
def get_entity(table_name, partition_key, row_key)
|
116
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
117
|
-
response = execute(:get, "#{table_name}(PartitionKey='#{partition_key}',RowKey='#{row_key}')", {}, default_headers)
|
118
|
-
return parse_response(response)
|
119
|
-
end
|
120
|
-
|
121
|
-
# Retrieves a set of entities on the current storage account for a given query.
|
122
|
-
# When the :top => n is passed it returns only the first n rows that match with the query
|
123
|
-
# Optional parameters:
|
124
|
-
# * :headers a hash containing the request headers
|
125
|
-
# * :expression the filter query that will be executed against the table (see http://msdn.microsoft.com/en-us/library/dd179421.aspx for more information),
|
126
|
-
# * :top limits the amount of fields for this query.
|
127
|
-
# * :continuation_token the hash obtained when you perform a query that has more than 1000 records or exceeds the allowed timeout (see http://msdn.microsoft.com/en-us/library/dd135718.aspx)
|
128
|
-
def query(table_name, options = {})
|
129
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
130
|
-
query = {'$filter' => (options[:expression] or '') }
|
131
|
-
query.merge!({ '$top' => options[:top] }) unless options[:top].nil?
|
132
|
-
query.merge!(options[:continuation_token]) unless options[:continuation_token].nil?
|
133
|
-
response = execute :get, "#{table_name}()", query, default_headers
|
134
|
-
continuation_token = {'NextPartitionKey' => response.headers[:x_ms_continuation_nextpartitionkey], 'NextRowKey' => response.headers[:x_ms_continuation_nextrowkey]}
|
135
|
-
parse_response(response, continuation_token)
|
136
|
-
end
|
137
|
-
|
138
|
-
private
|
139
|
-
def generate_payload(table_name, entity)
|
140
|
-
payload = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" \
|
141
|
-
"<entry xmlns:d=\"#{DATASERVICES_NAMESPACE}\" xmlns:m=\"#{DATASERVICES_METADATA_NAMESPACE}\" xmlns=\"http://www.w3.org/2005/Atom\">" \
|
142
|
-
"<id>#{generate_request_uri "#{table_name}"}(PartitionKey='#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}',RowKey='#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}')</id>" \
|
143
|
-
"<title /><updated>#{Time.now.utc.iso8601}</updated><author><name /></author><link rel=\"edit\" title=\"#{table_name}\" href=\"#{table_name}(PartitionKey='#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}',RowKey='#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}')\" />" \
|
144
|
-
"<content type=\"application/xml\"><m:properties>"
|
145
|
-
|
146
|
-
entity.sort_by { |k| k.to_s }.each do |k,v|
|
147
|
-
value, type = EdmTypeHelper.parse_to(v)[0].to_s, EdmTypeHelper.parse_to(v)[1].to_s
|
148
|
-
payload << (!v.nil? ? "<d:#{k.to_s} m:type=\"#{k.edm_type || type}\">#{REXML::Text.new(value, false, nil, false).to_s}</d:#{k.to_s}>" : "<d:#{k.to_s} m:type=\"#{k.edm_type || type}\" m:null=\"true\" />") unless k.eql?(:partition_key) or k.eql?(:row_key)
|
149
|
-
end
|
150
|
-
|
151
|
-
payload << "<d:PartitionKey>#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}</d:PartitionKey>" \
|
152
|
-
"<d:RowKey>#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}</d:RowKey>" \
|
153
|
-
"</m:properties></content></entry>"
|
154
|
-
return payload
|
155
|
-
end
|
156
|
-
|
157
|
-
def parse_response(response, continuation_token = nil)
|
158
|
-
doc = REXML::Document.new(response)
|
159
|
-
entities = REXML::XPath.each(doc, '//entry').map do |entry|
|
160
|
-
fields = REXML::XPath.each(entry.elements['content'], 'm:properties/*', {"m" => DATASERVICES_METADATA_NAMESPACE}).map do |f|
|
161
|
-
{ f.name.gsub(/PartitionKey/i, 'partition_key').gsub(/RowKey/i, 'row_key').to_sym => EdmTypeHelper.parse_from(f) }
|
162
|
-
end
|
163
|
-
Hash[*fields.collect {|h| h.to_a}.flatten]
|
164
|
-
end
|
165
|
-
entities = WAZ::Tables::TableArray.new(entities)
|
166
|
-
entities.continuation_token = continuation_token
|
167
|
-
return (REXML::XPath.first(doc, '/feed')) ? entities : entities.first
|
168
|
-
end
|
169
|
-
|
170
|
-
def default_headers
|
171
|
-
{ 'Date' => Time.new.httpdate,
|
172
|
-
'Content-Type' => 'application/atom+xml',
|
173
|
-
'DataServiceVersion' => '1.0;NetFx',
|
174
|
-
'MaxDataServiceVersion' => '1.0;NetFx' }
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
1
|
+
module WAZ
|
2
|
+
module Tables
|
3
|
+
# This is internally used by the waz-tables part of the gem and it exposes the Windows Azure Blob API REST methods
|
4
|
+
# implementation. You can use this class to perform an specific operation that isn't provided by the current API.
|
5
|
+
class Service
|
6
|
+
include WAZ::Storage::SharedKeyCoreService
|
7
|
+
|
8
|
+
DATASERVICES_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices"
|
9
|
+
DATASERVICES_METADATA_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
|
10
|
+
|
11
|
+
# Creates a table on the current Windows Azure Storage account.
|
12
|
+
def create_table(table_name)
|
13
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
14
|
+
|
15
|
+
payload = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" \
|
16
|
+
"<entry xmlns:d=\"#{DATASERVICES_NAMESPACE}\" xmlns:m=\"#{DATASERVICES_METADATA_NAMESPACE}\" xmlns=\"http://www.w3.org/2005/Atom\">" \
|
17
|
+
"<title /><updated>#{Time.now.utc.iso8601}</updated><author><name/></author><id/>" \
|
18
|
+
"<content type=\"application/xml\"><m:properties><d:TableName>#{table_name}</d:TableName></m:properties></content></entry>"
|
19
|
+
|
20
|
+
begin
|
21
|
+
execute :post, 'Tables', {}, default_headers, payload
|
22
|
+
return {:name => table_name, :url => "#{self.base_url}/Tables('#{table_name}"}
|
23
|
+
rescue RestClient::RequestFailed
|
24
|
+
raise WAZ::Tables::TableAlreadyExists, table_name if $!.http_code == 409
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Delete a table on the current Windows Azure Storage account.
|
29
|
+
def delete_table(table_name)
|
30
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
31
|
+
begin
|
32
|
+
execute :delete, "Tables('#{table_name}')", {}, default_headers
|
33
|
+
rescue RestClient::ResourceNotFound
|
34
|
+
raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Lists all existing tables on the current storage account.
|
39
|
+
# remove Content-Type if it's not working
|
40
|
+
def list_tables(next_table_name = nil)
|
41
|
+
query = { 'NextTableName' => next_table_name } unless next_table_name.nil?
|
42
|
+
content = execute :get, "Tables", query ||= {}, default_headers
|
43
|
+
|
44
|
+
doc = REXML::Document.new(content)
|
45
|
+
tables = REXML::XPath.each(doc, '/feed/entry').map do |item|
|
46
|
+
{ :name => REXML::XPath.first(item.elements['content'], "m:properties/d:TableName", {"m" => DATASERVICES_METADATA_NAMESPACE, "d" => DATASERVICES_NAMESPACE}).text,
|
47
|
+
:url => REXML::XPath.first(item, "id").text }
|
48
|
+
end
|
49
|
+
|
50
|
+
return tables, content.headers[:x_ms_continuation_nexttablename]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Retrieves an existing table on the current storage account.
|
54
|
+
# remove Content-Type if it's not working
|
55
|
+
def get_table(table_name)
|
56
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
57
|
+
|
58
|
+
begin
|
59
|
+
content = execute :get, "Tables('#{table_name}')", {}, default_headers
|
60
|
+
doc = REXML::Document.new(content)
|
61
|
+
item = REXML::XPath.first(doc, "entry")
|
62
|
+
return { :name => REXML::XPath.first(item.elements['content'], "m:properties/d:TableName", {"m" => DATASERVICES_METADATA_NAMESPACE, "d" => DATASERVICES_NAMESPACE}).text,
|
63
|
+
:url => REXML::XPath.first(item, "id").text }
|
64
|
+
rescue RestClient::ResourceNotFound
|
65
|
+
raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Insert a new entity on the provided table for the current storage account
|
70
|
+
# TODO: catch all api errors as described on Table Service Error Codes on MSDN (http://msdn.microsoft.com/en-us/library/dd179438.aspx)
|
71
|
+
def insert_entity(table_name, entity)
|
72
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
73
|
+
raise WAZ::Tables::TooManyProperties, entity.length if entity.length > 252
|
74
|
+
|
75
|
+
begin
|
76
|
+
response = execute(:post, table_name, {}, default_headers, generate_payload(table_name, entity))
|
77
|
+
return parse_response(response)
|
78
|
+
rescue RestClient::RequestFailed
|
79
|
+
raise WAZ::Tables::EntityAlreadyExists, entity[:row_key] if $!.http_code == 409 and $!.response.body.include?('EntityAlreadyExists')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Update an existing entity on the current storage account.
|
84
|
+
# TODO: handle specific errors
|
85
|
+
def update_entity(table_name, entity)
|
86
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
87
|
+
response = execute(:put, "#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')", {}, default_headers.merge({'If-Match' => '*'}) , generate_payload(table_name, entity))
|
88
|
+
return parse_response(response)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Merge an existing entity on the current storage account.
|
92
|
+
# The Merge Entity operation updates an existing entity by updating the entity's properties.
|
93
|
+
# This operation does not replace the existing entity, as the Update Entity operation does
|
94
|
+
# TODO: handle specific errors
|
95
|
+
def merge_entity(table_name, entity)
|
96
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
97
|
+
response = execute(:merge, "#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')", {}, default_headers.merge({'If-Match' => '*'}), generate_payload(table_name, entity))
|
98
|
+
return parse_response(response)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Delete an existing entity in a table.
|
102
|
+
def delete_entity(table_name, partition_key, row_key)
|
103
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
104
|
+
|
105
|
+
begin
|
106
|
+
execute :delete, "#{table_name}(PartitionKey='#{partition_key}',RowKey='#{row_key}')", {}, default_headers.merge({'If-Match' => '*'})
|
107
|
+
rescue RestClient::ResourceNotFound
|
108
|
+
raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404 and $!.response.body.include?('TableNotFound')
|
109
|
+
raise WAZ::Tables::EntityDoesNotExist, "(PartitionKey='#{partition_key}',RowKey='#{row_key}')" if $!.http_code == 404
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Retrieves an existing entity on the current storage account.
|
114
|
+
# TODO: handle specific errors
|
115
|
+
def get_entity(table_name, partition_key, row_key)
|
116
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
117
|
+
response = execute(:get, "#{table_name}(PartitionKey='#{partition_key}',RowKey='#{row_key}')", {}, default_headers)
|
118
|
+
return parse_response(response)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Retrieves a set of entities on the current storage account for a given query.
|
122
|
+
# When the :top => n is passed it returns only the first n rows that match with the query
|
123
|
+
# Optional parameters:
|
124
|
+
# * :headers a hash containing the request headers
|
125
|
+
# * :expression the filter query that will be executed against the table (see http://msdn.microsoft.com/en-us/library/dd179421.aspx for more information),
|
126
|
+
# * :top limits the amount of fields for this query.
|
127
|
+
# * :continuation_token the hash obtained when you perform a query that has more than 1000 records or exceeds the allowed timeout (see http://msdn.microsoft.com/en-us/library/dd135718.aspx)
|
128
|
+
def query(table_name, options = {})
|
129
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
130
|
+
query = {'$filter' => (options[:expression] or '') }
|
131
|
+
query.merge!({ '$top' => options[:top] }) unless options[:top].nil?
|
132
|
+
query.merge!(options[:continuation_token]) unless options[:continuation_token].nil?
|
133
|
+
response = execute :get, "#{table_name}()", query, default_headers
|
134
|
+
continuation_token = {'NextPartitionKey' => response.headers[:x_ms_continuation_nextpartitionkey], 'NextRowKey' => response.headers[:x_ms_continuation_nextrowkey]}
|
135
|
+
parse_response(response, continuation_token)
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
def generate_payload(table_name, entity)
|
140
|
+
payload = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" \
|
141
|
+
"<entry xmlns:d=\"#{DATASERVICES_NAMESPACE}\" xmlns:m=\"#{DATASERVICES_METADATA_NAMESPACE}\" xmlns=\"http://www.w3.org/2005/Atom\">" \
|
142
|
+
"<id>#{generate_request_uri "#{table_name}"}(PartitionKey='#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}',RowKey='#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}')</id>" \
|
143
|
+
"<title /><updated>#{Time.now.utc.iso8601}</updated><author><name /></author><link rel=\"edit\" title=\"#{table_name}\" href=\"#{table_name}(PartitionKey='#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}',RowKey='#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}')\" />" \
|
144
|
+
"<content type=\"application/xml\"><m:properties>"
|
145
|
+
|
146
|
+
entity.sort_by { |k| k.to_s }.each do |k,v|
|
147
|
+
value, type = EdmTypeHelper.parse_to(v)[0].to_s, EdmTypeHelper.parse_to(v)[1].to_s
|
148
|
+
payload << (!v.nil? ? "<d:#{k.to_s} m:type=\"#{k.edm_type || type}\">#{REXML::Text.new(value, false, nil, false).to_s}</d:#{k.to_s}>" : "<d:#{k.to_s} m:type=\"#{k.edm_type || type}\" m:null=\"true\" />") unless k.eql?(:partition_key) or k.eql?(:row_key)
|
149
|
+
end
|
150
|
+
|
151
|
+
payload << "<d:PartitionKey>#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}</d:PartitionKey>" \
|
152
|
+
"<d:RowKey>#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}</d:RowKey>" \
|
153
|
+
"</m:properties></content></entry>"
|
154
|
+
return payload
|
155
|
+
end
|
156
|
+
|
157
|
+
def parse_response(response, continuation_token = nil)
|
158
|
+
doc = REXML::Document.new(response)
|
159
|
+
entities = REXML::XPath.each(doc, '//entry').map do |entry|
|
160
|
+
fields = REXML::XPath.each(entry.elements['content'], 'm:properties/*', {"m" => DATASERVICES_METADATA_NAMESPACE}).map do |f|
|
161
|
+
{ f.name.gsub(/PartitionKey/i, 'partition_key').gsub(/RowKey/i, 'row_key').to_sym => EdmTypeHelper.parse_from(f) }
|
162
|
+
end
|
163
|
+
Hash[*fields.collect {|h| h.to_a}.flatten]
|
164
|
+
end
|
165
|
+
entities = WAZ::Tables::TableArray.new(entities)
|
166
|
+
entities.continuation_token = continuation_token
|
167
|
+
return (REXML::XPath.first(doc, '/feed')) ? entities : entities.first
|
168
|
+
end
|
169
|
+
|
170
|
+
def default_headers
|
171
|
+
{ 'Date' => Time.new.httpdate,
|
172
|
+
'Content-Type' => 'application/atom+xml',
|
173
|
+
'DataServiceVersion' => '1.0;NetFx',
|
174
|
+
'MaxDataServiceVersion' => '1.0;NetFx' }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|