ashikawa-core 0.5.1 → 0.6.0

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 (50) hide show
  1. data/.gitignore +11 -9
  2. data/.rspec +4 -0
  3. data/.travis.yml +8 -3
  4. data/CONTRIBUTING.md +5 -5
  5. data/Gemfile +5 -1
  6. data/Gemfile.devtools +65 -0
  7. data/Guardfile +1 -11
  8. data/README.md +14 -8
  9. data/Rakefile +5 -103
  10. data/ashikawa-core.gemspec +3 -29
  11. data/config/flay.yml +3 -0
  12. data/config/flog.yml +2 -0
  13. data/config/mutant.yml +3 -0
  14. data/config/roodi.yml +18 -0
  15. data/config/site.reek +95 -0
  16. data/config/yardstick.yml +2 -0
  17. data/lib/ashikawa-core/collection.rb +138 -178
  18. data/lib/ashikawa-core/connection.rb +74 -26
  19. data/lib/ashikawa-core/cursor.rb +30 -9
  20. data/lib/ashikawa-core/database.rb +23 -19
  21. data/lib/ashikawa-core/document.rb +33 -8
  22. data/lib/ashikawa-core/exceptions/collection_not_found.rb +15 -0
  23. data/lib/ashikawa-core/exceptions/document_not_found.rb +4 -0
  24. data/lib/ashikawa-core/exceptions/index_not_found.rb +15 -0
  25. data/lib/ashikawa-core/exceptions/no_collection_provided.rb +4 -0
  26. data/lib/ashikawa-core/exceptions/unknown_path.rb +15 -0
  27. data/lib/ashikawa-core/figure.rb +73 -0
  28. data/lib/ashikawa-core/index.rb +25 -7
  29. data/lib/ashikawa-core/query.rb +68 -55
  30. data/lib/ashikawa-core/status.rb +77 -0
  31. data/lib/ashikawa-core/version.rb +1 -1
  32. data/spec/acceptance/basic_spec.rb +14 -18
  33. data/spec/acceptance/index_spec.rb +4 -2
  34. data/spec/acceptance/query_spec.rb +18 -19
  35. data/spec/acceptance_auth/auth_spec.rb +2 -2
  36. data/spec/setup/arangodb.sh +34 -39
  37. data/spec/spec_helper.rb +27 -0
  38. data/spec/unit/collection_spec.rb +25 -73
  39. data/spec/unit/connection_spec.rb +46 -15
  40. data/spec/unit/cursor_spec.rb +3 -3
  41. data/spec/unit/database_spec.rb +8 -7
  42. data/spec/unit/document_spec.rb +2 -2
  43. data/spec/unit/exception_spec.rb +21 -0
  44. data/spec/unit/figure_spec.rb +28 -0
  45. data/spec/unit/index_spec.rb +1 -1
  46. data/spec/unit/query_spec.rb +25 -25
  47. data/spec/unit/spec_helper.rb +6 -4
  48. data/spec/unit/status_spec.rb +51 -0
  49. data/tasks/adjustments.rake +46 -0
  50. metadata +31 -203
@@ -1,17 +1,21 @@
1
1
  require "rest-client"
2
2
  require "json"
3
3
  require "uri"
4
+ require "ashikawa-core/exceptions/index_not_found"
5
+ require "ashikawa-core/exceptions/document_not_found"
6
+ require "ashikawa-core/exceptions/collection_not_found"
7
+ require "ashikawa-core/exceptions/unknown_path"
4
8
 
5
9
  module Ashikawa
6
10
  module Core
7
- # Represents a Connection via HTTP to a certain host
11
+ # A Connection via HTTP to a certain host
8
12
  class Connection
9
13
  # The host part of the connection
10
14
  #
11
15
  # @return [String]
12
16
  # @api public
13
17
  # @example Get the host part of the connection
14
- # connection = Connection.new "http://localhost:8529"
18
+ # connection = Connection.new("http://localhost:8529")
15
19
  # connection.host # => "localhost"
16
20
  attr_reader :host
17
21
 
