ashikawa-core 0.9.0 → 0.10.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +7 -7
  4. data/CHANGELOG.md +25 -0
  5. data/CONTRIBUTING.md +4 -4
  6. data/Gemfile.devtools +32 -16
  7. data/README.md +9 -11
  8. data/Rakefile +3 -0
  9. data/ashikawa-core.gemspec +7 -5
  10. data/config/flay.yml +2 -2
  11. data/config/flog.yml +2 -1
  12. data/config/mutant.yml +14 -1
  13. data/config/reek.yml +9 -4
  14. data/config/rubocop.yml +14 -14
  15. data/lib/ashikawa-core/collection.rb +10 -6
  16. data/lib/ashikawa-core/configuration.rb +10 -5
  17. data/lib/ashikawa-core/connection.rb +30 -21
  18. data/lib/ashikawa-core/cursor.rb +2 -3
  19. data/lib/ashikawa-core/database.rb +12 -35
  20. data/lib/ashikawa-core/document.rb +3 -4
  21. data/lib/ashikawa-core/edge.rb +5 -5
  22. data/lib/ashikawa-core/error_response.rb +108 -0
  23. data/lib/ashikawa-core/index.rb +4 -14
  24. data/lib/ashikawa-core/query.rb +1 -1
  25. data/lib/ashikawa-core/transaction.rb +1 -16
  26. data/lib/ashikawa-core/version.rb +1 -1
  27. data/spec/acceptance/basic_spec.rb +9 -6
  28. data/spec/acceptance/spec_helper.rb +5 -4
  29. data/spec/setup/arangodb.sh +12 -17
  30. data/spec/unit/collection_spec.rb +60 -18
  31. data/spec/unit/configuration_spec.rb +35 -4
  32. data/spec/unit/connection_spec.rb +23 -65
  33. data/spec/unit/cursor_spec.rb +2 -2
  34. data/spec/unit/database_spec.rb +16 -28
  35. data/spec/unit/document_spec.rb +3 -3
  36. data/spec/unit/edge_spec.rb +1 -1
  37. data/spec/unit/index_spec.rb +1 -1
  38. data/spec/unit/query_spec.rb +1 -1
  39. data/spec/unit/spec_helper.rb +3 -2
  40. data/tasks/adjustments.rake +0 -14
  41. metadata +26 -13
  42. data/lib/ashikawa-core/request_preprocessor.rb +0 -50
  43. data/lib/ashikawa-core/response_preprocessor.rb +0 -160
@@ -4,8 +4,8 @@ require 'faraday'
4
4
  require 'null_logger'
5
5
  require 'uri'
6
6
  require 'equalizer'
7
- require 'ashikawa-core/request_preprocessor'
8
- require 'ashikawa-core/response_preprocessor'
7
+ require 'faraday_middleware'
8
+ require 'ashikawa-core/error_response'
9
9
 
10
10
  module Ashikawa
11
11
  module Core
@@ -45,20 +45,34 @@ module Ashikawa
45
45
  # connection.port # => 8529
46
46
  def_delegator :@connection, :port
47
47
 
48
+ # The Faraday connection object
49
+ #
50
+ # @return [Faraday]
51
+ # @api public
52
+ # @example Set additional response middleware
53
+ # connection = Connection.new('http://localhost:8529')
54
+ # connection.connection.response :caching
55
+ attr_reader :connection
56
+
48
57
  # Initialize a Connection with a given API String
49
58
  #
50
59
  # @param [String] api_string scheme, hostname and port as a String
51
- # @option opts [Object] adapter The Faraday adapter you want to use. Defaults to Default Adapter
52
- # @option opts [Object] logger The logger you want to use. Defaults to Null Logger.
60
+ # @option options [Object] adapter The Faraday adapter you want to use. Defaults to Default Adapter
61
+ # @option options [Object] logger The logger you want to use. Defaults to Null Logger.
53
62
  # @api public
54
63
  # @example Create a new Connection
55
64
  # connection = Connection.new('http://localhost:8529')
56
- def initialize(api_string, opts = {})
57
- logger = opts[:logger] || NullLogger.instance
58
- adapter = opts[:adapter] || Faraday.default_adapter
65
+ def initialize(api_string, options = {})
66
+ logger = options.fetch(:logger) { NullLogger.instance }
67
+ adapter = options.fetch(:adapter) { Faraday.default_adapter }
68
+
59
69
  @connection = Faraday.new("#{api_string}/_api") do |connection|
