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.
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
+ }