@@ -20,7 +24,7 @@ module Ashikawa
20
24
  # @return [String]
21
25
  # @api public
22
26
  # @example Get the scheme of the connection
23
- # connection = Connection.new "http://localhost:8529"
27
+ # connection = Connection.new("http://localhost:8529")
24
28
  # connection.scheme # => "http"
25
29
  attr_reader :scheme
26
30
 
@@ -29,52 +33,84 @@ module Ashikawa
29
33
  # @return [Fixnum]
30
34
  # @api public
31
35
  # @example Get the port of the connection
32
- # connection = Connection.new "http://localhost:8529"
36
+ # connection = Connection.new("http://localhost:8529")
33
37
  # connection.port # => 8529
34
38
  attr_reader :port
35
39
 
36
- # Username and password of the connection
40
+ # Username of the connection if using authentication
41
+ # @note you can set these properties with the `authenticate_with` method
37
42
  #
38
- # Needed if the database is running with HTTP base authentication
39
- # enabled. Username and password are sent with every request to
40
- # authenticate against the database.
41
- #
42
- # You can set these properties with the `authenticate_with` method
43
+ # @return String
44
+ # @api public
45
+ # @example Get the username of the connection
46
+ # connection = Connection.new("http://localhost:8529")
47
+ # connection.authenticate_with(:username => 'james', :password => 'bond')
48
+ # connection.username # => 'james'
49
+ attr_reader :username
50
+
51
+ # Password of the connection if using authentication
52
+ # @note you can set these properties with the `authenticate_with` method
43
53
  #
54
+ # @return String
44
55
  # @api public
45
- attr_reader :username, :password
56
+ # @example Get the password of the connection
57
+ # connection = Connection.new("http://localhost:8529")
58
+ # connection.authenticate_with(:username => 'james', :password => 'bond')
59
+ # connection.password # => 'bond'
60
+ attr_reader :password
46
61
 
47
62
  # Initialize a Connection with a given API String
48
63
  #
49
64
  # @param [String] api_string scheme, hostname and port as a String
50
65
  # @api public
51
66
  # @example Create a new Connection
52
- # connection = Connection.new "http://localhost:8529"
67
+ # connection = Connection.new("http://localhost:8529")
53
68
  def initialize(api_string = "http://localhost:8529")
54
- uri = URI api_string
69
+ uri = URI(api_string)
55
70
  @host = uri.host
56
71
  @port = uri.port
57
72
  @scheme = uri.scheme
58
73
  end
59
74
 
60
75
  # Sends a request to a given path returning the parsed result
61
- # (Prepends the api_string automatically)
76
+ # @note prepends the api_string automatically
62
77
  #
63
- # @example get request
64
- # connection.send_request('/collection/new_collection')
65
- # @example post request
66
- # connection.send_request('/collection/new_collection', :post => { :name => 'new_collection' })
67
78
  # @param [String] path the path you wish to send a request to.
68
79
  # @option params [Hash] :post POST data in case you want to send a POST request.
69
80
  # @return [Hash] parsed JSON response from the server
70
81
  # @api public
82
+ # @example get request
83
+ # connection.send_request('/collection/new_collection')
84
+ # @example post request
85
+ # connection.send_request('/collection/new_collection', :post => { :name => 'new_collection' })
71
86
  def send_request(path, params = {})
72
- raw = raw_result_for path, params
73
- JSON.parse raw
87
+ begin
88
+ raw = raw_result_for(path, params)
89
+ rescue RestClient::ResourceNotFound
90
+ resource_not_found_for(path)
91
+ end
92
+ JSON.parse(raw)
93
+ end
94
+
95
+ # Raise the fitting ResourceNotFoundException
96
+ #
97
+ # @raise [DocumentNotFoundException, CollectionNotFoundException, IndexNotFoundException]
98
+ # @return nil
99
+ # @api private
100
+ def resource_not_found_for(path)
101
+ path = path.split("/").delete_if { |part| part == "" }
102
+ resource = path.first
103
+
104
+ raise case resource
105
+ when "document" then DocumentNotFoundException
106
+ when "collection" then CollectionNotFoundException
107
+ when "index" then IndexNotFoundException
108
+ else UnknownPath
109
+ end
74
110
  end