60
- connection.request :ashikawa_request, logger
61
- connection.response :ashikawa_response, logger
70
+ connection.request :json
71
+
72
+ connection.response :logger, logger
73
+ connection.response :error_response
74
+ connection.response :json
75
+
62
76
  connection.adapter(*adapter)
63
77
  end
64
78
  end
@@ -78,6 +92,8 @@ module Ashikawa
78
92
  method = http_verb(params)
79
93
  result = @connection.public_send(method, path, params[method])
80
94
  result.body
95
+ rescue Faraday::Error::ParsingError
96
+ raise Ashikawa::Core::JsonError
81
97
  end
82
98
 
83
99
  # Checks if authentication for this Connection is active or not
@@ -95,18 +111,11 @@ module Ashikawa
95
111
 
96
112
  # Authenticate with given username and password
97
113
  #
98
- # @option [String] username
99
- # @option [String] password
100
- # @return [self]
101
- # @raise [ArgumentError] if username or password are missing
102
- # @api public
103
- # @example Authenticate with the database for all future requests
104
- # connection = Connection.new('http://localhost:8529')
105
- # connection.authenticate_with(:username => 'james', :password => 'bond')
106
- def authenticate_with(options = {})
107
- raise ArgumentError, 'missing username or password' unless options.key? :username and options.key? :password
108
- @authentication = @connection.basic_auth(options[:username], options[:password])
109
- self
114
+ # @param [String] username
115
+ # @param [String] password
116
+ # @api private
117
+ def authenticate_with(username, password)
118
+ @authentication = @connection.basic_auth(username, password)
110
119
  end
111
120
 
112
121
  private
@@ -57,12 +57,11 @@ module Ashikawa
57
57
  def each
58
58
  return to_enum(__callee__) unless block_given?
59
59
 
60
- loop do
60
+ begin
61
61
  @current.each do |raw_document|
62
62
  yield parse_raw_document(raw_document)
63
63
  end
64
- break unless next_batch
65
- end
64
+ end while next_batch
66
65
  nil
67
66
  end
68
67
 
@@ -53,7 +53,7 @@ module Ashikawa
53
53
  # config.logger = my_logger
54
54
  # end
55
55
  def initialize
56
- configuration = Ashikawa::Core::Configuration.new
56
+ configuration = Configuration.new
57
57
  yield(configuration)
58
58
  @connection = configuration.connection
59
59
  end
@@ -85,16 +85,16 @@ module Ashikawa
85
85
  # Create a Collection based on name
86
86
  #
87
87
  # @param [String] collection_identifier The desired name of the collection
88
- # @option opts [Boolean] :is_volatile Should the collection be volatile? Default is false
89
- # @option opts [Boolean] :content_type What kind of content should the collection have? Default is :document
88
+ # @option options [Boolean] :is_volatile Should the collection be volatile? Default is false
89
+ # @option options [Boolean] :content_type What kind of content should the collection have? Default is :document
90
90
  # @return [Collection]
91
91
  # @api public
92
92
  # @example Create a new, volatile collection
93
93
  # database = Ashikawa::Core::Database.new('http://localhost:8529')
94
94
  # database.create_collection('a', :isVolatile => true) # => #<Collection name="a">
95
- def create_collection(collection_identifier, opts = {})
96
- response = send_request('collection', post: translate_params(collection_identifier, opts))
97
- Ashikawa::Core::Collection.new(self, response)
95
+ def create_collection(collection_identifier, options = {})
96
+ response = send_request('collection', post: translate_params(collection_identifier, options))
97
+ Collection.new(self, response)
98
98
  end
99
99
 
100
100
  # Get or create a Collection based on name or ID
@@ -109,13 +109,10 @@ module Ashikawa
109
109
  # database = Ashikawa::Core::Database.new('http://localhost:8529')
110
110
  # database['7254820'] # => #<Collection id=7254820>
111
111
  def collection(collection_identifier)
