ashikawa-core 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/Guardfile +18 -0
  2. data/README.md +10 -10
  3. data/Rakefile +5 -4
  4. data/ashikawa-core.gemspec +14 -6
  5. data/lib/ashikawa-core.rb +1 -0
  6. data/lib/ashikawa-core/collection.rb +189 -98
  7. data/lib/ashikawa-core/connection.rb +18 -20
  8. data/lib/ashikawa-core/cursor.rb +65 -0
  9. data/lib/ashikawa-core/database.rb +33 -46
  10. data/lib/ashikawa-core/document.rb +63 -22
  11. data/lib/ashikawa-core/exceptions/document_not_found.rb +8 -0
  12. data/lib/ashikawa-core/index.rb +47 -0
  13. data/lib/ashikawa-core/version.rb +1 -1
  14. data/spec/fixtures/cursor/26011191-2.json +19 -0
  15. data/spec/fixtures/cursor/26011191-3.json +13 -0
  16. data/spec/fixtures/cursor/26011191.json +19 -0
  17. data/spec/fixtures/cursor/query.json +18 -0
  18. data/spec/fixtures/documents/new-4590-333.json +5 -0
  19. data/spec/fixtures/indices/all.json +22 -0
  20. data/spec/fixtures/indices/hash-index.json +12 -0
  21. data/spec/fixtures/indices/new-hash-index.json +12 -0
  22. data/spec/fixtures/simple-queries/all.json +10 -4
  23. data/spec/fixtures/simple-queries/all_limit.json +9 -3
  24. data/spec/fixtures/simple-queries/all_skip.json +9 -3
  25. data/spec/fixtures/simple-queries/example.json +9 -3
  26. data/spec/fixtures/simple-queries/near.json +11 -5
  27. data/spec/fixtures/simple-queries/range.json +10 -0
  28. data/spec/fixtures/simple-queries/within.json +9 -3
  29. data/spec/integration/arango_helper.rb +27 -0
  30. data/spec/integration/basic_spec.rb +107 -28
  31. data/spec/integration/spec_helper.rb +0 -28
  32. data/spec/unit/collection_spec.rb +190 -83
  33. data/spec/unit/connection_spec.rb +17 -17
  34. data/spec/unit/cursor_spec.rb +75 -0
  35. data/spec/unit/database_spec.rb +34 -19
  36. data/spec/unit/document_spec.rb +77 -6
  37. data/spec/unit/index_spec.rb +39 -0
  38. 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
- JSON.parse RestClient.post("#{@api_string}/_api/#{path}", method_params[:post].to_json )
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
- JSON.parse RestClient.put("#{@api_string}/_api/#{path}", method_params[:put].to_json )
55
+ RestClient.put path, method_params[:put].to_json
56
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
57
+ RestClient.delete path
62
58
  else
63
- JSON.parse RestClient.get("#{@api_string}/_api/#{path}")
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 = @connection.send_request "/collection"
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 = @connection.send_request "/collection/#{collection_identifier}"
61
+ server_response = send_request "/collection/#{collection_identifier}"
75
62
  rescue RestClient::ResourceNotFound
76
- server_response = @connection.send_request "/collection", post: { name: collection_identifier }
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
- # 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
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 including the Collection prefix
6
- #
7
- # @return [String]
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 "1234567/2345678", "3456789"
11
- # document.id # => "1234567/2345678"
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 [String]
17
+ #
18
+ # @return [Int]
17
19
  # @api public
18
20
  # @example Get the Revision for a Document
19
- # document = Ashikawa::Core::Document.new "1234567/2345678", "3456789"
20
- # document.revision # => "3456789"
21
+ # document = Ashikawa::Core::Document.new database, raw_document
22
+ # document.revision # => 3456789
21
23
  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
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
- # @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
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,8 @@
1
+ module Ashikawa
2
+ module Core
3
+ # This Exception is thrown, when a document was requested from
4
+ # the server that does not exist.
5
+ class DocumentNotFoundException < RuntimeError
6
+ end
7
+ end
8
+ 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
@@ -1,6 +1,6 @@
1
1
  module Ashikawa
2
2
  module Core
3
3
  # Current version of Ashikawa::Core
4
- VERSION = "0.1"
4
+ VERSION = "0.2"
5
5
  end
6
6
  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
+ }