75
111
 
76
112
  # Sends a request to a given path returning the raw result
77
- # (Prepends the api_string automatically)
113
+ # @note prepends the api_string automatically
78
114
  #
79
115
  # @example get request
80
116
  # connection.raw_result_for('/collection/new_collection')
@@ -85,15 +121,15 @@ module Ashikawa
85
121
  # @return [String] raw response from the server
86
122
  # @api public
87
123
  def raw_result_for(path, params = {})
88
- path = full_path path
124
+ path = full_path(path)
89
125
  method = [:post, :put, :delete].find { |method_name|
90
- params.has_key? method_name
126
+ params.has_key?(method_name)
91
127
  } || :get
92
128
 
93
- if [:post, :put].include? method
94
- RestClient.send method, path, params[method].to_json
129
+ if [:post, :put].include?(method)
130
+ RestClient.send(method, path, params[method].to_json)
95
131
  else
96
- RestClient.send method, path
132
+ RestClient.send(method, path)
97
133
  end
98
134
  end
99
135
 
@@ -101,6 +137,11 @@ module Ashikawa
101
137
  #
102
138
  # @return [Boolean]
103
139
  # @api public
140
+ # @example Is authentication activated for this connection?
141
+ # connection = Connection.new("http://localhost:8529")
142
+ # connection.authentication? #=> false
143
+ # connection.authenticate_with(:username => 'james', :password => 'bond')
144
+ # connection.authentication? #=> true
104
145
  def authentication?
105
146
  !!@username
106
147
  end
@@ -112,6 +153,9 @@ module Ashikawa
112
153
  # @return [self]
113
154
  # @raise [ArgumentError] if username or password are missing
114
155
  # @api public
156
+ # @example Authenticate with the database for all future requests
157
+ # connection = Connection.new("http://localhost:8529")
158
+ # connection.authenticate_with(:username => 'james', :password => 'bond')
115
159
  def authenticate_with(options = {})
116
160
  if options.key? :username and options.key? :password
117
161
  @username = options[:username]
@@ -128,6 +172,10 @@ module Ashikawa
128
172
  # @param [String] path The API path
129
173
  # @return [String] Full path
130
174
  # @api public
175
+ # @example Get the full path
176
+ # connection = Connection.new("http://localhost:8529")
177
+ # connection.full_path('documents') #=> "http://localhost:8529/_api/documents"
178
+ # connection.full_path('/documents') #=> "http://localhost:8529/_api/documents"
131
179
  def full_path(path)
132
180
  prefix = if authentication?
133
181
  "#{@scheme}://#{@username}:#{@password}@#{@host}:#{@port}"
@@ -2,7 +2,7 @@ require 'ashikawa-core/document'
2
2
 
3
3
  module Ashikawa
4
4
  module Core
5
- # Represents a Cursor on a certain Database.
5
+ # A Cursor on a certain Database.
6
6
  # It is an enumerable.
7
7
  class Cursor
8
8
  include Enumerable
@@ -10,11 +10,17 @@ module Ashikawa
10
10
  # The ID of the cursor
11
11
  # @return [Int]
12
12
  # @api public
13
+ # @example Get the id of the cursor
14
+ # cursor = Ashikawa::Core::Cursor.new(database, raw_cursor)
15
+ # cursor.id #=> 1337
13
16
  attr_reader :id
14
17
 
15
18
  # The number of documents
16
19
  # @return [Int]
17
20
  # @api public
21
+ # @example Get the number of documents
22
+ # cursor = Ashikawa::Core::Cursor.new(database, raw_cursor)
23
+ # cursor.length #=> 23
18
24
  attr_reader :length
19
25
 
20
26
  # Initialize a Cursor with the database and raw data
@@ -22,39 +28,54 @@ module Ashikawa
22
28
  # @param [Database] database
23
29
  # @param [Hash] raw_cursor
24
30
  # @api public