112
- begin
113
- response = send_request("collection/#{collection_identifier}")
114
- rescue CollectionNotFoundException
115
- response = send_request('collection', post: { name: collection_identifier })
116
- end
117
-
118
- Ashikawa::Core::Collection.new(self, response)
112
+ response = send_request("collection/#{collection_identifier}")
113
+ Collection.new(self, response)
114
+ rescue CollectionNotFoundException
115
+ create_collection(collection_identifier)
119
116
  end
120
117
 
121
118
  alias_method :[], :collection
@@ -142,25 +139,7 @@ module Ashikawa
142
139
  # transaction = database.create_transaction('function () { return 5; }", :read => ["collection_1'])
143
140
  # transaction.execute #=> 5
144
141
  def create_transaction(action, collections)
145
- Ashikawa::Core::Transaction.new(self, action, collections)
146
- end
147
-
148
- # Authenticate with given username and password
149
- #
150
- # @option [String] username
151
- # @option [String] password
152
- # @return [self]
153
- # @raise [ArgumentError] if username or password are missing
154
- # @api public
155
- # @deprecated Use the initialization block instead
156
- # @example Authenticate with the database for all future requests
157
- # database = Ashikawa::Core::Database.new do |config|
158
- # config.url = 'http://localhost:8529'
159
- # end
160
- # database.authenticate_with(:username => 'james', :password => 'bond')
161
- def authenticate_with(options = {})
162
- warn 'authenticate_with is deprecated. Please use the config block instead.'
163
- @connection.authenticate_with(options)
142
+ Transaction.new(self, action, collections)
164
143
  end
165
144
 
166
145
  private
@@ -171,9 +150,7 @@ module Ashikawa
171
150
  # @return [Array]
172
151
  # @api private
173
152
  def parse_raw_collections(raw_collections)
174
- raw_collections.map { |collection|
175
- Ashikawa::Core::Collection.new(self, collection)
176
- }
153
+ raw_collections.map { |collection| Collection.new(self, collection) }
177
154
  end
178
155
 
179
156
  # Translate the key options into the required format
@@ -95,7 +95,6 @@ module Ashikawa
95
95
  # document = Ashikawa::Core::Document.new(database, raw_document)
96
96
  # document['name'] = 'The dude'
97
97
  def []=(attribute_name, value)
98
- check_if_persisted!
99
98
  @content[attribute_name] = value
100
99
  end
101
100
 
@@ -105,8 +104,8 @@ module Ashikawa
105
104
  # @api public
106
105
  # @example Get the hash representation of a document
107
106
  # document = Ashikawa::Core::Document.new(database, raw_document)
108
- # document.hash #=> { :name => 'Lebowski", :occupation => "Not occupied' }
109
- def hash
107
+ # document.to_h #=> { :name => 'Lebowski", :occupation => "Not occupied' }
108
+ def to_h
110
109
  @content
111
110
  end
112
111
 
@@ -145,7 +144,7 @@ module Ashikawa
145
144
  @id = raw_document['_id'] || :not_persisted
146
145
  @key = raw_document['_key']
147
146
  @revision = raw_document['_rev'] || :not_persisted
148
- @content = raw_document.delete_if { |key, value| key.start_with?('_') }
147
+ @content = raw_document.delete_if { |key| key.start_with?('_') }
149
148
  self
150
149
  end
151
150
 
@@ -28,16 +28,16 @@ module Ashikawa
28
28
 
29
29
  # Initialize an Edge with the database and raw data
30
30
  #
31
- # @param [Database] database
31
+ # @param [Database] _database
32
32
  # @param [Hash] raw_edge
33
- # @param [Hash] additional_data
33
+ # @param [Hash] _additional_data
34
34
  # @api public
35
35
  # @example Create an Edge
36
36
  # document = Ashikawa::Core::Edge.new(database, raw_edge)
37
- def initialize(database, raw_edge, additional_data = {})
37
+ def initialize(_database, raw_edge, _additional_data = {})
38
38
  @from_id = raw_edge['_from']
39
39
  @to_id = raw_edge['_to']
40
- super(database, raw_edge, additional_data)
40
+ super
41
41
  end
42
42
 
43
43
  protected
@@ -47,7 +47,7 @@ module Ashikawa
47
47
  # @param [Hash] opts Options for this request
48
48
  # @return [Hash] The parsed response from the server
49
49
  # @api private
