ashikawa-core 0.9.0 → 0.10.0

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