ashikawa-core 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/.rvmrc +1 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/README.md +63 -0
- data/Rakefile +66 -0
- data/ashikawa-core.gemspec +40 -0
- data/lib/ashikawa-core.rb +12 -0
- data/lib/ashikawa-core/collection.rb +524 -0
- data/lib/ashikawa-core/connection.rb +68 -0
- data/lib/ashikawa-core/database.rb +98 -0
- data/lib/ashikawa-core/document.rb +37 -0
- data/lib/ashikawa-core/version.rb +6 -0
- data/spec/fixtures/collections/4588.json +8 -0
- data/spec/fixtures/collections/4590-properties.json +9 -0
- data/spec/fixtures/collections/4590.json +8 -0
- data/spec/fixtures/collections/73482-figures.json +23 -0
- data/spec/fixtures/collections/all.json +32 -0
- data/spec/fixtures/collections/not_found.json +6 -0
- data/spec/fixtures/documents/4590-333.json +5 -0
- data/spec/fixtures/simple-queries/all.json +4 -0
- data/spec/fixtures/simple-queries/all_limit.json +3 -0
- data/spec/fixtures/simple-queries/all_skip.json +3 -0
- data/spec/fixtures/simple-queries/example.json +3 -0
- data/spec/fixtures/simple-queries/near.json +5 -0
- data/spec/fixtures/simple-queries/within.json +3 -0
- data/spec/integration/basic_spec.rb +167 -0
- data/spec/integration/spec_helper.rb +34 -0
- data/spec/unit/collection_spec.rb +228 -0
- data/spec/unit/connection_spec.rb +55 -0
- data/spec/unit/database_spec.rb +73 -0
- data/spec/unit/document_spec.rb +13 -0
- data/spec/unit/spec_helper.rb +11 -0
- metadata +199 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
require "rest-client"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module Ashikawa
|
5
|
+
module Core
|
6
|
+
# Represents a Connection via HTTP to a certain host
|
7
|
+
class Connection
|
8
|
+
# The IP of the connection
|
9
|
+
#
|
10
|
+
# @return [String]
|
11
|
+
# @api public
|
12
|
+
# @example Get the IP of the connection
|
13
|
+
# connection = Connection.new "http://localhost:8529"
|
14
|
+
# connection.ip # => "http://localhost"
|
15
|
+
attr_reader :ip
|
16
|
+
|
17
|
+
# The port of the connection
|
18
|
+
#
|
19
|
+
# @return [Fixnum]
|
20
|
+
# @api public
|
21
|
+
# @example Get the port of the connection
|
22
|
+
# connection = Connection.new "http://localhost:8529"
|
23
|
+
# connection.port # => 8529
|
24
|
+
attr_reader :port
|
25
|
+
|
26
|
+
# Initialize a Connection with a given API String
|
27
|
+
#
|
28
|
+
# @param [String] api_string IP and Port as a String
|
29
|
+
# @api public
|
30
|
+
# @example Create a new Connection
|
31
|
+
# connection = Connection.new "http://localhost:8529"
|
32
|
+
def initialize(api_string)
|
33
|
+
@api_string = api_string
|
34
|
+
@ip, @port = @api_string.scan(/(\S+):(\d+)/).first
|
35
|
+
@port = @port.to_i
|
36
|
+
end
|
37
|
+
|
38
|
+
# Sends a request to a given path (Prepends the api_string automatically)
|
39
|
+
#
|
40
|
+
# @example get request
|
41
|
+
# connection.send_request('/collection/new_collection')
|
42
|
+
# @example post request
|
43
|
+
# connection.send_request('/collection/new_collection', :post => { :name => 'new_collection' })
|
44
|
+
# @param [String] path the path you wish to send a request to.
|
45
|
+
# @param [Hash] method_params additional parameters for your request. Only needed if you want to send something other than a GET request.
|
46
|
+
# @option method_params [Hash] :post POST data in case you want to send a POST request.
|
47
|
+
# @return [Hash] parsed JSON response from the server
|
48
|
+
# @api semipublic
|
49
|
+
def send_request(path, method_params = {})
|
50
|
+
path.gsub! /^\//, ''
|
51
|
+
|
52
|
+
if method_params.has_key? :post
|
53
|
+
JSON.parse RestClient.post("#{@api_string}/_api/#{path}", method_params[:post].to_json )
|
54
|
+
elsif method_params.has_key? :put
|
55
|
+
JSON.parse RestClient.put("#{@api_string}/_api/#{path}", method_params[:put].to_json )
|
56
|
+
elsif method_params.has_key? :delete
|
57
|
+
if method_params[:delete] = {}
|
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
|
62
|
+
else
|
63
|
+
JSON.parse RestClient.get("#{@api_string}/_api/#{path}")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "ashikawa-core/collection"
|
2
|
+
require "ashikawa-core/connection"
|
3
|
+
|
4
|
+
module Ashikawa
|
5
|
+
module Core
|
6
|
+
# Represents an ArangoDB database in Ruby
|
7
|
+
class Database
|
8
|
+
# Initializes the connection to the database
|
9
|
+
#
|
10
|
+
# @param [Connection, String] connection A Connection object or a String to create a Connection object.
|
11
|
+
# @api public
|
12
|
+
# @example Access a Database by providing the URL
|
13
|
+
# database = Ashikawa::Core::Database.new "http://localhost:8529"
|
14
|
+
# @example Access a Database by providing a Connection
|
15
|
+
# connection = Connection.new "http://localhost:8529"
|
16
|
+
# database = Ashikawa::Core::Database.new connection
|
17
|
+
def initialize(connection)
|
18
|
+
if connection.class == String
|
19
|
+
@connection = Ashikawa::Core::Connection.new connection
|
20
|
+
else
|
21
|
+
@connection = connection
|
22
|
+
end
|
23
|
+
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
|
+
|
47
|
+
# Returns a list of all collections defined in the database
|
48
|
+
#
|
49
|
+
# @return [Array<Collection>]
|
50
|
+
# @api public
|
51
|
+
# @example Get an Array containing the Collections in the database
|
52
|
+
# database = Ashikawa::Core::Database.new "http://localhost:8529"
|
53
|
+
# database["a"]
|
54
|
+
# database["b"]
|
55
|
+
# database.collections # => [ #<Collection name="a">, #<Collection name="b">]
|
56
|
+
def collections
|
57
|
+
server_response = @connection.send_request "/collection"
|
58
|
+
server_response["collections"].map { |collection| Ashikawa::Core::Collection.new self, collection }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get or create a Collection based on name or ID
|
62
|
+
#
|
63
|
+
# @param [String, Fixnum] collection_identifier The name or ID of the collection
|
64
|
+
# @return [Collection]
|
65
|
+
# @api public
|
66
|
+
# @example Get a Collection from the database by name
|
67
|
+
# database = Ashikawa::Core::Database.new "http://localhost:8529"
|
68
|
+
# database["a"] # => #<Collection name="a">
|
69
|
+
# @example Get a Collection from the database by ID
|
70
|
+
# database = Ashikawa::Core::Database.new "http://localhost:8529"
|
71
|
+
# database["7254820"] # => #<Collection id=7254820>
|
72
|
+
def [](collection_identifier)
|
73
|
+
begin
|
74
|
+
server_response = @connection.send_request "/collection/#{collection_identifier}"
|
75
|
+
rescue RestClient::ResourceNotFound
|
76
|
+
server_response = @connection.send_request "/collection", post: { name: collection_identifier }
|
77
|
+
end
|
78
|
+
|
79
|
+
Ashikawa::Core::Collection.new self, server_response
|
80
|
+
end
|
81
|
+
|
82
|
+
# Sends a request to a given path (Prepends the api_string automatically)
|
83
|
+
#
|
84
|
+
# @example Send a get request to the database
|
85
|
+
# connection.send_request('/collection/new_collection')
|
86
|
+
# @example Send a post request to the database
|
87
|
+
# connection.send_request('/collection/new_collection', :post => { :name => 'new_collection' })
|
88
|
+
# @param [String] path the path you wish to send a request to.
|
89
|
+
# @param [Hash] method_params additional parameters for your request. Only needed if you want to send something other than a GET request.
|
90
|
+
# @option method_params [Hash] :post POST data in case you want to send a POST request.
|
91
|
+
# @return [Hash] parsed JSON response from the server
|
92
|
+
# @api semipublic
|
93
|
+
def send_request(path, method_params = {})
|
94
|
+
@connection.send_request path, method_params
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Ashikawa
|
2
|
+
module Core
|
3
|
+
# Represents a certain Document within a certain Collection
|
4
|
+
class Document
|
5
|
+
# The ID of the document including the Collection prefix
|
6
|
+
#
|
7
|
+
# @return [String]
|
8
|
+
# @api public
|
9
|
+
# @example Get the ID for a Document
|
10
|
+
# document = Ashikawa::Core::Document.new "1234567/2345678", "3456789"
|
11
|
+
# document.id # => "1234567/2345678"
|
12
|
+
attr_reader :id
|
13
|
+
|
14
|
+
# The current revision of the document
|
15
|
+
#
|
16
|
+
# @return [String]
|
17
|
+
# @api public
|
18
|
+
# @example Get the Revision for a Document
|
19
|
+
# document = Ashikawa::Core::Document.new "1234567/2345678", "3456789"
|
20
|
+
# document.revision # => "3456789"
|
21
|
+
attr_reader :revision
|
22
|
+
|
23
|
+
# Initialize a Document with an ID and revision
|
24
|
+
#
|
25
|
+
# @param [String] id The complete ID including the Collection prefix
|
26
|
+
# @param [String] revision
|
27
|
+
# @api public
|
28
|
+
# @example The Document 2345678 in the Collection 1234567 in revision 3456789
|
29
|
+
# document = Ashikawa::Core::Document.new "1234567/2345678", "3456789"
|
30
|
+
def initialize(id, revision)
|
31
|
+
@id = id
|
32
|
+
@revision = revision
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
{
|
2
|
+
"name": "UnitTestsCollectionBasics",
|
3
|
+
"code": 200,
|
4
|
+
"figures": {
|
5
|
+
"datafiles": {
|
6
|
+
"count": 1
|
7
|
+
},
|
8
|
+
"alive": {
|
9
|
+
"size": 0,
|
10
|
+
"count": 0
|
11
|
+
},
|
12
|
+
"dead": {
|
13
|
+
"size": 2384,
|
14
|
+
"count": 149
|
15
|
+
}
|
16
|
+
},
|
17
|
+
"waitForSync": true,
|
18
|
+
"id": 73482,
|
19
|
+
"journalSize": 134217728,
|
20
|
+
"count": 0,
|
21
|
+
"status": 3,
|
22
|
+
"error": false
|
23
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
{
|
2
|
+
"collections": [
|
3
|
+
{
|
4
|
+
"name": "example_1",
|
5
|
+
"waitForSync": true,
|
6
|
+
"id": 4588,
|
7
|
+
"status": 3
|
8
|
+
},
|
9
|
+
{
|
10
|
+
"name": "example_2",
|
11
|
+
"waitForSync": true,
|
12
|
+
"id": 4589,
|
13
|
+
"status": 3
|
14
|
+
}
|
15
|
+
],
|
16
|
+
"code": 200,
|
17
|
+
"error": false,
|
18
|
+
"names":{
|
19
|
+
"example_1": {
|
20
|
+
"name": "example_1",
|
21
|
+
"waitForSync": true,
|
22
|
+
"id": 4588,
|
23
|
+
"status": 3
|
24
|
+
},
|
25
|
+
"example_2": {
|
26
|
+
"name": "example_2",
|
27
|
+
"waitForSync": true,
|
28
|
+
"id": 4589,
|
29
|
+
"status": 3
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'integration/spec_helper'
|
2
|
+
|
3
|
+
describe "Basics" do
|
4
|
+
subject { "http://localhost:8529" }
|
5
|
+
|
6
|
+
it "should have booted up an ArangoDB instance" do
|
7
|
+
expect { JSON.parse RestClient.get(subject) }.to raise_error(RestClient::NotImplemented)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "initialized database" do
|
11
|
+
subject { Ashikawa::Core::Database.new "http://localhost:8529" }
|
12
|
+
|
13
|
+
it "should do what the README describes" do
|
14
|
+
subject["my_collection"]
|
15
|
+
subject["my_collection"].name = "new_name"
|
16
|
+
subject["new_name"].delete
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should create and delete collections" do
|
20
|
+
subject.collections.should ==[]
|
21
|
+
subject["collection_1"]
|
22
|
+
subject["collection_2"]
|
23
|
+
subject["collection_3"]
|
24
|
+
subject.collections.length.should == 3
|
25
|
+
subject["collection_3"].delete
|
26
|
+
subject.collections.length.should == 2
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should be possible to change the name of a collection" do
|
30
|
+
my_collection = subject["test_collection"]
|
31
|
+
my_collection.name.should == "test_collection"
|
32
|
+
my_collection.name = "my_new_name"
|
33
|
+
my_collection.name.should == "my_new_name"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should be possible to find a collection by ID" do
|
37
|
+
my_collection = subject["test_collection"]
|
38
|
+
subject[my_collection.id].name.should == "test_collection"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should be possible to load and unload collections" do
|
42
|
+
my_collection = subject["test_collection"]
|
43
|
+
my_collection.loaded?.should be_true
|
44
|
+
my_collection.unload
|
45
|
+
my_id = my_collection.id
|
46
|
+
my_collection = subject[my_id]
|
47
|
+
subject[my_id].loaded?.should be_false
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should be possible to get figures" do
|
51
|
+
my_collection = subject["test_collection"]
|
52
|
+
my_collection.figure(:datafiles_count).class.should == Fixnum
|
53
|
+
my_collection.figure(:alive_size).class.should == Fixnum
|
54
|
+
my_collection.figure(:alive_count).class.should == Fixnum
|
55
|
+
my_collection.figure(:dead_size).class.should == Fixnum
|
56
|
+
my_collection.figure(:dead_count).class.should == Fixnum
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should change and receive information about waiting for sync" do
|
60
|
+
my_collection = subject["my_collection"]
|
61
|
+
my_collection.wait_for_sync?.should be_false
|
62
|
+
my_collection.wait_for_sync = true
|
63
|
+
my_collection.wait_for_sync?.should be_true
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should be possible to get information about the number of documents" do
|
67
|
+
pending("`<<` not implemented yet")
|
68
|
+
empty_collection = subject["empty_collection"]
|
69
|
+
empty_collection.length.should == 0
|
70
|
+
empty_collection << { name: "testname", age: 27}
|
71
|
+
empty_collection << { name: "anderer name", age: 28}
|
72
|
+
empty_collection.length.should == 2
|
73
|
+
empty_collection.truncate!
|
74
|
+
empty_collection.length.should == 0
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should return all documents of a collection" do
|
78
|
+
pending("`<<` not implemented yet")
|
79
|
+
empty_collection = subject["empty_collection"]
|
80
|
+
empty_collection << { name: "testname", age: 27}
|
81
|
+
empty_collection.all.first["name"].should == "testname"
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should be possible to limit and skip results" do
|
85
|
+
pending("`<<` not implemented yet")
|
86
|
+
empty_collection = subject["empty_collection"]
|
87
|
+
empty_collection.truncate!
|
88
|
+
|
89
|
+
empty_collection << { name: "test1"}
|
90
|
+
empty_collection << { name: "test2"}
|
91
|
+
empty_collection << { name: "test3"}
|
92
|
+
|
93
|
+
empty_collection.all(limit: 2).length.should == 2
|
94
|
+
empty_collection.all(skip: 2).length.should == 1
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
it "should be possible to access and create documents from a collection" do
|
99
|
+
collection = subject["documenttests"]
|
100
|
+
|
101
|
+
pending("`create` not implemented yet")
|
102
|
+
document = collection.create name: "The Dude", bowling: true
|
103
|
+
document_id = document.id
|
104
|
+
collection[document_id]["name"].should == "The Dude"
|
105
|
+
|
106
|
+
collection[document_id] = { name: "Other Dude", bowling: true }
|
107
|
+
collection[document_id]["name"].should == "Other Dude"
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "geolocation" do
|
111
|
+
before :each do
|
112
|
+
@places = subject['geo_collection']
|
113
|
+
@places.truncate!
|
114
|
+
|
115
|
+
pending("`<<` not implemented yet")
|
116
|
+
@places << { name: "cologne", latitude: 50.948045, longitude: 6.961212 }
|
117
|
+
@places << { name: "san francisco", latitude: -122.395899, longitude: 37.793621 }
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should be possible to query documents near a certain location" do
|
121
|
+
near_places = @places.near latitude: -122.395898, longitude: 37.793622
|
122
|
+
near_places.length.should == 1
|
123
|
+
near_places.first.name.should == "san francisco"
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should be possible to query documents within a certain range" do
|
127
|
+
near_places = @places.within latitude: 50.948040, longitude: 6.961210, radius: 2
|
128
|
+
near_places.length.should == 1
|
129
|
+
near_places.first.name.should == "cologne"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "created document" do
|
134
|
+
before :each do
|
135
|
+
pending("`create` not implemented yet")
|
136
|
+
@collection = subject["documenttests"]
|
137
|
+
@document = @collection.create name: "The Dude"
|
138
|
+
@document_id = @document.id
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should be possible to manipulate documents and save them" do
|
142
|
+
@document = @collection[document_id]
|
143
|
+
@document["name"] = "Jeffrey Lebowski"
|
144
|
+
@document["name"].should == "Jeffrey Lebowski"
|
145
|
+
@collection[@document_id].should == "The Dude"
|
146
|
+
@document.save
|
147
|
+
@collection[@document_id]["name"].should == "Jeffrey Lebowski"
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should be possible to delete a document" do
|
151
|
+
@collection = subject["documenttests"]
|
152
|
+
@document = @collection.create name: "The Dude"
|
153
|
+
@document_id = @document.id
|
154
|
+
@collection[@document_id].delete
|
155
|
+
expect { @collection[@document_id] }.to raise_exception Ashikawa::DocumentNotFoundException
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should be possible to query documents by example" do
|
160
|
+
collection = subject["documenttests"]
|
161
|
+
|
162
|
+
pending("`<<` not implemented yet")
|
163
|
+
collection << { name: "Random Collection" }
|
164
|
+
collection.by_example("name" => "Random Collection").length.should == 1
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|