50
- def send_request_for_document(opts = {})
50
+ def send_request_for_document(opts)
51
51
  @database.send_request("edge/#{@id}", opts)
52
52
  end
53
53
  end
@@ -0,0 +1,108 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'ashikawa-core/exceptions/client_error'
3
+ require 'ashikawa-core/exceptions/client_error/resource_not_found'
4
+ require 'ashikawa-core/exceptions/client_error/resource_not_found/index_not_found'
5
+ require 'ashikawa-core/exceptions/client_error/resource_not_found/document_not_found'
6
+ require 'ashikawa-core/exceptions/client_error/resource_not_found/collection_not_found'
7
+ require 'ashikawa-core/exceptions/client_error/bad_syntax'
8
+ require 'ashikawa-core/exceptions/client_error/authentication_failed'
9
+ require 'ashikawa-core/exceptions/server_error'
10
+ require 'ashikawa-core/exceptions/server_error/json_error'
11
+
12
+ module Ashikawa
13
+ module Core
14
+ # A response from the server
15
+ class ErrorResponse < Faraday::Response::Middleware
16
+ # Status code for a [Bad Request](http://httpstatus.es/400)
17
+ BadSyntaxStatus = 400
18
+
19
+ # Status code for an [Unauthorized Request](http://httpstatus.es/401)
20
+ AuthenticationFailed = 401
21
+
22
+ # Status code for a [Not Found Resource](http://httpstatus.es/404)
23
+ ResourceNotFoundError = 404
24
+
25
+ # All other status codes for client errors
26
+ ClientErrorStatuses = 405...499
27
+
28
+ # All status codes for server errors
29
+ ServerErrorStatuses = 500...599
30
+
31
+ def on_complete(env)
32
+ @body = env[:body]
33
+ @url = env[:url]
34
+
35
+ case env[:status]
36
+ when BadSyntaxStatus then bad_syntax
37
+ when AuthenticationFailed then authentication_failed
38
+ when ResourceNotFoundError then resource_not_found
39
+ when ClientErrorStatuses then client_error
40
+ when ServerErrorStatuses then server_error
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # Raise a Bad Syntax Error
47
+ #
48
+ # @raise [BadSyntax]
49
+ # @return nil
50
+ # @api private
51
+ def bad_syntax
52
+ raise BadSyntax
53
+ end
54
+
55
+ # Raise an Authentication Failed Error
56
+ #
57
+ # @raise [AuthenticationFailed]
58
+ # @return nil
59
+ # @api private
60
+ def authentication_failed
61
+ raise Core::AuthenticationFailed
62
+ end
63
+
64
+ # Raise a Client Error for a given body
65
+ #
66
+ # @raise [ClientError]
67
+ # @return nil
68
+ # @api private
69
+ def client_error
70
+ raise ClientError, error
71
+ end
72
+
73
+ # Raise a Server Error for a given body
74
+ #
75
+ # @raise [ServerError]
76
+ # @return nil
77
+ # @api private
78
+ def server_error
79
+ raise ServerError, error
80
+ end
81
+
82
+ # Raise the fitting ResourceNotFoundException
83
+ #
84
+ # @raise [DocumentNotFoundException, CollectionNotFoundException, IndexNotFoundException]
85
+ # @return nil
86
+ # @api private
87
+ def resource_not_found
88
+ raise case @url.path
89
+ when %r{\A(/_db/[^/]+)?/_api/document} then DocumentNotFoundException
90
+ when %r{\A(/_db/[^/]+)?/_api/collection} then CollectionNotFoundException
91
+ when %r{\A(/_db/[^/]+)?/_api/index} then IndexNotFoundException
92
+ else ResourceNotFound
93
+ end
94
+ end
95
+
96
+ # Read the error message for the request
97
+ #
98
+ # @param [String] The raw body of the request
99
+ # @return [String] The formatted error message
100
+ # @api private
101
+ def error
102
+ "#{@body['errorNum']}: #{@body["errorMessage"]}"
103
+ end
104
+ end
105
+
106
+ Faraday.register_middleware :response, error_response: -> { ErrorResponse }
107
+ end
108
+ end
@@ -53,7 +53,10 @@ module Ashikawa
53
53
  # index = Ashikawa::Core::Index.new(collection, raw_index)
54
54
  def initialize(collection, raw_index)
55
55
  @collection = collection
