ashikawa-core 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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