ashikawa-core 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Guardfile +18 -0
- data/README.md +10 -10
- data/Rakefile +5 -4
- data/ashikawa-core.gemspec +14 -6
- data/lib/ashikawa-core.rb +1 -0
- data/lib/ashikawa-core/collection.rb +189 -98
- data/lib/ashikawa-core/connection.rb +18 -20
- data/lib/ashikawa-core/cursor.rb +65 -0
- data/lib/ashikawa-core/database.rb +33 -46
- data/lib/ashikawa-core/document.rb +63 -22
- data/lib/ashikawa-core/exceptions/document_not_found.rb +8 -0
- data/lib/ashikawa-core/index.rb +47 -0
- data/lib/ashikawa-core/version.rb +1 -1
- data/spec/fixtures/cursor/26011191-2.json +19 -0
- data/spec/fixtures/cursor/26011191-3.json +13 -0
- data/spec/fixtures/cursor/26011191.json +19 -0
- data/spec/fixtures/cursor/query.json +18 -0
- data/spec/fixtures/documents/new-4590-333.json +5 -0
- data/spec/fixtures/indices/all.json +22 -0
- data/spec/fixtures/indices/hash-index.json +12 -0
- data/spec/fixtures/indices/new-hash-index.json +12 -0
- data/spec/fixtures/simple-queries/all.json +10 -4
- data/spec/fixtures/simple-queries/all_limit.json +9 -3
- data/spec/fixtures/simple-queries/all_skip.json +9 -3
- data/spec/fixtures/simple-queries/example.json +9 -3
- data/spec/fixtures/simple-queries/near.json +11 -5
- data/spec/fixtures/simple-queries/range.json +10 -0
- data/spec/fixtures/simple-queries/within.json +9 -3
- data/spec/integration/arango_helper.rb +27 -0
- data/spec/integration/basic_spec.rb +107 -28
- data/spec/integration/spec_helper.rb +0 -28
- data/spec/unit/collection_spec.rb +190 -83
- data/spec/unit/connection_spec.rb +17 -17
- data/spec/unit/cursor_spec.rb +75 -0
- data/spec/unit/database_spec.rb +34 -19
- data/spec/unit/document_spec.rb +77 -6
- data/spec/unit/index_spec.rb +39 -0
- metadata +98 -6
@@ -6,25 +6,25 @@ module Ashikawa
|
|
6
6
|
# Represents a Connection via HTTP to a certain host
|
7
7
|
class Connection
|
8
8
|
# The IP of the connection
|
9
|
-
#
|
9
|
+
#
|
10
10
|
# @return [String]
|
11
11
|
# @api public
|
12
12
|
# @example Get the IP of the connection
|
13
13
|
# connection = Connection.new "http://localhost:8529"
|
14
14
|
# connection.ip # => "http://localhost"
|
15
15
|
attr_reader :ip
|
16
|
-
|
16
|
+
|
17
17
|
# The port of the connection
|
18
|
-
#
|
18
|
+
#
|
19
19
|
# @return [Fixnum]
|
20
20
|
# @api public
|
21
21
|
# @example Get the port of the connection
|
22
22
|
# connection = Connection.new "http://localhost:8529"
|
23
23
|
# connection.port # => 8529
|
24
24
|
attr_reader :port
|
25
|
-
|
25
|
+
|
26
26
|
# Initialize a Connection with a given API String
|
27
|
-
#
|
27
|
+
#
|
28
28
|
# @param [String] api_string IP and Port as a String
|
29
29
|
# @api public
|
30
30
|
# @example Create a new Connection
|
@@ -34,10 +34,10 @@ module Ashikawa
|
|
34
34
|
@ip, @port = @api_string.scan(/(\S+):(\d+)/).first
|
35
35
|
@port = @port.to_i
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
# Sends a request to a given path (Prepends the api_string automatically)
|
39
|
-
#
|
40
|
-
# @example get request
|
39
|
+
#
|
40
|
+
# @example get request
|
41
41
|
# connection.send_request('/collection/new_collection')
|
42
42
|
# @example post request
|
43
43
|
# connection.send_request('/collection/new_collection', :post => { :name => 'new_collection' })
|
@@ -47,22 +47,20 @@ module Ashikawa
|
|
47
47
|
# @return [Hash] parsed JSON response from the server
|
48
48
|
# @api semipublic
|
49
49
|
def send_request(path, method_params = {})
|
50
|
-
path.gsub
|
51
|
-
|
52
|
-
if method_params.has_key? :post
|
53
|
-
|
50
|
+
path = "#{@api_string}/_api/#{path.gsub(/^\//, '')}"
|
51
|
+
|
52
|
+
answer = if method_params.has_key? :post
|
53
|
+
RestClient.post path, method_params[:post].to_json
|
54
54
|
elsif method_params.has_key? :put
|
55
|
-
|
55
|
+
RestClient.put path, method_params[:put].to_json
|
56
56
|
elsif method_params.has_key? :delete
|
57
|
-
|
58
|
-
JSON.parse RestClient.delete("#{@api_string}/_api/#{path}" )
|
59
|
-
else
|
60
|
-
JSON.parse RestClient.delete("#{@api_string}/_api/#{path}", method_params[:delete].to_json )
|
61
|
-
end
|
57
|
+
RestClient.delete path
|
62
58
|
else
|
63
|
-
|
59
|
+
RestClient.get path
|
64
60
|
end
|
61
|
+
|
62
|
+
JSON.parse answer
|
65
63
|
end
|
66
64
|
end
|
67
65
|
end
|
68
|
-
end
|
66
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'ashikawa-core/document'
|
2
|
+
|
3
|
+
module Ashikawa
|
4
|
+
module Core
|
5
|
+
# Represents a Cursor on a certain Database.
|
6
|
+
# It is an enumerable.
|
7
|
+
class Cursor
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
# The ID of the cursor
|
11
|
+
# @return [Int]
|
12
|
+
# @api public
|
13
|
+
attr_reader :id
|
14
|
+
|
15
|
+
# The number of documents
|
16
|
+
# @return [Int]
|
17
|
+
# @api public
|
18
|
+
attr_reader :length
|
19
|
+
|
20
|
+
# Initialize a Cursor with the database and raw data
|
21
|
+
#
|
22
|
+
# @param [Database] database
|
23
|
+
# @param [Hash] raw_cursor
|
24
|
+
# @api public
|
25
|
+
def initialize(database, raw_cursor)
|
26
|
+
@database = database
|
27
|
+
@id = raw_cursor['id'].to_i if raw_cursor.has_key? 'id'
|
28
|
+
@has_more = raw_cursor['hasMore']
|
29
|
+
@length = raw_cursor['count'].to_i if raw_cursor.has_key? 'count'
|
30
|
+
@current = raw_cursor['result']
|
31
|
+
end
|
32
|
+
|
33
|
+
# Iterate over the documents found by the cursor
|
34
|
+
#
|
35
|
+
# @yield [document]
|
36
|
+
# @api public
|
37
|
+
def each(&block)
|
38
|
+
begin
|
39
|
+
@current.each do |raw_document|
|
40
|
+
block.call Document.new(@database, raw_document)
|
41
|
+
end
|
42
|
+
end while next_batch
|
43
|
+
end
|
44
|
+
|
45
|
+
# Delete the cursor
|
46
|
+
def delete
|
47
|
+
@database.send_request "/cursor/#{@id}", delete: {}
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Get a new batch from the server
|
53
|
+
#
|
54
|
+
# @return [Boolean] Is there a next batch?
|
55
|
+
def next_batch
|
56
|
+
return @false unless @has_more
|
57
|
+
raw_cursor = @database.send_request "/cursor/#{@id}", put: {}
|
58
|
+
@id = raw_cursor['id']
|
59
|
+
@has_more = raw_cursor['hasMore']
|
60
|
+
@length = raw_cursor['count']
|
61
|
+
@current = raw_cursor['result']
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -1,12 +1,21 @@
|
|
1
1
|
require "ashikawa-core/collection"
|
2
2
|
require "ashikawa-core/connection"
|
3
|
+
require "ashikawa-core/cursor"
|
4
|
+
require "forwardable"
|
3
5
|
|
4
6
|
module Ashikawa
|
5
7
|
module Core
|
6
8
|
# Represents an ArangoDB database in Ruby
|
7
9
|
class Database
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
# Delegate sending requests and managing IP and port to the connection
|
13
|
+
delegate send_request: :@connection
|
14
|
+
delegate ip: :@connection
|
15
|
+
delegate port: :@connection
|
16
|
+
|
8
17
|
# Initializes the connection to the database
|
9
|
-
#
|
18
|
+
#
|
10
19
|
# @param [Connection, String] connection A Connection object or a String to create a Connection object.
|
11
20
|
# @api public
|
12
21
|
# @example Access a Database by providing the URL
|
@@ -21,31 +30,9 @@ module Ashikawa
|
|
21
30
|
@connection = connection
|
22
31
|
end
|
23
32
|
end
|
24
|
-
|
25
|
-
# The IP of the database
|
26
|
-
#
|
27
|
-
# @return [String]
|
28
|
-
# @api public
|
29
|
-
# @example Get the IP of the connection
|
30
|
-
# database = Ashikawa::Core::Database.new "http://localhost:8529"
|
31
|
-
# database.ip # => http://localhost
|
32
|
-
def ip
|
33
|
-
@connection.ip
|
34
|
-
end
|
35
|
-
|
36
|
-
# The Port of the database
|
37
|
-
#
|
38
|
-
# @return [Fixnum]
|
39
|
-
# @api public
|
40
|
-
# @example Get the port for the connection
|
41
|
-
# database = Ashikawa::Core::Database.new "http://localhost:8529"
|
42
|
-
# database.port # => 8529
|
43
|
-
def port
|
44
|
-
@connection.port
|
45
|
-
end
|
46
|
-
|
33
|
+
|
47
34
|
# Returns a list of all collections defined in the database
|
48
|
-
#
|
35
|
+
#
|
49
36
|
# @return [Array<Collection>]
|
50
37
|
# @api public
|
51
38
|
# @example Get an Array containing the Collections in the database
|
@@ -54,12 +41,12 @@ module Ashikawa
|
|
54
41
|
# database["b"]
|
55
42
|
# database.collections # => [ #<Collection name="a">, #<Collection name="b">]
|
56
43
|
def collections
|
57
|
-
server_response =
|
44
|
+
server_response = send_request "/collection"
|
58
45
|
server_response["collections"].map { |collection| Ashikawa::Core::Collection.new self, collection }
|
59
46
|
end
|
60
|
-
|
47
|
+
|
61
48
|
# Get or create a Collection based on name or ID
|
62
|
-
#
|
49
|
+
#
|
63
50
|
# @param [String, Fixnum] collection_identifier The name or ID of the collection
|
64
51
|
# @return [Collection]
|
65
52
|
# @api public
|
@@ -71,28 +58,28 @@ module Ashikawa
|
|
71
58
|
# database["7254820"] # => #<Collection id=7254820>
|
72
59
|
def [](collection_identifier)
|
73
60
|
begin
|
74
|
-
server_response =
|
61
|
+
server_response = send_request "/collection/#{collection_identifier}"
|
75
62
|
rescue RestClient::ResourceNotFound
|
76
|
-
server_response =
|
63
|
+
server_response = send_request "/collection", post: { name: collection_identifier }
|
77
64
|
end
|
78
|
-
|
65
|
+
|
79
66
|
Ashikawa::Core::Collection.new self, server_response
|
80
67
|
end
|
81
|
-
|
82
|
-
#
|
83
|
-
#
|
84
|
-
# @
|
85
|
-
#
|
86
|
-
# @
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
68
|
+
|
69
|
+
# Send a query to the database
|
70
|
+
#
|
71
|
+
# @param [String] query
|
72
|
+
# @option opts [Integer] :count Should the number of results be counted?
|
73
|
+
# @option opts [Integer] :batch_size Set the number of results returned at once
|
74
|
+
def query(query, opts = {})
|
75
|
+
parameter = { query: query }
|
76
|
+
|
77
|
+
parameter[:count] = opts[:count] if opts.has_key? :count
|
78
|
+
parameter[:batchSize] = opts[:batch_size] if opts.has_key? :batch_size
|
79
|
+
|
80
|
+
server_response = send_request "/cursor", post: parameter
|
81
|
+
Cursor.new self, server_response
|
95
82
|
end
|
96
83
|
end
|
97
84
|
end
|
98
|
-
end
|
85
|
+
end
|
@@ -1,37 +1,78 @@
|
|
1
|
+
require 'ashikawa-core/exceptions/document_not_found'
|
2
|
+
|
1
3
|
module Ashikawa
|
2
4
|
module Core
|
3
5
|
# Represents a certain Document within a certain Collection
|
4
6
|
class Document
|
5
|
-
# The ID of the document
|
6
|
-
#
|
7
|
-
# @return [
|
7
|
+
# The ID of the document without the Collection prefix
|
8
|
+
#
|
9
|
+
# @return [Int]
|
8
10
|
# @api public
|
9
11
|
# @example Get the ID for a Document
|
10
|
-
# document = Ashikawa::Core::Document.new
|
11
|
-
# document.id # =>
|
12
|
+
# document = Ashikawa::Core::Document.new database, raw_document
|
13
|
+
# document.id # => 2345678
|
12
14
|
attr_reader :id
|
13
|
-
|
15
|
+
|
14
16
|
# The current revision of the document
|
15
|
-
#
|
16
|
-
# @return [
|
17
|
+
#
|
18
|
+
# @return [Int]
|
17
19
|
# @api public
|
18
20
|
# @example Get the Revision for a Document
|
19
|
-
# document = Ashikawa::Core::Document.new
|
20
|
-
# document.revision # =>
|
21
|
+
# document = Ashikawa::Core::Document.new database, raw_document
|
22
|
+
# document.revision # => 3456789
|
21
23
|
attr_reader :revision
|
22
|
-
|
23
|
-
# Initialize a Document with
|
24
|
-
#
|
25
|
-
# @param [
|
26
|
-
# @param [
|
24
|
+
|
25
|
+
# Initialize a Document with the database and raw data
|
26
|
+
#
|
27
|
+
# @param [Database] database
|
28
|
+
# @param [Hash] raw_document
|
27
29
|
# @api public
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
@
|
30
|
+
def initialize(database, raw_document)
|
31
|
+
@database = database
|
32
|
+
@is_persistent = raw_document.has_key?('_id') and raw_document.has_key?('rev')
|
33
|
+
|
34
|
+
if @is_persistent
|
35
|
+
@collection_id, @id = raw_document['_id'].split('/').map { |id| id.to_i }
|
36
|
+
@revision = raw_document['_rev'].to_i
|
37
|
+
end
|
38
|
+
|
39
|
+
@content = raw_document.delete_if { |key, value| key[0] == "_" }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get the value of an attribute of the document
|
43
|
+
#
|
44
|
+
# @param [String] attribute_name
|
45
|
+
# @api public
|
46
|
+
# @return [Object] The value of the attribute
|
47
|
+
def [](attribute_name)
|
48
|
+
@content[attribute_name]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Remove the document from the database
|
52
|
+
#
|
53
|
+
# @api public
|
54
|
+
def delete
|
55
|
+
raise DocumentNotFoundException unless @is_persistent
|
56
|
+
@database.send_request "document/#{@collection_id}/#{@id}", delete: {}
|
57
|
+
end
|
58
|
+
|
59
|
+
# Update the value of an attribute (Does not write to database)
|
60
|
+
#
|
61
|
+
# @param [String] attribute_name
|
62
|
+
# @param [Object] value
|
63
|
+
# @api public
|
64
|
+
def []=(attribute_name, value)
|
65
|
+
raise DocumentNotFoundException unless @is_persistent
|
66
|
+
@content[attribute_name] = value
|
67
|
+
end
|
68
|
+
|
69
|
+
# Save the changes to the database
|
70
|
+
#
|
71
|
+
# @api public
|
72
|
+
def save()
|
73
|
+
raise DocumentNotFoundException unless @is_persistent
|
74
|
+
@database.send_request "document/#{@collection_id}/#{@id}", put: @content
|
33
75
|
end
|
34
|
-
|
35
76
|
end
|
36
77
|
end
|
37
|
-
end
|
78
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Ashikawa
|
2
|
+
module Core
|
3
|
+
# Represents an index on a certain collection
|
4
|
+
class Index
|
5
|
+
# The fields the index is defined on as symbols
|
6
|
+
# @return [Array<Symbol>]
|
7
|
+
# @api public
|
8
|
+
attr_reader :on
|
9
|
+
|
10
|
+
# The type of index as a symbol
|
11
|
+
# @return [Symbol]
|
12
|
+
# @api public
|
13
|
+
attr_reader :type
|
14
|
+
|
15
|
+
# Is the unique constraint set?
|
16
|
+
# @return [Boolean]
|
17
|
+
# @api public
|
18
|
+
attr_reader :unique
|
19
|
+
|
20
|
+
# The id of the index
|
21
|
+
# @return [Int]
|
22
|
+
# @api public
|
23
|
+
attr_reader :id
|
24
|
+
|
25
|
+
# Create a new Index
|
26
|
+
#
|
27
|
+
# @param [Collection] collection The collection the index is defined on
|
28
|
+
# @param [Hash] raw_data The JSON representation of the index
|
29
|
+
# @return [Index]
|
30
|
+
# @api
|
31
|
+
def initialize(collection, raw_data)
|
32
|
+
@collection = collection
|
33
|
+
@id = raw_data["id"].split("/")[1].to_i if raw_data["id"]
|
34
|
+
@on = raw_data["fields"].map { |field| field.to_sym } if raw_data.has_key? "fields"
|
35
|
+
@type = raw_data["type"].to_sym if raw_data.has_key? "type"
|
36
|
+
@unique = raw_data["unique"] if raw_data.has_key? "unique"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Remove the index from the collection
|
40
|
+
#
|
41
|
+
# @api public
|
42
|
+
def delete
|
43
|
+
@collection.send_request("index/#{@collection.id}/#{@id}", delete: {})
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
{
|
2
|
+
"hasMore": true,
|
3
|
+
"error": false,
|
4
|
+
"id": 26011191,
|
5
|
+
"result": [
|
6
|
+
{
|
7
|
+
"n": 2,
|
8
|
+
"_rev": 25880119,
|
9
|
+
"_id": "23914039/25880119"
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"n": 3,
|
13
|
+
"_rev": 25880119,
|
14
|
+
"_id": "23914039/25880119"
|
15
|
+
}
|
16
|
+
],
|
17
|
+
"code": 200,
|
18
|
+
"count": 5
|
19
|
+
}
|