ashikawa-core 0.1
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/.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
|