31
+ # @example Create a new Cursor from the raw representation
32
+ # cursor = Ashikawa::Core::Cursor.new(database, raw_cursor)
25
33
  def initialize(database, raw_cursor)
26
34
  @database = database
27
- parse_raw_cursor raw_cursor
35
+ parse_raw_cursor(raw_cursor)
28
36
  end
29
37
 
30
38
  # Iterate over the documents found by the cursor
31
39
  #
32
40
  # @yield [document]
41
+ # @return nil
33
42
  # @api public
34
- def each(&block)
43
+ # @example Print all documents
44
+ # cursor = Ashikawa::Core::Cursor.new(database, raw_cursor)
45
+ # cursor.each do |document|
46
+ # p document
47
+ # end
48
+ def each
35
49
  begin
36
50
  @current.each do |raw_document|
37
- block.call Document.new(@database, raw_document)
51
+ yield Document.new(@database, raw_document)
38
52
  end
39
53
  end while next_batch
54
+ nil
40
55
  end
41
56
 
42
57
  # Delete the cursor
58
+ # @return [Hash] parsed JSON response from the server
43
59
  # @api public
60
+ # @example Delete the cursor
61
+ # cursor = Ashikawa::Core::Cursor.new(database, raw_cursor)
62
+ # cursor.delete
44
63
  def delete
45
- @database.send_request "/cursor/#{@id}", delete: {}
64
+ @database.send_request("/cursor/#{@id}", :delete => {})
46
65
  end
47
66
 
48
67
  private
49
68
 
50
69
  # Pull the raw data from the cursor into this object
51
70
  #
71
+ # @return self
52
72
  # @api private
53
73
  def parse_raw_cursor(raw_cursor)
54
- @id = raw_cursor['id'].to_i if raw_cursor.has_key? 'id'
74
+ @id = raw_cursor['id'].to_i if raw_cursor.has_key?('id')
55
75
  @has_more = raw_cursor['hasMore']
56
- @length = raw_cursor['count'].to_i if raw_cursor.has_key? 'count'
76
+ @length = raw_cursor['count'].to_i if raw_cursor.has_key?('count')
57
77
  @current = raw_cursor['result']
78
+ self
58
79
  end
59
80
 
60
81
  # Get a new batch from the server
@@ -63,8 +84,8 @@ module Ashikawa
63
84
  # @api private
64
85
  def next_batch
65
86
  return false unless @has_more
66
- raw_cursor = @database.send_request "/cursor/#{@id}", put: {}
67
- parse_raw_cursor raw_cursor
87
+ raw_cursor = @database.send_request("/cursor/#{@id}", :put => {})
88
+ parse_raw_cursor(raw_cursor)
68
89
  end
69
90
  end
70
91
  end
@@ -1,3 +1,4 @@
1
+ require "ashikawa-core/exceptions/collection_not_found"
1
2
  require "ashikawa-core/collection"
2
3
  require "ashikawa-core/connection"
3
4
  require "ashikawa-core/cursor"
@@ -5,29 +6,29 @@ require "forwardable"
5
6
 
6
7
  module Ashikawa
7
8
  module Core
8
- # Represents an ArangoDB database in Ruby
9
+ # An ArangoDB database
9
10
  class Database
10
11
  extend Forwardable
11
12
 
12
13
  # Delegate sending requests to the connection
13
- delegate send_request: :@connection
14
- delegate host: :@connection
15
- delegate port: :@connection
16
- delegate scheme: :@connection
17
- delegate authenticate_with: :@connection
14
+ def_delegator :@connection, :send_request
15
+ def_delegator :@connection, :host
16
+ def_delegator :@connection, :port
17
+ def_delegator :@connection, :scheme
18
+ def_delegator :@connection, :authenticate_with
18
19
 
19
20
  # Initializes the connection to the database
20
21
  #
21
22
  # @param [Connection, String] connection A Connection object or a String to create a Connection object.
22
23
  # @api public
23
24
  # @example Access a Database by providing the URL
24
- # database = Ashikawa::Core::Database.new "http://localhost:8529"
25
+ # database = Ashikawa::Core::Database.new("http://localhost:8529")
25
26
  # @example Access a Database by providing a Connection