56
- parse_raw_index(raw_index)
56
+ @id = raw_index['id']
57
+ @on = convert_to_symbols(raw_index['fields'])
58
+ @type = raw_index['type'].to_sym
59
+ @unique = raw_index['unique']
57
60
  end
58
61
 
59
62
  # Remove the index from the collection
@@ -69,19 +72,6 @@ module Ashikawa
69
72
 
70
73
  private
71
74
 
72
- # Parse information returned from the server
73
- #
74
- # @param [Hash] raw_index
75
- # @return self
76
- # @api private
77
- def parse_raw_index(raw_index)
78
- @id = raw_index['id']
79
- @on = convert_to_symbols(raw_index['fields']) if raw_index.key?('fields')
80
- @type = raw_index['type'].to_sym if raw_index.key?('type')
81
- @unique = raw_index['unique']
82
- self
83
- end
84
-
85
75
  # Convert all elements of an array to symbols
86
76
  #
87
77
  # @param [Array] arr
@@ -63,7 +63,7 @@ module Ashikawa
63
63
  # @api public
64
64
  # @example Find all documents in a collection that are red
65
65
  # query = Ashikawa::Core::Query.new(collection)
66
- # query.by_example({ 'color' => 'red' }, :options => { :limit => 1 }) #=> #<Cursor id=2444>
66
+ # query.by_example({ 'color' => 'red' }, { :limit => 1 }) #=> #<Cursor id=2444>
67
67
  def by_example(example = {}, options = {})
68
68
  simple_query_request('simple/by-example', { example: example }.merge(options))
69
69
  end
@@ -77,7 +77,7 @@ module Ashikawa
77
77
  @database = database
78
78
  @request_parameters = {
79
79
  action: action,
80
- collections: parse_options(options),
80
+ collections: options,
81
81
  waitForSync: false
82
82
  }
83
83
  end
@@ -94,21 +94,6 @@ module Ashikawa
94
94
  response = @database.send_request('transaction', post: @request_parameters)
95
95
  response['result']
96
96
  end
97
-
98
- private
99
-
100
- # Parse the read and write collections from the options
101
- #
102
- # @option options [Array<String>] :write The collections you want to write to
103
- # @option options [Array<String>] :read The collections you want to read from
104
- # @return [Hash]
105
- # @api private
106
- def parse_options(options)
107
- collections = {}
108
- collections[:write] = options[:write] if options.key? :write
109
- collections[:read] = options[:read] if options.key? :read
110
- collections
111
- end
112
97
  end
113
98
  end
114
99
  end
@@ -2,6 +2,6 @@
2
2
  module Ashikawa
3
3
  module Core
4
4
  # Current version of Ashikawa::Core
5
- VERSION = '0.9.0'
5
+ VERSION = '0.10.0'
6
6
  end
7
7
  end
@@ -146,6 +146,13 @@ describe 'Basics' do
146
146
  expect(e.to_id).to eq(b.id)
147
147
  end
148
148
 
149
+ it 'should be possible to get a document by either its key or its ID' do
150
+ collection = subject['documenttests']
151
+ document = collection.create_document(name: 'The Dude')
152
+
153
+ expect(collection.fetch(document.key)).to eq collection.fetch(document.id)
154
+ end
155
+
149
156
  it 'should be possible to get a single attribute by AQL query' do
150
157
  collection = subject['documenttests']
151
158
  collection.truncate!
@@ -171,15 +178,11 @@ describe 'Basics' do
171
178
 
172
179
  it 'should be possible to delete a document' do
173
180
  collection.fetch(document_key).delete
174
- expect {
175
- collection.fetch(document_key)
176
- }.to raise_exception Ashikawa::Core::DocumentNotFoundException
181
+ expect { collection.fetch(document_key) }.to raise_exception Ashikawa::Core::DocumentNotFoundException
177
182
  end
178
183
 
179
184
  it "should not be possible to delete a document that doesn't exist" do
180
- expect {
181
- collection.fetch(123).delete
182
- }.to raise_exception Ashikawa::Core::DocumentNotFoundException
185
+ expect { collection.fetch(123).delete }.to raise_exception Ashikawa::Core::DocumentNotFoundException
183
186
  end
184
187
 
185
188
  it 'should be possible to refresh a document' do