ashikawa-core 0.1 → 0.2
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/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
|
+
}
|