ashikawa-core 0.11.0 → 0.12.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -1
- data/CHANGELOG.md +18 -0
- data/Gemfile +1 -3
- data/README.md +7 -3
- data/Rakefile +1 -0
- data/ashikawa-core.gemspec +9 -11
- data/config/reek.yml +5 -5
- data/lib/ashikawa-core/collection.rb +4 -0
- data/lib/ashikawa-core/configuration.rb +13 -1
- data/lib/ashikawa-core/connection.rb +34 -52
- data/lib/ashikawa-core/database.rb +9 -5
- data/lib/ashikawa-core/document.rb +6 -6
- data/lib/ashikawa-core/faraday_factory.rb +89 -0
- data/lib/ashikawa-core/minimal_logger.rb +64 -0
- data/lib/ashikawa-core/version.rb +1 -1
- data/log/.gitkeep +0 -0
- data/spec/acceptance/basic_spec.rb +2 -2
- data/spec/acceptance/index_spec.rb +5 -0
- data/spec/acceptance/spec_helper.rb +37 -22
- data/spec/unit/collection_spec.rb +34 -33
- data/spec/unit/configuration_spec.rb +8 -8
- data/spec/unit/connection_spec.rb +28 -48
- data/spec/unit/cursor_spec.rb +5 -4
- data/spec/unit/database_spec.rb +8 -16
- data/spec/unit/document_spec.rb +11 -10
- data/spec/unit/edge_spec.rb +12 -11
- data/spec/unit/exception_spec.rb +2 -2
- data/spec/unit/faraday_factory_spec.rb +51 -0
- data/spec/unit/figure_spec.rb +11 -11
- data/spec/unit/index_spec.rb +7 -6
- data/spec/unit/key_options_spec.rb +12 -15
- data/spec/unit/minimal_logger_spec.rb +55 -0
- data/spec/unit/query_spec.rb +53 -53
- data/spec/unit/spec_helper.rb +1 -0
- data/spec/unit/transaction_spec.rb +12 -14
- metadata +44 -53
- data/spec/fixtures/collections/60768679-figures.json +0 -35
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
require 'ashikawa-core/minimal_logger'
|
4
|
+
|
5
|
+
module Ashikawa
|
6
|
+
module Core
|
7
|
+
# Create Faraday objects
|
8
|
+
class FaradayFactory
|
9
|
+
# Defaults for the options of create connection
|
10
|
+
DEFAULTS = {
|
11
|
+
additional_request_middlewares: [],
|
12
|
+
additional_response_middlewares: [],
|
13
|
+
additional_middlewares: [],
|
14
|
+
adapter: Faraday.default_adapter
|
15
|
+
}
|
16
|
+
|
17
|
+
# Request middlewares that will be prepended
|
18
|
+
DEFAULT_REQUEST_MIDDLEWARES = [:json]
|
19
|
+
|
20
|
+
# Response middlewares that will be prepended
|
21
|
+
DEFAULT_RESPONSE_MIDDLEWARES = [:error_response, :json]
|
22
|
+
|
23
|
+
# Create a Faraday object
|
24
|
+
#
|
25
|
+
# @param [String] url The complete URL of the ArangoDB instance
|
26
|
+
# @option options [Object] adapter The Faraday adapter you want to use. Defaults to Default Adapter
|
27
|
+
# @option options [Object] logger The logger you want to use. Defaults to no logger.
|
28
|
+
# @option options [Array] additional_request_middlewares Additional request middlewares
|
29
|
+
# @option options [Array] additional_response_middlewares Additional response middlewares
|
30
|
+
# @option options [Array] additional_middlewares Additional middlewares
|
31
|
+
# @api private
|
32
|
+
# @example Create a FaradayObject with the given configuration
|
33
|
+
# faraday = FaradayFactory.new('http://localhost:8529/_db/mydb/_api', logger: my_logger)
|
34
|
+
def self.create_connection(url, options)
|
35
|
+
options = DEFAULTS.merge(options)
|
36
|
+
faraday = new(
|
37
|
+
options.fetch(:adapter),
|
38
|
+
options.fetch(:additional_request_middlewares),
|
39
|
+
options.fetch(:additional_response_middlewares),
|
40
|
+
options.fetch(:additional_middlewares)
|
41
|
+
)
|
42
|
+
faraday.debug_headers = options.fetch(:debug_headers) { false }
|
43
|
+
faraday.logger = options.fetch(:logger) if options.key?(:logger)
|
44
|
+
faraday.faraday_for(url)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Debug headers to be used by Faraday
|
48
|
+
#
|
49
|
+
# @api private
|
50
|
+
attr_accessor :debug_headers
|
51
|
+
|
52
|
+
# Create a new Faraday Factory with additional middlewares
|
53
|
+
#
|
54
|
+
# @param [Adapter] adapter The adapter to use
|
55
|
+
# @param [Array] additional_request_middlewares Additional request middlewares
|
56
|
+
# @param [Array] additional_response_middlewares Additional response middlewares
|
57
|
+
# @param [Array] additional_middlewares Additional middlewares
|
58
|
+
# @api private
|
59
|
+
def initialize(adapter, additional_request_middlewares, additional_response_middlewares, additional_middlewares)
|
60
|
+
@adapter = adapter
|
61
|
+
@request_middlewares = DEFAULT_REQUEST_MIDDLEWARES + additional_request_middlewares
|
62
|
+
@response_middlewares = DEFAULT_RESPONSE_MIDDLEWARES + additional_response_middlewares
|
63
|
+
@additional_middlewares = additional_middlewares
|
64
|
+
end
|
65
|
+
|
66
|
+
# Logger to be used by Faraday
|
67
|
+
#
|
68
|
+
# @param [Logger] logger The logger you want to use
|
69
|
+
# @api private
|
70
|
+
def logger=(logger)
|
71
|
+
@response_middlewares << [:minimal_logger, logger, debug_headers: debug_headers]
|
72
|
+
end
|
73
|
+
|
74
|
+
# Create the Faraday for the given URL
|
75
|
+
#
|
76
|
+
# @param [String] url
|
77
|
+
# @return [Faraday]
|
78
|
+
# @api private
|
79
|
+
def faraday_for(url)
|
80
|
+
Faraday.new(url) do |connection|
|
81
|
+
@request_middlewares.each { |middleware| connection.request(*middleware) }
|
82
|
+
@response_middlewares.each { |middleware| connection.response(*middleware) }
|
83
|
+
@additional_middlewares.each { |middleware| connection.use(*middleware) }
|
84
|
+
connection.adapter(*@adapter)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'faraday_middleware'
|
5
|
+
|
6
|
+
module Ashikawa
|
7
|
+
module Core
|
8
|
+
# A more minimal logger as replacement for the very chatty Faraday logger
|
9
|
+
class MinimalLogger < ::Faraday::Response::Middleware
|
10
|
+
# The logger to be used
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
# @return [Logger] The configured logger
|
14
|
+
attr_reader :logger
|
15
|
+
|
16
|
+
# Should HTTP headers be logged
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
# @return [Boolean] If the headers will be logged or not. Defaults to `false`
|
20
|
+
attr_reader :debug_headers
|
21
|
+
|
22
|
+
# Initialize the middleware
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
# @param [Faraday::Middleware] app The middleware to nest this one in
|
26
|
+
# @param [Logger] logger The logger to be used
|
27
|
+
# @option options [Boolean] :debug_headers Should the headers be logged. Defaults to `false`
|
28
|
+
def initialize(app, logger, options = {})
|
29
|
+
super(app)
|
30
|
+
@logger = logger
|
31
|
+
@debug_headers = options.fetch(:debug_headers) { false }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Calls the this middleware and passes on to `super`
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
# @param [Faraday::Env] env The current env object
|
38
|
+
def call(env)
|
39
|
+
logger.debug('request') { "#{env.method.upcase} #{env.url}#{dump_headers(env.request_headers)}" }
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
# The callback when the request was completed
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
# @param [Faraday::Env] env The current env object
|
47
|
+
def on_complete(env)
|
48
|
+
logger.debug('response') { "#{env.method.upcase} #{env.url} #{env.status}#{dump_headers(env.response_headers)}" }
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Creates a one-liner out of headers
|
54
|
+
#
|
55
|
+
# @api private
|
56
|
+
# @params [Hash] headers A headers hash
|
57
|
+
def dump_headers(headers)
|
58
|
+
" #{headers.map { |field_name, field_value| "#{field_name}: #{field_value.inspect}" }.join(' ')}" if debug_headers
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
::Faraday::Response.register_middleware minimal_logger: -> { MinimalLogger }
|
63
|
+
end
|
64
|
+
end
|
data/log/.gitkeep
ADDED
File without changes
|
@@ -18,7 +18,7 @@ describe 'Basics' do
|
|
18
18
|
it 'should have one more database if a database with random name is created' do
|
19
19
|
expect {
|
20
20
|
database_with_random_name.create
|
21
|
-
}.to change {
|
21
|
+
}.to change { SYSTEM_DATABASE.all_databases.length }.by(1)
|
22
22
|
end
|
23
23
|
|
24
24
|
it 'should create and delete collections' do
|
@@ -197,7 +197,7 @@ describe 'Basics' do
|
|
197
197
|
changed_document.save
|
198
198
|
|
199
199
|
expect(subject['name']).to eq('The Dude')
|
200
|
-
subject.refresh
|
200
|
+
subject.refresh
|
201
201
|
expect(subject['name']).to eq('New Name')
|
202
202
|
end
|
203
203
|
end
|
@@ -6,6 +6,11 @@ describe 'Indices' do
|
|
6
6
|
subject { database['documenttest'] }
|
7
7
|
let(:index) { subject.add_index(:skiplist, on: [:identifier]) }
|
8
8
|
|
9
|
+
it 'should accept a single attribute' do
|
10
|
+
single_attr_index = subject.add_index(:hash, on: :identifier)
|
11
|
+
expect(single_attr_index.on).to eq [:identifier]
|
12
|
+
end
|
13
|
+
|
9
14
|
it 'should be possible to set indices' do
|
10
15
|
index.delete
|
11
16
|
|
@@ -2,44 +2,59 @@
|
|
2
2
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
3
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
4
|
|
5
|
-
RSpec.configure do |config|
|
6
|
-
config.expect_with :rspec do |c|
|
7
|
-
c.syntax = :expect
|
8
|
-
end
|
9
|
-
|
10
|
-
config.mock_with :rspec do |c|
|
11
|
-
c.syntax = :expect
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
5
|
require 'ashikawa-core'
|
6
|
+
require 'logging'
|
16
7
|
|
17
8
|
PORT = ENV.fetch('ARANGODB_PORT', 8529)
|
18
9
|
USERNAME = ENV.fetch('ARANGODB_USERNAME', 'root')
|
19
10
|
PASSWORD = ENV.fetch('ARANGODB_PASSWORD', '')
|
20
11
|
AUTHENTIFICATION_ENABLED = ENV['ARANGODB_DISABLE_AUTHENTIFICATION'] == 'false'
|
21
12
|
|
22
|
-
|
23
|
-
|
24
|
-
|
13
|
+
def database_with_name(database_name = "_system")
|
14
|
+
Ashikawa::Core::Database.new do |config|
|
15
|
+
config.url = "http://localhost:#{PORT}"
|
16
|
+
# Log to a file
|
17
|
+
logger = Logging.logger['ashikawa-logger']
|
18
|
+
logger.add_appenders(
|
19
|
+
Logging.appenders.file('log/acceptance.log')
|
20
|
+
)
|
21
|
+
logger.level = :debug
|
22
|
+
|
23
|
+
config.logger = logger
|
24
|
+
config.database_name = database_name
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
if AUTHENTIFICATION_ENABLED
|
27
|
+
config.username = USERNAME
|
28
|
+
config.password = PASSWORD
|
29
|
+
end
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
32
33
|
def database_with_random_name
|
33
34
|
# This results in a database that has a valid name according to:
|
34
35
|
# https://www.arangodb.org/manuals/2/NamingConventions.html#DatabaseNames
|
35
|
-
|
36
|
+
database_with_name("a#{rand.to_s[2, 10]}")
|
37
|
+
end
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
+
# The database for the general specs
|
40
|
+
DATABASE = database_with_name('ashikawa-acceptance-specs')
|
41
|
+
# Some specs require access to the _system database
|
42
|
+
SYSTEM_DATABASE = database_with_name('_system')
|
39
43
|
|
40
|
-
|
41
|
-
|
42
|
-
|
44
|
+
RSpec.configure do |config|
|
45
|
+
config.expect_with :rspec do |c|
|
46
|
+
c.syntax = :expect
|
47
|
+
end
|
48
|
+
|
49
|
+
config.mock_with :rspec do |c|
|
50
|
+
c.syntax = :expect
|
51
|
+
end
|
52
|
+
|
53
|
+
config.before(:each) do
|
54
|
+
begin
|
55
|
+
DATABASE.create
|
56
|
+
rescue Ashikawa::Core::ClientError
|
43
57
|
end
|
58
|
+
DATABASE.truncate
|
44
59
|
end
|
45
60
|
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
require 'unit/spec_helper'
|
3
3
|
require 'ashikawa-core/collection'
|
4
|
+
require 'ashikawa-core/database'
|
4
5
|
|
5
6
|
describe Ashikawa::Core::Collection do
|
6
|
-
let(:database) {
|
7
|
+
let(:database) { instance_double('Ashikawa::Core::Database') }
|
7
8
|
let(:raw_document_collection) do
|
8
9
|
{
|
9
10
|
'id' => '60768679',
|
@@ -25,11 +26,10 @@ describe Ashikawa::Core::Collection do
|
|
25
26
|
describe 'an initialized collection' do
|
26
27
|
subject { Ashikawa::Core::Collection.new(database, raw_document_collection) }
|
27
28
|
|
28
|
-
let(:
|
29
|
-
let(:
|
30
|
-
let(:
|
31
|
-
let(:
|
32
|
-
let(:figure) { double }
|
29
|
+
let(:key_options) { instance_double('Ashikawa::Core::KeyOptions') }
|
30
|
+
let(:response) { instance_double('Hash') }
|
31
|
+
let(:value) { double('Value') }
|
32
|
+
let(:figure) { instance_double('Ashikawa::Core::Figure') }
|
33
33
|
|
34
34
|
its(:name) { should eq('example_1') }
|
35
35
|
its(:id) { should eq('60768679') }
|
@@ -164,7 +164,7 @@ describe Ashikawa::Core::Collection do
|
|
164
164
|
end
|
165
165
|
|
166
166
|
describe 'indexes' do
|
167
|
-
let(:index_response) {
|
167
|
+
let(:index_response) { instance_double('Hash') }
|
168
168
|
|
169
169
|
it 'should add a new index' do
|
170
170
|
expect(database).to receive(:send_request)
|
@@ -190,6 +190,18 @@ describe Ashikawa::Core::Collection do
|
|
190
190
|
subject.add_index(:hash, on: [:a, :b], unique: true)
|
191
191
|
end
|
192
192
|
|
193
|
+
it 'should accept a single attribute for indexing' do
|
194
|
+
expect(database).to receive(:send_request)
|
195
|
+
.with('index?collection=60768679', post: {
|
196
|
+
'type' => 'hash', 'fields' => %w(a), 'unique' => true
|
197
|
+
})
|
198
|
+
.and_return(index_response)
|
199
|
+
expect(Ashikawa::Core::Index).to receive(:new)
|
200
|
+
.with(subject, index_response)
|
201
|
+
|
202
|
+
subject.add_index(:hash, on: :a, unique: true)
|
203
|
+
end
|
204
|
+
|
193
205
|
it 'should get an index by ID' do
|
194
206
|
allow(database).to receive(:send_request)
|
195
207
|
.with('index/example_1/168054969')
|
@@ -201,18 +213,9 @@ describe Ashikawa::Core::Collection do
|
|
201
213
|
end
|
202
214
|
|
203
215
|
it 'should get all indexes' do
|
204
|
-
multi_index_response = double
|
205
|
-
allow(multi_index_response).to receive(:map)
|
206
|
-
.and_yield(index_response)
|
207
|
-
|
208
|
-
cursor = double
|
209
|
-
allow(cursor).to receive(:[])
|
210
|
-
.with('indexes')
|
211
|
-
.and_return(multi_index_response)
|
212
|
-
|
213
216
|
allow(database).to receive(:send_request)
|
214
217
|
.with('index?collection=60768679')
|
215
|
-
.and_return(
|
218
|
+
.and_return('indexes' => [index_response])
|
216
219
|
|
217
220
|
expect(Ashikawa::Core::Index).to receive(:new)
|
218
221
|
.with(subject, index_response)
|
@@ -223,12 +226,12 @@ describe Ashikawa::Core::Collection do
|
|
223
226
|
end
|
224
227
|
|
225
228
|
describe 'an initialized document collection' do
|
226
|
-
subject { Ashikawa::Core::Collection.new
|
229
|
+
subject { Ashikawa::Core::Collection.new(database, raw_document_collection) }
|
227
230
|
|
228
|
-
let(:document) {
|
229
|
-
let(:response) { double }
|
230
|
-
let(:raw_document) { double }
|
231
|
-
let(:value) { double }
|
231
|
+
let(:document) { instance_double('Ashikawa::Core::Document') }
|
232
|
+
let(:response) { double('Response') }
|
233
|
+
let(:raw_document) { double('RawDocument') }
|
234
|
+
let(:value) { double('Value') }
|
232
235
|
|
233
236
|
its(:content_type) { should be(:document) }
|
234
237
|
|
@@ -238,7 +241,6 @@ describe Ashikawa::Core::Collection do
|
|
238
241
|
it 'should receive a document by ID via fetch' do
|
239
242
|
expect(database).to receive(:send_request)
|
240
243
|
.with('document/60768679/333', {})
|
241
|
-
.and_return(double)
|
242
244
|
expect(Ashikawa::Core::Document).to receive(:new)
|
243
245
|
|
244
246
|
subject.fetch(key)
|
@@ -247,7 +249,6 @@ describe Ashikawa::Core::Collection do
|
|
247
249
|
it 'should receive a document by ID via []' do
|
248
250
|
expect(database).to receive(:send_request)
|
249
251
|
.with('document/60768679/333', {})
|
250
|
-
.and_return(double)
|
251
252
|
expect(Ashikawa::Core::Document).to receive(:new)
|
252
253
|
|
253
254
|
subject[key]
|
@@ -267,7 +268,6 @@ describe Ashikawa::Core::Collection do
|
|
267
268
|
it 'should receive a document by ID via fetch' do
|
268
269
|
expect(database).to receive(:send_request)
|
269
270
|
.with('document/60768679/333', {})
|
270
|
-
.and_return(double)
|
271
271
|
expect(Ashikawa::Core::Document).to receive(:new)
|
272
272
|
|
273
273
|
subject.fetch(id)
|
@@ -276,7 +276,6 @@ describe Ashikawa::Core::Collection do
|
|
276
276
|
it 'should receive a document by ID via []' do
|
277
277
|
expect(database).to receive(:send_request)
|
278
278
|
.with('document/60768679/333', {})
|
279
|
-
.and_return(double)
|
280
279
|
expect(Ashikawa::Core::Document).to receive(:new)
|
281
280
|
|
282
281
|
subject[id]
|
@@ -302,8 +301,10 @@ describe Ashikawa::Core::Collection do
|
|
302
301
|
end
|
303
302
|
|
304
303
|
it 'should not create a new edge' do
|
304
|
+
from = instance_double('Ashikawa::Core::Document')
|
305
|
+
to = instance_double('Ashikawa::Core::Document')
|
305
306
|
expect do
|
306
|
-
subject.create_edge(
|
307
|
+
subject.create_edge(from, to, { 'quote' => "D'ya have to use s'many cuss words?" })
|
307
308
|
end.to raise_exception(RuntimeError, "Can't create an edge in a document collection")
|
308
309
|
end
|
309
310
|
end
|
@@ -311,16 +312,15 @@ describe Ashikawa::Core::Collection do
|
|
311
312
|
describe 'an initialized edge collection' do
|
312
313
|
subject { Ashikawa::Core::Collection.new database, raw_edge_collection }
|
313
314
|
|
314
|
-
let(:document) {
|
315
|
-
let(:response) { double }
|
316
|
-
let(:raw_document) { double }
|
315
|
+
let(:document) { instance_double('Ashikawa::Core::Document') }
|
316
|
+
let(:response) { double('Response') }
|
317
|
+
let(:raw_document) { double('RawDocument') }
|
317
318
|
|
318
319
|
its(:content_type) { should be(:edge) }
|
319
320
|
|
320
321
|
it 'should receive an edge by ID via fetch' do
|
321
322
|
expect(database).to receive(:send_request)
|
322
323
|
.with('edge/60768679/333', {})
|
323
|
-
.and_return(double)
|
324
324
|
expect(Ashikawa::Core::Edge).to receive(:new)
|
325
325
|
|
326
326
|
subject.fetch(333)
|
@@ -329,7 +329,6 @@ describe Ashikawa::Core::Collection do
|
|
329
329
|
it 'should receive an edge by ID via []' do
|
330
330
|
expect(database).to receive(:send_request)
|
331
331
|
.with('edge/60768679/333', {})
|
332
|
-
.and_return(double)
|
333
332
|
expect(Ashikawa::Core::Edge).to receive(:new)
|
334
333
|
|
335
334
|
subject[333]
|
@@ -350,7 +349,9 @@ describe Ashikawa::Core::Collection do
|
|
350
349
|
.with(database, response, raw_document)
|
351
350
|
.and_return(document)
|
352
351
|
|
353
|
-
|
352
|
+
from = instance_double('Ashikawa::Core::Document', id: 1)
|
353
|
+
to = instance_double('Ashikawa::Core::Document', id: 2)
|
354
|
+
subject.create_edge(from, to, raw_document)
|
354
355
|
end
|
355
356
|
|
356
357
|
it 'should not create a new document' do
|