waz-storage 0.5.81 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/waz-storage.rb +14 -0
- data/lib/waz-tables.rb +20 -0
- data/lib/waz/blobs/blob_object.rb +1 -1
- data/lib/waz/blobs/container.rb +1 -1
- data/lib/waz/queues/message.rb +1 -1
- data/lib/waz/queues/queue.rb +1 -1
- data/lib/waz/storage/core_service.rb +8 -5
- data/lib/waz/storage/validation_rules.rb +9 -0
- data/lib/waz/storage/version.rb +3 -3
- data/lib/waz/tables/edm_type_helper.rb +45 -0
- data/lib/waz/tables/exceptions.rb +45 -0
- data/lib/waz/tables/service.rb +178 -0
- data/lib/waz/tables/table.rb +75 -0
- data/lib/waz/tables/table_array.rb +11 -0
- data/rakefile +2 -3
- metadata +8 -2
data/lib/waz-storage.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
1
3
|
$:.unshift(File.dirname(__FILE__))
|
2
4
|
require 'waz/storage/base'
|
3
5
|
require 'waz/storage/core_service'
|
@@ -14,4 +16,16 @@ unless String.method_defined? :start_with?
|
|
14
16
|
self[0, prefix.length] == prefix
|
15
17
|
end
|
16
18
|
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# The Merge method is not defined in the RFC 2616
|
22
|
+
# and it's required to Merge entities in Windows Azure
|
23
|
+
module Net
|
24
|
+
class HTTP < Protocol
|
25
|
+
class Merge < HTTPRequest
|
26
|
+
METHOD = 'MERGE'
|
27
|
+
REQUEST_HAS_BODY = true
|
28
|
+
RESPONSE_HAS_BODY = false
|
29
|
+
end
|
30
|
+
end
|
17
31
|
end
|
data/lib/waz-tables.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'cgi'
|
3
|
+
require 'base64'
|
4
|
+
require 'rexml/document'
|
5
|
+
require 'rexml/xpath'
|
6
|
+
require 'restclient'
|
7
|
+
require 'hmac-sha2'
|
8
|
+
|
9
|
+
$:.unshift(File.dirname(__FILE__))
|
10
|
+
require 'waz-storage'
|
11
|
+
require 'waz/tables/exceptions'
|
12
|
+
require 'waz/tables/table'
|
13
|
+
require 'waz/tables/table_array'
|
14
|
+
require 'waz/tables/service'
|
15
|
+
require 'waz/tables/edm_type_helper'
|
16
|
+
|
17
|
+
# extendes the Symbol class to assign a type to an entity field
|
18
|
+
class Symbol
|
19
|
+
attr_accessor :edm_type
|
20
|
+
end
|
@@ -31,7 +31,7 @@ module WAZ
|
|
31
31
|
# from the default_connection on WAZ::Storage::Base initialized thru establish_connection!
|
32
32
|
def service_instance
|
33
33
|
options = WAZ::Storage::Base.default_connection.merge(:type_of_service => "blob")
|
34
|
-
(@service_instances ||= {})[:account_name] ||= Service.new(options)
|
34
|
+
(@service_instances ||= {})[options[:account_name]] ||= Service.new(options)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
data/lib/waz/blobs/container.rb
CHANGED
@@ -66,7 +66,7 @@ module WAZ
|
|
66
66
|
# from the default_connection on WAZ::Storage::Base initialized thru establish_connection!
|
67
67
|
def service_instance
|
68
68
|
options = WAZ::Storage::Base.default_connection.merge(:type_of_service => "blob")
|
69
|
-
(@service_instances ||= {})[:account_name] ||= Service.new(options)
|
69
|
+
(@service_instances ||= {})[options[:account_name]] ||= Service.new(options)
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
data/lib/waz/queues/message.rb
CHANGED
@@ -29,7 +29,7 @@ module WAZ
|
|
29
29
|
# from the default_connection on WAZ::Storage::Base initialized thru establish_connection!
|
30
30
|
def service_instance
|
31
31
|
options = WAZ::Storage::Base.default_connection.merge(:type_of_service => "queue")
|
32
|
-
(@service_instances ||= {})[:account_name] ||= Service.new(options)
|
32
|
+
(@service_instances ||= {})[options[:account_name]] ||= Service.new(options)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
data/lib/waz/queues/queue.rb
CHANGED
@@ -76,7 +76,7 @@ module WAZ
|
|
76
76
|
# from the default_connection on WAZ::Storage::Base initialized thru establish_connection!
|
77
77
|
def service_instance
|
78
78
|
options = WAZ::Storage::Base.default_connection.merge(:type_of_service => "queue")
|
79
|
-
(@service_instances ||= {})[:account_name] ||= Service.new(options)
|
79
|
+
(@service_instances ||= {})[options[:account_name]] ||= Service.new(options)
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
@@ -3,15 +3,17 @@ module WAZ
|
|
3
3
|
# This module is imported by the specific services that use Shared Key authentication profile. On the current implementation
|
4
4
|
# this module is imported from WAZ::Queues::Service and WAZ::Blobs::Service.
|
5
5
|
module SharedKeyCoreService
|
6
|
-
attr_accessor :account_name, :access_key, :use_ssl, :base_url, :type_of_service
|
6
|
+
attr_accessor :account_name, :access_key, :use_ssl, :base_url, :type_of_service, :use_devenv
|
7
7
|
|
8
8
|
# Creates an instance of the implementor service (internally used by the API).
|
9
9
|
def initialize(options = {})
|
10
10
|
self.account_name = options[:account_name]
|
11
11
|
self.access_key = options[:access_key]
|
12
|
+
self.type_of_service = options[:type_of_service]
|
12
13
|
self.use_ssl = options[:use_ssl] or false
|
13
|
-
self.
|
14
|
-
self.base_url = "#{options[:type_of_service] or "blobs"}.#{options[:base_url] or "core.windows.net"}"
|
14
|
+
self.use_devenv = !!options[:use_devenv]
|
15
|
+
self.base_url = "#{options[:type_of_service] or "blobs"}.#{options[:base_url] or "core.windows.net"}" unless self.use_devenv
|
16
|
+
self.base_url ||= (options[:base_url] or "core.windows.net")
|
15
17
|
end
|
16
18
|
|
17
19
|
# Generates a request based on Adam Wiggings' rest-client, including all the required headers
|
@@ -32,7 +34,8 @@ module WAZ
|
|
32
34
|
def generate_request_uri(path = nil, options = {})
|
33
35
|
protocol = use_ssl ? "https" : "http"
|
34
36
|
query_params = options.keys.sort{ |a, b| a.to_s <=> b.to_s}.map{ |k| "#{k.to_s.gsub(/_/, '')}=#{CGI.escape(options[k].to_s)}"}.join("&") unless options.nil? or options.empty?
|
35
|
-
uri = "#{protocol}://#{
|
37
|
+
uri = "#{protocol}://#{base_url}/#{account_name}#{(path or "").start_with?("/") ? "" : "/"}#{(path or "")}" if !self.use_devenv.nil? and self.use_devenv
|
38
|
+
uri ||= "#{protocol}://#{account_name}.#{base_url}#{(path or "").start_with?("/") ? "" : "/"}#{(path or "")}"
|
36
39
|
uri << "?#{query_params}" if query_params
|
37
40
|
return uri
|
38
41
|
end
|
@@ -101,7 +104,7 @@ module WAZ
|
|
101
104
|
# Generates a Windows Azure Storage call, it internally calls url generation method
|
102
105
|
# and the request generation message.
|
103
106
|
def execute(verb, path, query = {}, headers = {}, payload = nil)
|
104
|
-
url = generate_request_uri(path, query)
|
107
|
+
url = generate_request_uri(path, query)
|
105
108
|
request = generate_request(verb, url, headers, payload)
|
106
109
|
request.execute()
|
107
110
|
end
|
@@ -11,6 +11,15 @@ module WAZ
|
|
11
11
|
def valid_name?(name)
|
12
12
|
name =~ /^[a-z0-9][a-z0-9\-]{1,}[^-]$/ && name.length < 64
|
13
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
|
14
23
|
end
|
15
24
|
end
|
16
25
|
end
|
data/lib/waz/storage/version.rb
CHANGED
@@ -0,0 +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
|
45
|
+
end
|
@@ -0,0 +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
|
45
|
+
end
|
@@ -0,0 +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='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')</id>" \
|
143
|
+
"<title /><updated>#{Time.now.utc.iso8601}</updated><author><name /></author><link rel=\"edit\" title=\"#{table_name}\" href=\"#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')\" />" \
|
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}\">#{value}</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>#{entity[:partition_key]}</d:PartitionKey>" \
|
152
|
+
"<d:RowKey>#{entity[:row_key]}</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
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Tables
|
3
|
+
# This class represents a Table on Windows Azure Tables API. These are the methods implemented from Microsoft's API description
|
4
|
+
# available on MSDN at http://msdn.microsoft.com/en-us/library/dd179423.aspx
|
5
|
+
#
|
6
|
+
# # list available tables
|
7
|
+
# tables = WAZ::Tables::Table.list
|
8
|
+
#
|
9
|
+
# # list more tables
|
10
|
+
# WAZ::Tables::Table.list(tables.continuation_token)
|
11
|
+
#
|
12
|
+
# # get a specific table
|
13
|
+
# my_table = WAZ::Tables::Table.find('my-table')
|
14
|
+
#
|
15
|
+
# # delete table
|
16
|
+
# my_table.destroy!
|
17
|
+
#
|
18
|
+
# # create a new table
|
19
|
+
# WAZ::Tables::Table.create('new-table')
|
20
|
+
#
|
21
|
+
class Table
|
22
|
+
class << self
|
23
|
+
INVALID_TABLE_ERROR_MESSAGE = "must start with at least one lower/upper characted, can have character or any digit starting from the second position, must be from 3 through 63 characters long"
|
24
|
+
|
25
|
+
# Finds a table by name. It will return nil if no table was found.
|
26
|
+
def find(table_name)
|
27
|
+
raise WAZ::Storage::InvalidParameterValue, {:name => table_name, :values => [INVALID_TABLE_ERROR_MESSAGE]} unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
28
|
+
begin
|
29
|
+
WAZ::Tables::Table.new(service_instance.get_table(table_name))
|
30
|
+
rescue WAZ::Tables::TableDoesNotExist
|
31
|
+
return nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns an array of the existing tables (WAZ::Tables::Table) on the current
|
36
|
+
# Windows Azure Storage account.
|
37
|
+
def list(continuation_token = {})
|
38
|
+
table_list, next_table_name = service_instance.list_tables(continuation_token['NextTableName'])
|
39
|
+
tables = TableArray.new(table_list.map { |table| WAZ::Tables::Table.new({ :name => table[:name], :url => table[:url] }) })
|
40
|
+
tables.continuation_token = {'NextTableName' => next_table_name} unless next_table_name.nil?
|
41
|
+
return tables
|
42
|
+
end
|
43
|
+
|
44
|
+
# Creates a table on the current account.
|
45
|
+
def create(table_name)
|
46
|
+
raise WAZ::Storage::InvalidParameterValue, {:name => table_name, :values => [INVALID_TABLE_ERROR_MESSAGE]} unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
47
|
+
WAZ::Tables::Table.new(service_instance.create_table(table_name))
|
48
|
+
end
|
49
|
+
|
50
|
+
# This method is internally used by this class. It's the way we keep a single instance of the
|
51
|
+
# service that wraps the calls the Windows Azure Tables API. It's initialized with the values
|
52
|
+
# from the default_connection on WAZ::Storage::Base initialized thru establish_connection!
|
53
|
+
def service_instance
|
54
|
+
options = WAZ::Storage::Base.default_connection.merge(:type_of_service => "table")
|
55
|
+
(@service_instances ||= {})[options[:account_name]] ||= Service.new(options)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_accessor :name, :url
|
60
|
+
|
61
|
+
def initialize(options = {})
|
62
|
+
raise WAZ::Storage::InvalidOption, :name unless options.keys.include?(:name) and !options[:name].empty?
|
63
|
+
raise WAZ::Storage::InvalidOption, :url unless options.keys.include?(:url) and !options[:url].empty?
|
64
|
+
raise WAZ::Storage::InvalidParameterValue, {:name => options[:name], :values => [INVALID_TABLE_ERROR_MESSAGE]} unless WAZ::Storage::ValidationRules.valid_table_name?(options[:name])
|
65
|
+
self.name = options[:name]
|
66
|
+
self.url = options[:url]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Removes the table from the current account.
|
70
|
+
def destroy!
|
71
|
+
self.class.service_instance.delete_table(self.name)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/rakefile
CHANGED
@@ -5,10 +5,9 @@ require 'rake/gempackagetask'
|
|
5
5
|
require 'rake/rdoctask'
|
6
6
|
require 'lib/waz-storage'
|
7
7
|
|
8
|
-
|
9
8
|
namespace :test do
|
10
9
|
Spec::Rake::SpecTask.new('run_with_rcov') do |t|
|
11
|
-
t.spec_files = FileList['tests/waz/queues/*.rb', 'tests/waz/blobs/*.rb', 'tests/waz/storage/*.rb']
|
10
|
+
t.spec_files = FileList['tests/waz/queues/*.rb', 'tests/waz/blobs/*.rb', 'tests/waz/tables/*.rb', 'tests/waz/storage/*.rb']
|
12
11
|
t.rcov = true
|
13
12
|
t.rcov_opts = ['--text-report', '--exclude', "exclude.*/.gem,test,Library,#{ENV['GEM_HOME']}", '--sort', 'coverage' ]
|
14
13
|
t.spec_opts = ['-cfn']
|
@@ -49,4 +48,4 @@ namespace :docs do
|
|
49
48
|
t.rdoc_files.include('README.rdoc')
|
50
49
|
t.rdoc_files.include('lib/**/*.rb')
|
51
50
|
end
|
52
|
-
end
|
51
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: waz-storage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Johnny G. Halife
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-02-
|
12
|
+
date: 2010-02-04 00:00:00 -03:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -55,9 +55,15 @@ files:
|
|
55
55
|
- lib/waz/storage/exceptions.rb
|
56
56
|
- lib/waz/storage/validation_rules.rb
|
57
57
|
- lib/waz/storage/version.rb
|
58
|
+
- lib/waz/tables/edm_type_helper.rb
|
59
|
+
- lib/waz/tables/exceptions.rb
|
60
|
+
- lib/waz/tables/service.rb
|
61
|
+
- lib/waz/tables/table.rb
|
62
|
+
- lib/waz/tables/table_array.rb
|
58
63
|
- lib/waz-blobs.rb
|
59
64
|
- lib/waz-queues.rb
|
60
65
|
- lib/waz-storage.rb
|
66
|
+
- lib/waz-tables.rb
|
61
67
|
has_rdoc: true
|
62
68
|
homepage: http://waz-storage.heroku.com
|
63
69
|
licenses: []
|