ashikawa-core 0.10.0 → 0.11.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 (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'