ashikawa-core 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/{config/rubocop.yml → .hound.yml} +17 -2
  4. data/.ruby-version +1 -1
  5. data/.travis.yml +6 -6
  6. data/CHANGELOG.md +22 -0
  7. data/Gemfile +3 -4
  8. data/Guardfile +3 -3
  9. data/README.md +12 -11
  10. data/Rakefile +52 -6
  11. data/ashikawa-core.gemspec +42 -19
  12. data/config/mutant.yml +1 -3
  13. data/config/reek.yml +3 -1
  14. data/lib/ashikawa-core/collection.rb +9 -7
  15. data/lib/ashikawa-core/configuration.rb +1 -1
  16. data/lib/ashikawa-core/connection.rb +43 -0
  17. data/lib/ashikawa-core/database.rb +61 -2
  18. data/lib/ashikawa-core/error_response.rb +14 -3
  19. data/lib/ashikawa-core/exceptions/client_error.rb +4 -4
  20. data/lib/ashikawa-core/exceptions/client_error/bad_syntax.rb +3 -2
  21. data/lib/ashikawa-core/exceptions/server_error.rb +4 -4
  22. data/lib/ashikawa-core/key_options.rb +3 -2
  23. data/lib/ashikawa-core/query.rb +33 -29
  24. data/lib/ashikawa-core/status.rb +9 -0
  25. data/lib/ashikawa-core/transaction.rb +3 -3
  26. data/lib/ashikawa-core/version.rb +1 -1
  27. data/spec/acceptance/basic_spec.rb +14 -8
  28. data/spec/acceptance/index_spec.rb +2 -2
  29. data/spec/acceptance/query_spec.rb +4 -4
  30. data/spec/acceptance/spec_helper.rb +24 -9
  31. data/spec/acceptance/transactions_spec.rb +2 -2
  32. data/spec/unit/collection_spec.rb +3 -3
  33. data/spec/unit/connection_spec.rb +38 -6
  34. data/spec/unit/database_spec.rb +58 -7
  35. data/spec/unit/document_spec.rb +3 -3
  36. data/spec/unit/exception_spec.rb +5 -1
  37. data/spec/unit/index_spec.rb +6 -1
  38. data/spec/unit/query_spec.rb +32 -22
  39. data/spec/unit/spec_helper.rb +5 -20
  40. data/spec/unit/status_spec.rb +25 -25
  41. data/spec/unit/transaction_spec.rb +4 -4
  42. metadata +192 -31
  43. data/.coveralls.yml +0 -1
  44. data/Gemfile.devtools +0 -71
  45. data/config/devtools.yml +0 -5
  46. data/config/flay.yml +0 -3
  47. data/config/flog.yml +0 -3
  48. data/config/yardstick.yml +0 -2
  49. data/tasks/adjustments.rake +0 -17
@@ -12,6 +12,7 @@ module Ashikawa
12
12
  module Core
13
13
  # An ArangoDB database
14
14
  class Database