26
- # connection = Connection.new "http://localhost:8529"
27
+ # connection = Connection.new("http://localhost:8529")
27
28
  # database = Ashikawa::Core::Database.new connection
28
29
  def initialize(connection)
29
30
  if connection.class == String
30
- @connection = Ashikawa::Core::Connection.new connection
31
+ @connection = Ashikawa::Core::Connection.new(connection)
31
32
  else
32
33
  @connection = connection
33
34
  end
@@ -38,13 +39,13 @@ module Ashikawa
38
39
  # @return [Array<Collection>]
39
40
  # @api public
40
41
  # @example Get an Array containing the Collections in the database
41
- # database = Ashikawa::Core::Database.new "http://localhost:8529"
42
+ # database = Ashikawa::Core::Database.new("http://localhost:8529")
42
43
  # database["a"]
43
44
  # database["b"]
44
45
  # database.collections # => [ #<Collection name="a">, #<Collection name="b">]
45
46
  def collections
46
- server_response = send_request "/collection"
47
- server_response["collections"].map { |collection| Ashikawa::Core::Collection.new self, collection }
47
+ server_response = send_request("/collection")
48
+ server_response["collections"].map { |collection| Ashikawa::Core::Collection.new(self, collection) }
48
49
  end
49
50
 
50
51
  # Get or create a Collection based on name or ID
@@ -53,27 +54,30 @@ module Ashikawa
53
54
  # @return [Collection]
54
55
  # @api public
55
56
  # @example Get a Collection from the database by name
56
- # database = Ashikawa::Core::Database.new "http://localhost:8529"
57
+ # database = Ashikawa::Core::Database.new("http://localhost:8529")
57
58
  # database["a"] # => #<Collection name="a">
58
59
  # @example Get a Collection from the database by ID
59
- # database = Ashikawa::Core::Database.new "http://localhost:8529"
60
+ # database = Ashikawa::Core::Database.new("http://localhost:8529")
60
61
  # database["7254820"] # => #<Collection id=7254820>
61
62
  def [](collection_identifier)
62
63
  begin
63
- server_response = send_request "/collection/#{collection_identifier}"
64
- rescue RestClient::ResourceNotFound
65
- server_response = send_request "/collection", post: { name: collection_identifier }
64
+ server_response = send_request("/collection/#{collection_identifier}")
65
+ rescue CollectionNotFoundException
66
+ server_response = send_request("/collection", :post => { :name => collection_identifier })
66
67
  end
67
68
 
68
- Ashikawa::Core::Collection.new self, server_response
69
+ Ashikawa::Core::Collection.new(self, server_response)
69
70
  end
70
71
 
71
72
  # Return a Query initialized with this database
72
73
  #
73
74
  # @return [Query]
74
75
  # @api public
76
+ # @example Send an AQL query to the database
77
+ # database = Ashikawa::Core::Database.new("http://localhost:8529")
78
+ # database.query.execute "FOR u IN users LIMIT 2" # => #<Cursor id=33>
75
79
  def query
76
- Query.new self
80
+ Query.new(self)
77
81
  end
78
82
  end
79
83
  end
@@ -2,14 +2,14 @@ require 'ashikawa-core/exceptions/document_not_found'
2
2
 
3
3
  module Ashikawa
4
4
  module Core
5
- # Represents a certain Document within a certain Collection
5
+ # A certain Document within a certain Collection
6
6
  class Document
7
7
  # The ID of the document without the Collection prefix
8
8
  #
9
9
  # @return [Int]
10
10
  # @api public
11
11
  # @example Get the ID for a Document
12
- # document = Ashikawa::Core::Document.new database, raw_document
12
+ # document = Ashikawa::Core::Document.new(database, raw_document)
13
13
  # document.id # => 2345678
14
14
  attr_reader :id
15
15
 
@@ -18,7 +18,7 @@ module Ashikawa
18
18
  # @return [Int]
19
19
  # @api public
20
20
  # @example Get the Revision for a Document
