waz-storage 0.5.81 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|