15
+ # ArangoDB defines two different kinds of collections: Document and Edge Collections
15
16
  COLLECTION_TYPES = {
16
17
  document: 2,
17
18
  edge: 3
@@ -58,6 +59,64 @@ module Ashikawa
58
59
  @connection = configuration.connection
59
60
  end
60
61
 
62
+ # Create the database
63
+ #
64
+ # @example Create a new database with the name 'ashikawa'
65
+ # database = Ashikawa::Core::Database.new do |config|
66
+ # config.url = 'http://localhost:8529/_db/ashikawa'
67
+ # end
68
+ # database.create
69
+ def create
70
+ @connection.send_request_without_database_suffix('database', post: { name: @connection.database_name })
71
+ end
72
+
73
+ # Drop the database
74
+ #
75
+ # @example Drop a new database with the name 'ashikawa'
76
+ # database = Ashikawa::Core::Database.new do |config|
77
+ # config.url = 'http://localhost:8529/_db/ashikawa'
78
+ # end
79
+ # database.drop
80
+ def drop
81
+ @connection.send_request_without_database_suffix("database/#{name}", delete: {})
82
+ end
83
+
84
+ # Truncate all collections of the database
85
+ #
86
+ # @example Truncate all collections of the database
87
+ # database = Ashikawa::Core::Database.new do |config|
88
+ # config.url = 'http://localhost:8529'
89
+ # end
90
+ # database.truncate
91
+ def truncate
92
+ collections.each { |collection| collection.truncate }
93
+ end
94
+
95
+ # The name of the database
96
+ #
97
+ # @return [String]
98
+ # @api public
99
+ # @example Get the name of the databasse
100
+ # database = Ashikawa::Core::Database.new do |config|
101
+ # config.url = 'http://localhost:8529/_api/ashikawa'
102
+ # end
103
+ # database.name # => 'ashikawa'
104
+ def name
105
+ @connection.database_name
106
+ end
107
+
108
+ # Get a list of all databases
109
+ #
110
+ # @api public
111
+ # @example Get a list of all databases
112
+ # database = Ashikawa::Core::Database.new do |config|
113
+ # config.url = 'http://localhost:8529'
114
+ # end
115
+ # database.all_databases # => ['_system']
116
+ def all_databases
117
+ send_request('database')['result']
118
+ end
119
+
61
120
  # Returns a list of all non-system collections defined in the database
62
121
  #
63
122
  # @return [Array<Collection>]
@@ -131,8 +190,8 @@ module Ashikawa
131
190
  # Create a new Transaction for this database
132
191
  #
133
192
  # @param [String] action The JS action you want to execute
134
- # @options collections [Array<String>] :read The collections you want to read from
135
- # @options collections [Array<String>] :write The collections you want to write to
193
+ # @option collections [Array<String>] :read The collections you want to read from
194
+ # @option collections [Array<String>] :write The collections you want to write to
136
195
  # @return [Object] The result of the transaction
137
196
  # @api public
138
197
  # @example Create a new Transaction
@@ -28,6 +28,17 @@ module Ashikawa
28
28
  # All status codes for server errors
29
29
  ServerErrorStatuses = 500...599
30
30
 
31
+ # On completion of the request raise errors depending on the status
32
+ #
33
+ # @raise [BadSyntax] If the status code is a 400
34
+ # @raise [AuthenticationFailed] If the status code is a 401
35
+ # @raise [DocumentNotFoundException] If the status code is 404 and a document was requested
36
+ # @raise [CollectionNotFoundException] If the status code is 404 and a collection was requested
37
+ # @raise [IndexNotFoundException] If the status code is 404 and an index was requested
38
+ # @raise [ResourceNotFoundError] If the status code is 404 and any other resource was requested
39
+ # @raise [ClientError] If the status code is any other 4XX code
40
+ # @raise [ServerError] If the status code is any of the 5XX codes
41
+ # @return nil
31
42
  def on_complete(env)
32
43
  @body = env[:body]
33
44
  @url = env[:url]
@@ -49,7 +60,7 @@ module Ashikawa
49
60
  # @return nil
50
61
  # @api private
51
62
  def bad_syntax
52
- raise BadSyntax
63
+ raise BadSyntax, error
53
64
  end
54
65
 
55
66
  # Raise an Authentication Failed Error
@@ -99,10 +110,10 @@ module Ashikawa
99
110
  # @return [String] The formatted error message
100
111
  # @api private
101
112
  def error
102
- "#{@body['errorNum']}: #{@body["errorMessage"]}"
113
+ "#{@body['errorNum']}: #{@body['errorMessage']}"
103
114
  end
104
115
  end
105
116
 
106
- Faraday.register_middleware :response, error_response: -> { ErrorResponse }
117
+ Faraday::Response.register_middleware error_response: -> { ErrorResponse }
107
118
  end
108
119
  end
@@ -5,11 +5,11 @@ module Ashikawa
5
5
  class ClientError < RuntimeError
6
6
  # Create a new instance
7
7
  #
8
- # @param [Integer] status
8
+ # @param [Fixnum] status_code
9
9
  # @return RuntimeError
10
10
  # @api private
11
- def initialize(description)
12
- @description = description
11
+ def initialize(status_code)
12
+ @status_code = status_code
13
13
  end
14
14
 
15
15
  # String representation of the exception
@@ -17,7 +17,7 @@ module Ashikawa
17
17
  # @return String
18
18
  # @api private
19
19
  def to_s
20
- @description
20
+ @status_code
21
21
  end
22
22
  end
23
23
  end
@@ -9,7 +9,8 @@ module Ashikawa
9
9
  #
10
10
  # @return RuntimeError
11
11
  # @api private
12
- def initialize
12
+ def initialize(message = 'Status 400: The syntax of the request was bad')
13
+ @message = message
13
14
  super(400)
14
15
  end
15
16
 
@@ -18,7 +19,7 @@ module Ashikawa
18
19
  # @return String
19
20
  # @api private
20
21
  def to_s
21
- 'Status 400: The syntax of the request was bad'
22
+ @message
22
23
  end
23
24
  end
24
25
  end
@@ -5,11 +5,11 @@ module Ashikawa
5
5
  class ServerError < RuntimeError
6
6
  # Create a new instance
7
7
  #
8
- # @param [Integer] status
8
+ # @param [Fixnum] status_code
9
9
  # @return RuntimeError
10
10
  # @api private
11
- def initialize(description)
12
- @description = description
11
+ def initialize(status_code)
12
+ @status_code = status_code
13
13
  end
14
14
 
15
15
  # String representation of the exception
@@ -17,7 +17,7 @@ module Ashikawa
17
17
  # @return String
18
18
  # @api private
19
19
  def to_s
20
- @description
20
+ @status_code
21
21
  end
22
22
  end
23
23
  end
@@ -14,7 +14,7 @@ module Ashikawa
14
14
 
15
15
  # A specific start value
16
16
  #
17
- # @return Integer
17
+ # @return Fixnum
18
18
  # @api public
19
19
  # @example Get the type of the KeyOptions
20
20
  # keyOptions = KeyOptions.new({ :offset => 12 })
@@ -23,7 +23,7 @@ module Ashikawa
23
23
 
24
24
  # Size of increment steps
25
25
  #
26
- # @return Integer
26
+ # @return Fixnum
27
27
  # @api public
28
28
  # @example Get the type of the KeyOptions
29
29
  # keyOptions = KeyOptions.new({ :increment => 12 })
@@ -41,6 +41,7 @@ module Ashikawa
41
41
 
42
42
  # Create a new KeyOptions object from the raw key options
43
43
  #
44
+ # @param [Hash] raw_key_options The raw options for the key returned from the server
44
45
  # @api public
45
46
  # @example Create a new KeyOptions object
46
47
  # KeyOptions.new({ :type => :autoincrement })
@@ -11,13 +11,14 @@ module Ashikawa
11
11
  class Query
12
12
  extend Forwardable
13
13
 
14
+ # For each simple query define the allowed attributes for filtering
14
15
  ALLOWED_KEYS_FOR_PATH = {
15
16
  'simple/all' => [:limit, :skip, :collection],
16
17
  'simple/by-example' => [:limit, :skip, :example, :collection],
17
18
  'simple/near' => [:latitude, :longitude, :distance, :skip, :limit, :geo, :collection],
18
19
  'simple/within' => [:latitude, :longitude, :radius, :distance, :skip, :limit, :geo, :collection],
19
20
  'simple/range' => [:attribute, :left, :right, :closed, :limit, :skip, :collection],
20
- 'cursor' => [:query, :count, :batch_size, :collection],
21
+ 'cursor' => [:query, :count, :batch_size, :collection, :bind_vars],
21
22
  'query' => [:query],
22
23
  'simple/first-example' => [:example, :collection]
23
24
  }
@@ -40,8 +41,8 @@ module Ashikawa
40
41
  # Retrieves all documents for a collection
41
42
  #
42
43
  # @note It is advised to NOT use this method due to possible HUGE data amounts requested
43
- # @option options [Integer] :limit limit the maximum number of queried and returned elements.
44
- # @option options [Integer] :skip skip the first <n> documents of the query.
44
+ # @option options [Fixnum] :limit limit the maximum number of queried and returned elements.
45
+ # @option options [Fixnum] :skip skip the first <n> documents of the query.
45
46
  # @return [Cursor]
46
47
  # @raise [NoCollectionProvidedException] If you provided a database, no collection
47
48
  # @api public
@@ -56,8 +57,8 @@ module Ashikawa
56
57
  #
57
58
  # @option example [Hash] a Hash with data matching the documents you are looking for.
58
59
  # @option options [Hash] a Hash with additional settings for the query.
59
- # @option options [Integer] :limit limit the maximum number of queried and returned elements.
60
- # @option options [Integer] :skip skip the first <n> documents of the query.
60
+ # @option options [Fixnum] :limit limit the maximum number of queried and returned elements.
61
+ # @option options [Fixnum] :skip skip the first <n> documents of the query.
61
62
  # @return [Cursor]
62
63
  # @raise [NoCollectionProvidedException] If you provided a database, no collection
63
64
  # @api public
@@ -85,12 +86,12 @@ module Ashikawa
85
86
 
86
87
  # Looks for documents in a collection based on location
87
88
  #
88
- # @option options [Integer] :latitude Latitude location for your search.
89
- # @option options [Integer] :longitude Longitude location for your search.
90
- # @option options [Integer] :skip The documents to skip in the query.
91
- # @option options [Integer] :distance If given, the attribute key used to store the distance.
92
- # @option options [Integer] :limit The maximal amount of documents to return (default: 100).
93
- # @option options [Integer] :geo If given, the identifier of the geo-index to use.
89
+ # @option options [Fixnum] :latitude Latitude location for your search.
90
+ # @option options [Fixnum] :longitude Longitude location for your search.
91
+ # @option options [Fixnum] :skip The documents to skip in the query.
92
+ # @option options [Fixnum] :distance If given, the attribute key used to store the distance.
93
+ # @option options [Fixnum] :limit The maximal amount of documents to return (default: 100).
94
+ # @option options [Fixnum] :geo If given, the identifier of the geo-index to use.
94
95
  # @return [Cursor]
95
96
  # @raise [NoCollectionProvidedException] If you provided a database, no collection
96
97
  # @api public
@@ -103,13 +104,13 @@ module Ashikawa
103
104
 
104
105
  # Looks for documents in a collection within a radius
105
106
  #
106
- # @option options [Integer] :latitude Latitude location for your search.
107
- # @option options [Integer] :longitude Longitude location for your search.
108
- # @option options [Integer] :radius Radius around the given location you want to search in.
109
- # @option options [Integer] :skip The documents to skip in the query.
110
- # @option options [Integer] :distance If given, the attribute key used to store the distance.
111
- # @option options [Integer] :limit The maximal amount of documents to return (default: 100).
112
- # @option options [Integer] :geo If given, the identifier of the geo-index to use.
107
+ # @option options [Fixnum] :latitude Latitude location for your search.
108
+ # @option options [Fixnum] :longitude Longitude location for your search.
109
+ # @option options [Fixnum] :radius Radius around the given location you want to search in.
110
+ # @option options [Fixnum] :skip The documents to skip in the query.
111
+ # @option options [Fixnum] :distance If given, the attribute key used to store the distance.
112
+ # @option options [Fixnum] :limit The maximal amount of documents to return (default: 100).
113
+ # @option options [Fixnum] :geo If given, the identifier of the geo-index to use.
113
114
  # @return [Cursor]
114
115
  # @api public
115
116
  # @raise [NoCollectionProvidedException] If you provided a database, no collection
@@ -122,12 +123,12 @@ module Ashikawa
122
123
 
123
124
  # Looks for documents in a collection with an attribute between two values
124
125
  #
125
- # @option options [Integer] :attribute The attribute path to check.
126
- # @option options [Integer] :left The lower bound
127
- # @option options [Integer] :right The upper bound
128
- # @option options [Integer] :closed If true, the interval includes right
129
- # @option options [Integer] :skip The documents to skip in the query (optional).
130
- # @option options [Integer] :limit The maximal amount of documents to return (optional).
126
+ # @option options [Fixnum] :attribute The attribute path to check.
127
+ # @option options [Fixnum] :left The lower bound
128
+ # @option options [Fixnum] :right The upper bound
129
+ # @option options [Fixnum] :closed If true, the interval includes right
130
+ # @option options [Fixnum] :skip The documents to skip in the query (optional).
131
+ # @option options [Fixnum] :limit The maximal amount of documents to return (optional).
131
132
  # @return [Cursor]
132
133
  # @raise [NoCollectionProvidedException] If you provided a database, no collection
133
134
  # @api public
@@ -141,13 +142,18 @@ module Ashikawa
141
142
  # Send an AQL query to the database
142
143
  #
143
144
  # @param [String] query
144
- # @option options [Integer] :count Should the number of results be counted?
145
- # @option options [Integer] :batch_size Set the number of results returned at once
145
+ # @option options [Fixnum] :count Should the number of results be counted?
146
+ # @option options [Fixnum] :batch_size Set the number of results returned at once
146
147
  # @return [Cursor]
147
148
  # @api public
148
149
  # @example Send an AQL query to the database
149
150
  # query = Ashikawa::Core::Query.new(collection)
150
151
  # query.execute('FOR u IN users LIMIT 2') # => #<Cursor id=33>
152
+ # @example Usage of bind variables
153
+ # db = Ashikawa::Core::Database.new(){|conf| conf.url="http://127.0.0.1:8529"}
154
+ # query = 'FOR t IN TRAVERSAL(imdb_vertices, imdb_edges, "imdb_vertices/759", "outbound", {maxDepth: 2})' +
155
+ # 'FILTER t.vertex.genre == @foo RETURN t'
156
+ # db.query.execute(query, bind_vars: {'foo' => 'Comedy'}).to_a
151
157
  def execute(query, options = {})
152
158
  wrapped_request('cursor', :post, options.merge({ query: query }))
153
159
  end
@@ -187,8 +193,8 @@ module Ashikawa
187
193
 
188
194
  # Removes the keys that are not allowed from an object
189
195
  #
196
+ # @param [String] path The path for the request
190
197
  # @param [Hash] options
191
- # @param [Array<Symbol>] allowed_keys
192
198
  # @return [Hash] The filtered Hash
193
199
  # @api private
194
200
  def prepare_request(path, options)
@@ -213,7 +219,6 @@ module Ashikawa
213
219
  #
214
220
  # @param [String] path The path for the request
215
221
  # @param [Hash] request The data send to the database
216
- # @param [Array<Symbol>] allowed_keys The keys allowed for this request
217
222
  # @return [String] Server response
218
223
  # @raise [NoCollectionProvidedException] If you provided a database, no collection
219
224
  # @api private
@@ -226,7 +231,6 @@ module Ashikawa
226
231
  # @param [String] path The path for the request
227
232
  # @param [Symbol] request_method The request method to perform
228
233
  # @param [Hash] request The data send to the database
229
- # @param [Array] allowed_keys Keys allowed in request, if nil: All keys are allowed
230
234
  # @return [Cursor]
231
235
  # @api private
232
236
  def wrapped_request(path, request_method, request)
@@ -3,10 +3,19 @@ module Ashikawa
3
3
  module Core
4
4
  # Wrapper around the status of a collection
5
5
  class Status
6
+ # ArangoDB Status for a new born collection
6
7
  STATUS_NEW_BORN = 1
8
+
9
+ # ArangoDB Status for a collection that is unloaded
7
10
  STATUS_UNLOADED = 2
11
+
12
+ # ArangoDB Status for a collection that is loaded
8
13
  STATUS_LOADED = 3
14
+
15
+ # ArangoDB Status for a collection that is being unloaded
9
16
  STATUS_BEING_UNLOADED = 4
17
+
18
+ # Highest ArangoDB Status that means that the collection is not corrupted
10
19
  MAX_UNCORRUPTED = 5
11
20
 
12
21
  # Create a wrapper around a given status
@@ -45,7 +45,7 @@ module Ashikawa
45
45
 
46
46
  # An optional numeric value used to set a timeout for waiting on collection locks
47
47
  #
48
- # @return [Integer]
48
+ # @return [Fixnum]
49
49
  # @api public
50
50
  # @example Check how long the lock timeout is
51
51
  # transaction.lock_timeout # => 30
@@ -55,7 +55,7 @@ module Ashikawa
55
55
 
56
56
  # An optional numeric value used to set a timeout for waiting on collection locks
57
57
  #
58
- # @param [Integer] lock_timeout
58
+ # @param [Fixnum] timeout
59
59
  # @api public
60
60
  # @example Set the lock timeout to 30
61
61
  # transaction.lock_timeout = 30
@@ -84,7 +84,7 @@ module Ashikawa
84
84
 
85
85
  # Execute the transaction
86
86
  #
87
- # @param [Object] action_params The parameters for the defined action
87
+ # @param [Object] action_parameters The parameters for the defined action
88
88
  # @return Object The result of the transaction
89
89
  # @api public
90
90
  # @example Run a Transaction
@@ -2,6 +2,6 @@
2
2
  module Ashikawa
3
3
  module Core
4
4
  # Current version of Ashikawa::Core
5
- VERSION = '0.10.0'
5
+ VERSION = '0.11.0'
6
6
  end
7
7
  end
@@ -15,6 +15,12 @@ describe 'Basics' do
15
15
  subject['new_name'].delete
16
16
  end
17
17
 
18
+ it 'should have one more database if a database with random name is created' do
19
+ expect {
20
+ database_with_random_name.create
21
+ }.to change { DATABASE.all_databases.length }.by(1)
22
+ end
23
+
18
24
  it 'should create and delete collections' do
19
25
  subject.collections.each { |collection| collection.delete }
20
26
  subject['collection_1']
@@ -27,12 +33,12 @@ describe 'Basics' do
27
33
 
28
34
  it 'should create a non-volatile collection by default' do
29
35
  subject.create_collection('nonvolatile_collection')
30
- expect(subject['nonvolatile_collection'].volatile?).to be_false
36
+ expect(subject['nonvolatile_collection'].volatile?).to be_falsey
31
37
  end
32
38
 
33
39
  it 'should create a volatile collection' do
34
40
  subject.create_collection('volatile_collection', is_volatile: true)
35
- expect(subject['volatile_collection'].volatile?).to be_true
41
+ expect(subject['volatile_collection'].volatile?).to be_truthy
36
42
  end
37
43
 
38
44
  it 'should create an autoincrementing collection' do
@@ -72,11 +78,11 @@ describe 'Basics' do
72
78
 
73
79
  it 'should be possible to load and unload collections' do
74
80
  my_collection = subject['test_collection']
75
- expect(my_collection.status.loaded?).to be_true
81
+ expect(my_collection.status.loaded?).to be_truthy
76
82
  my_collection.unload
77
83
  my_id = my_collection.id
78
84
  subject[my_id]
79
- expect(subject[my_id].status.loaded?).to be_false
85
+ expect(subject[my_id].status.loaded?).to be_falsey
80
86
  end
81
87
 
82
88
  it 'should be possible to get figures' do
@@ -96,9 +102,9 @@ describe 'Basics' do
96
102
  it 'should change and receive information about waiting for sync' do
97
103
  my_collection = subject['my_collection']
98
104
  my_collection.wait_for_sync = false
99
- expect(my_collection.wait_for_sync?).to be_false
105
+ expect(my_collection.wait_for_sync?).to be_falsey
100
106
  my_collection.wait_for_sync = true
101
- expect(my_collection.wait_for_sync?).to be_true
107
+ expect(my_collection.wait_for_sync?).to be_truthy
102
108
  end
103
109
 
104
110
  it 'should be possible to get information about the number of documents' do
@@ -107,7 +113,7 @@ describe 'Basics' do
107
113
  empty_collection.create_document(name: 'testname', age: 27)
108
114
  empty_collection.create_document(name: 'anderer name', age: 28)
109
115
  expect(empty_collection.length).to eq(2)
110
- empty_collection.truncate!
116
+ empty_collection.truncate
111
117
  expect(empty_collection.length).to eq(0)
112
118
  end
113
119
 
@@ -155,7 +161,7 @@ describe 'Basics' do
155
161
 
156
162
  it 'should be possible to get a single attribute by AQL query' do
157
163
  collection = subject['documenttests']
158
- collection.truncate!
164
+ collection.truncate
159
165
  collection.create_document(name: 'The Dude', bowling: true)
160
166
 
161
167
  expect(subject.query.execute('FOR doc IN documenttests RETURN doc.name').to_a.first). to eq 'The Dude'