21
- # document = Ashikawa::Core::Document.new database, raw_document
21
+ # document = Ashikawa::Core::Document.new(database, raw_document)
22
22
  # document.revision # => 3456789
23
23
  attr_reader :revision
24
24
 
@@ -27,17 +27,23 @@ module Ashikawa
27
27
  # @param [Database] database
28
28
  # @param [Hash] raw_document
29
29
  # @api public
30
+ # @example Create a document
31
+ # document = Ashikawa::Core::Document.new(database, raw_document)
30
32
  def initialize(database, raw_document)
31
33
  @database = database
32
34
  @collection_id, @id = raw_document['_id'].split('/').map { |id| id.to_i } unless raw_document['_id'].nil?
33
35
  @revision = raw_document['_rev'].to_i unless raw_document['_rev'].nil?
34
- @content = raw_document.delete_if { |key, value| key[0] == "_" }
36
+ @content = raw_document.delete_if { |key, value| key.start_with?("_") }
35
37
  end
36
38
 
37
39
  # Raises an exception if the document is not persisted
38
40
  #
39
41
  # @raise [DocumentNotFoundException]
40
- # @api semi-public
42
+ # @return nil
43
+ # @api semipublic
44
+ # @example Check if the document is persisted
45
+ # document = Ashikawa::Core::Document.new(database, raw_document)
46
+ # document.check_if_persisted!
41
47
  def check_if_persisted!
42
48
  raise DocumentNotFoundException if @id.nil?
43
49
  end
@@ -45,25 +51,36 @@ module Ashikawa
45
51
  # Get the value of an attribute of the document
46
52
  #
47
53
  # @param [String] attribute_name
48
- # @api public
49
54
  # @return [Object] The value of the attribute
55
+ # @api public
56
+ # @example Get the name attribute of a document
57
+ # document = Ashikawa::Core::Document.new(database, raw_document)
58
+ # document['name'] #=> 'Lebowski'
50
59
  def [](attribute_name)
51
60
  @content[attribute_name]
52
61
  end
53
62
 
54
63
  # Remove the document from the database
55
64
  #
65
+ # @return [Hash] parsed JSON response from the server
56
66
  # @api public
67
+ # @example Delete a document
68
+ # document = Ashikawa::Core::Document.new(database, raw_document)
69
+ # document.delete
57
70
  def delete
58
71
  check_if_persisted!
59
- @database.send_request "document/#{@collection_id}/#{@id}", delete: {}
72
+ @database.send_request("document/#{@collection_id}/#{@id}", :delete => {})
60
73
  end
61
74
 
62
75
  # Update the value of an attribute (Does not write to database)
63
76
  #
64
77
  # @param [String] attribute_name
65
78
  # @param [Object] value
79
+ # @return [Object] The value
66
80
  # @api public
81
+ # @example Change the name attribute of a document
82
+ # document = Ashikawa::Core::Document.new(database, raw_document)
83
+ # document['name'] = 'The dude'
67
84
  def []=(attribute_name, value)
68
85
  check_if_persisted!
69
86
  @content[attribute_name] = value
@@ -73,16 +90,24 @@ module Ashikawa
73
90
  #
74
91
  # @return [Hash]
75
92
  # @api public
93
+ # @example Get the hash representation of a document
94
+ # document = Ashikawa::Core::Document.new(database, raw_document)
95
+ # document.to_hash #=> { :name => "Lebowski", :occupation => "Not occupied" }
76
96
  def to_hash
77
97
  @content
78
98
  end
79
99
 
80
100
  # Save the changes to the database
81
101
  #
102
+ # @return [Hash] parsed JSON response from the server
82
103
  # @api public
104
+ # @example Save changes to a document
105
+ # document = Ashikawa::Core::Document.new(database, raw_document)
106
+ # document['occupation'] = 'Not occupied'
107
+ # document.save
83
108
  def save()
84
109
  check_if_persisted!
85
- @database.send_request "document/#{@collection_id}/#{@id}", put: @content
110
+ @database.send_request("document/#{@collection_id}/#{@id}", :put => @content)
86
111
  end
87
112
  end
88
113
  end