ashikawa-core 0.7.2 → 0.8.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 +7 -0
- data/.coveralls.yml +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +0 -4
- data/Gemfile +1 -1
- data/Gemfile.devtools +24 -18
- data/README.md +6 -11
- data/ashikawa-core.gemspec +7 -6
- data/config/flay.yml +2 -2
- data/config/flog.yml +2 -1
- data/config/reek.yml +68 -84
- data/config/rubocop.yml +99 -0
- data/config/yardstick.yml +1 -1
- data/lib/ashikawa-core/collection.rb +74 -21
- data/lib/ashikawa-core/configuration.rb +26 -0
- data/lib/ashikawa-core/connection.rb +5 -2
- data/lib/ashikawa-core/cursor.rb +28 -16
- data/lib/ashikawa-core/database.rb +89 -12
- data/lib/ashikawa-core/document.rb +32 -5
- data/lib/ashikawa-core/edge.rb +3 -0
- data/lib/ashikawa-core/exceptions/client_error.rb +3 -3
- data/lib/ashikawa-core/exceptions/server_error.rb +3 -3
- data/lib/ashikawa-core/figure.rb +17 -5
- data/lib/ashikawa-core/index.rb +4 -0
- data/lib/ashikawa-core/key_options.rb +54 -0
- data/lib/ashikawa-core/query.rb +39 -74
- data/lib/ashikawa-core/request_preprocessor.rb +2 -2
- data/lib/ashikawa-core/response_preprocessor.rb +21 -12
- data/lib/ashikawa-core/transaction.rb +113 -0
- data/lib/ashikawa-core/version.rb +1 -1
- data/spec/acceptance/basic_spec.rb +40 -12
- data/spec/acceptance/index_spec.rb +2 -1
- data/spec/acceptance/query_spec.rb +18 -17
- data/spec/acceptance/transactions_spec.rb +30 -0
- data/spec/fixtures/collections/all.json +90 -30
- data/spec/fixtures/cursor/edges.json +23 -0
- data/spec/setup/arangodb.sh +7 -6
- data/spec/unit/collection_spec.rb +89 -13
- data/spec/unit/connection_spec.rb +23 -14
- data/spec/unit/cursor_spec.rb +15 -4
- data/spec/unit/database_spec.rb +58 -17
- data/spec/unit/document_spec.rb +24 -4
- data/spec/unit/edge_spec.rb +1 -1
- data/spec/unit/exception_spec.rb +4 -2
- data/spec/unit/figure_spec.rb +17 -10
- data/spec/unit/index_spec.rb +1 -1
- data/spec/unit/key_options_spec.rb +25 -0
- data/spec/unit/query_spec.rb +1 -1
- data/spec/unit/spec_helper.rb +20 -2
- data/spec/unit/transaction_spec.rb +153 -0
- data/tasks/adjustments.rake +23 -14
- metadata +31 -41
- data/.rvmrc +0 -1
- data/config/roodi.yml +0 -17
- data/spec/spec_helper.rb +0 -27
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'ashikawa-core/exceptions/client_error/resource_not_found/document_not_found'
|
2
|
+
require 'equalizer'
|
2
3
|
|
3
4
|
module Ashikawa
|
4
5
|
module Core
|
5
6
|
# A certain Document within a certain Collection
|
6
7
|
class Document
|
8
|
+
include Equalizer.new(:id, :revision)
|
9
|
+
|
7
10
|
# The ID of the document (this includes the Collection prefix)
|
8
11
|
#
|
9
12
|
# @return [String]
|
@@ -52,7 +55,7 @@ module Ashikawa
|
|
52
55
|
# document = Ashikawa::Core::Document.new(database, raw_document)
|
53
56
|
# document.check_if_persisted!
|
54
57
|
def check_if_persisted!
|
55
|
-
raise DocumentNotFoundException if @id
|
58
|
+
raise DocumentNotFoundException if @id == :not_persisted
|
56
59
|
end
|
57
60
|
|
58
61
|
# Get the value of an attribute of the document
|
@@ -99,9 +102,22 @@ module Ashikawa
|
|
99
102
|
# @api public
|
100
103
|
# @example Get the hash representation of a document
|
101
104
|
# document = Ashikawa::Core::Document.new(database, raw_document)
|
105
|
+
# document.hash #=> { :name => "Lebowski", :occupation => "Not occupied" }
|
106
|
+
def hash
|
107
|
+
@content
|
108
|
+
end
|
109
|
+
|
110
|
+
# Convert the document into a hash
|
111
|
+
#
|
112
|
+
# @return [Hash]
|
113
|
+
# @api public
|
114
|
+
# @deprecated Use {#hash} instead.
|
115
|
+
# @example Get the hash representation of a document
|
116
|
+
# document = Ashikawa::Core::Document.new(database, raw_document)
|
102
117
|
# document.to_hash #=> { :name => "Lebowski", :occupation => "Not occupied" }
|
103
118
|
def to_hash
|
104
|
-
|
119
|
+
warn "`to_hash` is deprecated, please use `hash`"
|
120
|
+
hash
|
105
121
|
end
|
106
122
|
|
107
123
|
# Save the changes to the database
|
@@ -112,11 +128,22 @@ module Ashikawa
|
|
112
128
|
# document = Ashikawa::Core::Document.new(database, raw_document)
|
113
129
|
# document['occupation'] = 'Not occupied'
|
114
130
|
# document.save
|
115
|
-
def save
|
131
|
+
def save
|
116
132
|
check_if_persisted!
|
117
133
|
send_request_for_document(:put => @content)
|
118
134
|
end
|
119
135
|
|
136
|
+
# Get a fresh version of this document from the database
|
137
|
+
#
|
138
|
+
# @return self
|
139
|
+
# @api public
|
140
|
+
# @example Refresh the document
|
141
|
+
# document = Ashikawa::Core::Document.new(database, raw_document)
|
142
|
+
# document.refresh!
|
143
|
+
def refresh!
|
144
|
+
parse_raw_document(send_request_for_document)
|
145
|
+
end
|
146
|
+
|
120
147
|
protected
|
121
148
|
|
122
149
|
# Parse information returned from the server
|
@@ -125,9 +152,9 @@ module Ashikawa
|
|
125
152
|
# @return self
|
126
153
|
# @api private
|
127
154
|
def parse_raw_document(raw_document)
|
128
|
-
@id = raw_document['_id']
|
155
|
+
@id = raw_document['_id'] || :not_persisted
|
129
156
|
@key = raw_document['_key']
|
130
|
-
@revision = raw_document['_rev']
|
157
|
+
@revision = raw_document['_rev'] || :not_persisted
|
131
158
|
@content = raw_document.delete_if { |key, value| key.start_with?("_") }
|
132
159
|
self
|
133
160
|
end
|
data/lib/ashikawa-core/edge.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'ashikawa-core/document'
|
2
|
+
require 'equalizer'
|
2
3
|
|
3
4
|
module Ashikawa
|
4
5
|
module Core
|
5
6
|
# A certain Edge within a certain Collection
|
6
7
|
class Edge < Document
|
8
|
+
include Equalizer.new(:id, :revision, :from_id, :to_id)
|
9
|
+
|
7
10
|
# The ID of the 'from' document
|
8
11
|
#
|
9
12
|
# @return [String]
|
@@ -7,8 +7,8 @@ module Ashikawa
|
|
7
7
|
# @param [Integer] status
|
8
8
|
# @return RuntimeError
|
9
9
|
# @api private
|
10
|
-
def initialize(
|
11
|
-
@
|
10
|
+
def initialize(description)
|
11
|
+
@description = description
|
12
12
|
end
|
13
13
|
|
14
14
|
# String representation of the exception
|
@@ -16,7 +16,7 @@ module Ashikawa
|
|
16
16
|
# @return String
|
17
17
|
# @api private
|
18
18
|
def to_s
|
19
|
-
|
19
|
+
@description
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
@@ -7,8 +7,8 @@ module Ashikawa
|
|
7
7
|
# @param [Integer] status
|
8
8
|
# @return RuntimeError
|
9
9
|
# @api private
|
10
|
-
def initialize(
|
11
|
-
@
|
10
|
+
def initialize(description)
|
11
|
+
@description = description
|
12
12
|
end
|
13
13
|
|
14
14
|
# String representation of the exception
|
@@ -16,7 +16,7 @@ module Ashikawa
|
|
16
16
|
# @return String
|
17
17
|
# @api private
|
18
18
|
def to_s
|
19
|
-
|
19
|
+
@description
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
data/lib/ashikawa-core/figure.rb
CHANGED
@@ -9,11 +9,12 @@ module Ashikawa
|
|
9
9
|
# @example Create a new figure from a raw figure
|
10
10
|
# figure = Ashikawa::Core::Figure.new(raw_figure)
|
11
11
|
def initialize(raw_figure)
|
12
|
-
@datafiles
|
13
|
-
@alive
|
14
|
-
@dead
|
15
|
-
@shapes
|
16
|
-
@journals
|
12
|
+
@datafiles = raw_figure["datafiles"]
|
13
|
+
@alive = raw_figure["alive"]
|
14
|
+
@dead = raw_figure["dead"]
|
15
|
+
@shapes = raw_figure["shapes"]
|
16
|
+
@journals = raw_figure["journals"]
|
17
|
+
@attributes = raw_figure["attributes"]
|
17
18
|
end
|
18
19
|
|
19
20
|
# The number of active datafiles
|
@@ -125,6 +126,17 @@ module Ashikawa
|
|
125
126
|
def journals_file_size
|
126
127
|
@journals["fileSize"]
|
127
128
|
end
|
129
|
+
|
130
|
+
# Number of different attributes that are or have been used in the collection
|
131
|
+
#
|
132
|
+
# @return Fixnum
|
133
|
+
# @api public
|
134
|
+
# @example Get the number of attributes
|
135
|
+
# figure = Ashikawa::Core::Figure.new(raw_figure)
|
136
|
+
# figure.attributes_count #=> 12
|
137
|
+
def attributes_count
|
138
|
+
@attributes["count"]
|
139
|
+
end
|
128
140
|
end
|
129
141
|
end
|
130
142
|
end
|
data/lib/ashikawa-core/index.rb
CHANGED
@@ -0,0 +1,54 @@
|
|
1
|
+
module Ashikawa
|
2
|
+
module Core
|
3
|
+
# Options for controlling keys of a collection
|
4
|
+
class KeyOptions
|
5
|
+
# Either traditional or autoincrement
|
6
|
+
#
|
7
|
+
# @return Symbol
|
8
|
+
# @api public
|
9
|
+
# @example Get the type of the KeyOptions
|
10
|
+
# keyOptions = KeyOptions.new({ :type => :autoincrement })
|
11
|
+
# keyOptions.type # => :autoincrement
|
12
|
+
attr_reader :type
|
13
|
+
|
14
|
+
# A specific start value
|
15
|
+
#
|
16
|
+
# @return Integer
|
17
|
+
# @api public
|
18
|
+
# @example Get the type of the KeyOptions
|
19
|
+
# keyOptions = KeyOptions.new({ :offset => 12 })
|
20
|
+
# keyOptions.offset # => 12
|
21
|
+
attr_reader :offset
|
22
|
+
|
23
|
+
# Size of increment steps
|
24
|
+
#
|
25
|
+
# @return Integer
|
26
|
+
# @api public
|
27
|
+
# @example Get the type of the KeyOptions
|
28
|
+
# keyOptions = KeyOptions.new({ :increment => 12 })
|
29
|
+
# keyOptions.increment # => 12
|
30
|
+
attr_reader :increment
|
31
|
+
|
32
|
+
# Is the user allowed to set keys by him- or herself?
|
33
|
+
#
|
34
|
+
# @return Boolean
|
35
|
+
# @api public
|
36
|
+
# @example Get the type of the KeyOptions
|
37
|
+
# keyOptions = KeyOptions.new({ :allowUserKeys => true })
|
38
|
+
# keyOptions.allow_user_keys # => true
|
39
|
+
attr_reader :allow_user_keys
|
40
|
+
|
41
|
+
# Create a new KeyOptions object from the raw key options
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
# @example Create a new KeyOptions object
|
45
|
+
# KeyOptions.new({ :type => :autoincrement })
|
46
|
+
def initialize(raw_key_options)
|
47
|
+
@type = raw_key_options["type"]
|
48
|
+
@offset = raw_key_options["offset"]
|
49
|
+
@increment = raw_key_options["increment"]
|
50
|
+
@allow_user_keys = raw_key_options["allowUserKeys"]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/ashikawa-core/query.rb
CHANGED
@@ -3,7 +3,6 @@ require 'ashikawa-core/document'
|
|
3
3
|
require 'ashikawa-core/exceptions/no_collection_provided'
|
4
4
|
require 'ashikawa-core/exceptions/client_error/bad_syntax'
|
5
5
|
require 'forwardable'
|
6
|
-
require 'backports'
|
7
6
|
|
8
7
|
module Ashikawa
|
9
8
|
module Core
|
@@ -11,6 +10,17 @@ module Ashikawa
|
|
11
10
|
class Query
|
12
11
|
extend Forwardable
|
13
12
|
|
13
|
+
ALLOWED_KEYS_FOR_PATH = {
|
14
|
+
"simple/all" => [:limit, :skip, :collection],
|
15
|
+
"simple/by-example" => [:limit, :skip, :example, :collection],
|
16
|
+
"simple/near" => [:latitude, :longitude, :distance, :skip, :limit, :geo, :collection],
|
17
|
+
"simple/within" => [:latitude, :longitude, :radius, :distance, :skip, :limit, :geo, :collection],
|
18
|
+
"simple/range" => [:attribute, :left, :right, :closed, :limit, :skip, :collection],
|
19
|
+
"cursor" => [:query, :count, :batch_size, :collection],
|
20
|
+
"query" => [:query],
|
21
|
+
"simple/first-example" => [:example, :collection]
|
22
|
+
}
|
23
|
+
|
14
24
|
# Delegate sending requests to the connection
|
15
25
|
def_delegator :@connection, :send_request
|
16
26
|
|
@@ -37,10 +47,8 @@ module Ashikawa
|
|
37
47
|
# @example Get an array with all documents
|
38
48
|
# query = Ashikawa::Core::Query.new(collection)
|
39
49
|
# query.all # => #<Cursor id=33>
|
40
|
-
def all(options={})
|
41
|
-
simple_query_request("simple/all",
|
42
|
-
options,
|
43
|
-
[:limit, :skip])
|
50
|
+
def all(options = {})
|
51
|
+
simple_query_request("simple/all", options)
|
44
52
|
end
|
45
53
|
|
46
54
|
# Looks for documents in a collection which match the given criteria
|
@@ -55,10 +63,8 @@ module Ashikawa
|
|
55
63
|
# @example Find all documents in a collection that are red
|
56
64
|
# query = Ashikawa::Core::Query.new(collection)
|
57
65
|
# query.by_example({ "color" => "red" }, :options => { :limit => 1 }) #=> #<Cursor id=2444>
|
58
|
-
def by_example(example={}, options={})
|
59
|
-
simple_query_request("simple/by-example",
|
60
|
-
{ :example => example }.merge(options),
|
61
|
-
[:limit, :skip, :example])
|
66
|
+
def by_example(example = {}, options = {})
|
67
|
+
simple_query_request("simple/by-example", { :example => example }.merge(options))
|
62
68
|
end
|
63
69
|
|
64
70
|
# Looks for one document in a collection which matches the given criteria
|
@@ -71,9 +77,9 @@ module Ashikawa
|
|
71
77
|
# query = Ashikawa::Core::Query.new(collection)
|
72
78
|
# query.first_example({ "color" => "red"}) # => #<Document id=2444 color="red">
|
73
79
|
def first_example(example = {})
|
74
|
-
|
75
|
-
|
76
|
-
|
80
|
+
request = prepare_request("simple/first-example", { :example => example, :collection => collection.name })
|
81
|
+
response = send_request("simple/first-example", { :put => request })
|
82
|
+
Document.new(database, response["document"])
|
77
83
|
end
|
78
84
|
|
79
85
|
# Looks for documents in a collection based on location
|
@@ -90,10 +96,8 @@ module Ashikawa
|
|
90
96
|
# @example Find all documents at Infinite Loop
|
91
97
|
# query = Ashikawa::Core::Query.new(collection)
|
92
98
|
# query.near(:latitude => 37.331693, :longitude => -122.030468)
|
93
|
-
def near(options={})
|
94
|
-
simple_query_request("simple/near",
|
95
|
-
options,
|
96
|
-
[:latitude, :longitude, :distance, :skip, :limit, :geo])
|
99
|
+
def near(options = {})
|
100
|
+
simple_query_request("simple/near", options)
|
97
101
|
end
|
98
102
|
|
99
103
|
# Looks for documents in a collection within a radius
|
@@ -111,10 +115,8 @@ module Ashikawa
|
|
111
115
|
# @example Find all documents within a radius of 100 to Infinite Loop
|
112
116
|
# query = Ashikawa::Core::Query.new(collection)
|
113
117
|
# query.within(:latitude => 37.331693, :longitude => -122.030468, :radius => 100)
|
114
|
-
def within(options={})
|
115
|
-
simple_query_request("simple/within",
|
116
|
-
options,
|
117
|
-
[:latitude, :longitude, :radius, :distance, :skip, :limit, :geo])
|
118
|
+
def within(options = {})
|
119
|
+
simple_query_request("simple/within", options)
|
118
120
|
end
|
119
121
|
|
120
122
|
# Looks for documents in a collection with an attribute between two values
|
@@ -131,10 +133,8 @@ module Ashikawa
|
|
131
133
|
# @example Find all documents within a radius of 100 to Infinite Loop
|
132
134
|
# query = Ashikawa::Core::Query.new(collection)
|
133
135
|
# query.within(:latitude => 37.331693, :longitude => -122.030468, :radius => 100)
|
134
|
-
def in_range(options={})
|
135
|
-
simple_query_request("simple/range",
|
136
|
-
options,
|
137
|
-
[:attribute, :left, :right, :closed, :limit, :skip])
|
136
|
+
def in_range(options = {})
|
137
|
+
simple_query_request("simple/range", options)
|
138
138
|
end
|
139
139
|
|
140
140
|
# Send an AQL query to the database
|
@@ -148,9 +148,7 @@ module Ashikawa
|
|
148
148
|
# query = Ashikawa::Core::Query.new(collection)
|
149
149
|
# query.execute("FOR u IN users LIMIT 2") # => #<Cursor id=33>
|
150
150
|
def execute(query, options = {})
|
151
|
-
|
152
|
-
options.merge({ :query => query }),
|
153
|
-
[:query, :count, :batch_size])
|
151
|
+
wrapped_request("cursor", :post, options.merge({ :query => query }))
|
154
152
|
end
|
155
153
|
|
156
154
|
# Test if an AQL query is valid
|
@@ -162,7 +160,7 @@ module Ashikawa
|
|
162
160
|
# query = Ashikawa::Core::Query.new(collection)
|
163
161
|
# query.valid?("FOR u IN users LIMIT 2") # => true
|
164
162
|
def valid?(query)
|
165
|
-
!!
|
163
|
+
!!wrapped_request("query", :post, { :query => query })
|
166
164
|
rescue Ashikawa::Core::BadSyntax
|
167
165
|
false
|
168
166
|
end
|
@@ -192,71 +190,38 @@ module Ashikawa
|
|
192
190
|
# @param [Array<Symbol>] allowed_keys
|
193
191
|
# @return [Hash] The filtered Hash
|
194
192
|
# @api private
|
195
|
-
def
|
193
|
+
def prepare_request(path, options)
|
194
|
+
allowed_keys = ALLOWED_KEYS_FOR_PATH.fetch(path)
|
196
195
|
options.keep_if { |key, _| allowed_keys.include?(key) }
|
197
|
-
|
198
|
-
|
199
|
-
# Transforms the keys into the required format
|
200
|
-
#
|
201
|
-
# @param [Hash] request_data
|
202
|
-
# @return [Hash] Cleaned request data
|
203
|
-
# @api private
|
204
|
-
def prepare_request_data(request_data)
|
205
|
-
Hash[request_data.map { |key, value|
|
196
|
+
Hash[options.map { |key, value|
|
206
197
|
[key.to_s.gsub(/_(.)/) { $1.upcase }, value]
|
207
|
-
}]
|
198
|
+
}]
|
208
199
|
end
|
209
200
|
|
210
201
|
# Send a simple query to the server
|
211
202
|
#
|
212
203
|
# @param [String] path The path for the request
|
213
|
-
# @param [Hash]
|
204
|
+
# @param [Hash] request The data send to the database
|
214
205
|
# @param [Array<Symbol>] allowed_keys The keys allowed for this request
|
215
206
|
# @return [String] Server response
|
216
207
|
# @raise [NoCollectionProvidedException] If you provided a database, no collection
|
217
208
|
# @api private
|
218
|
-
def simple_query_request(path,
|
219
|
-
|
220
|
-
put_request(path,
|
221
|
-
request_data,
|
222
|
-
allowed_keys << :collection)
|
209
|
+
def simple_query_request(path, request)
|
210
|
+
wrapped_request(path, :put, request.merge({ :collection => collection.name }))
|
223
211
|
end
|
224
212
|
|
225
213
|
# Perform a wrapped request
|
226
214
|
#
|
227
215
|
# @param [String] path The path for the request
|
228
216
|
# @param [Symbol] request_method The request method to perform
|
229
|
-
# @param [Hash]
|
230
|
-
# @param [Array] allowed_keys Keys allowed in
|
231
|
-
# @return [Cursor]
|
232
|
-
# @api private
|
233
|
-
def wrapped_request(path, request_method, request_data, allowed_keys)
|
234
|
-
request_data = allowed_options(request_data, allowed_keys) unless allowed_keys.nil?
|
235
|
-
request_data = prepare_request_data(request_data)
|
236
|
-
server_response = send_request(path, { request_method => request_data })
|
237
|
-
Cursor.new(database, server_response)
|
238
|
-
end
|
239
|
-
|
240
|
-
# Perform a wrapped put request
|
241
|
-
#
|
242
|
-
# @param [String] path The path for the request
|
243
|
-
# @param [Hash] request_data The data send to the database
|
244
|
-
# @param [Array] allowed_keys Keys allowed in request_data, if nil: All keys are allowed
|
245
|
-
# @return [Cursor]
|
246
|
-
# @api private
|
247
|
-
def put_request(path, request_data, allowed_keys = nil)
|
248
|
-
wrapped_request(path, :put, request_data, allowed_keys)
|
249
|
-
end
|
250
|
-
|
251
|
-
# Perform a wrapped post request
|
252
|
-
#
|
253
|
-
# @param [String] path The path for the request
|
254
|
-
# @param [Hash] request_data The data send to the database
|
255
|
-
# @param [Array] allowed_keys Keys allowed in request_data, if nil: All keys are allowed
|
217
|
+
# @param [Hash] request The data send to the database
|
218
|
+
# @param [Array] allowed_keys Keys allowed in request, if nil: All keys are allowed
|
256
219
|
# @return [Cursor]
|
257
220
|
# @api private
|
258
|
-
def
|
259
|
-
|
221
|
+
def wrapped_request(path, request_method, request)
|
222
|
+
request = prepare_request(path, request)
|
223
|
+
response = send_request(path, { request_method => request })
|
224
|
+
Cursor.new(database, response)
|
260
225
|
end
|
261
226
|
end
|
262
227
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require "faraday"
|
2
|
-
require "
|
2
|
+
require "json"
|
3
3
|
|
4
4
|
module Ashikawa
|
5
5
|
module Core
|
@@ -23,7 +23,7 @@ module Ashikawa
|
|
23
23
|
# @api private
|
24
24
|
def call(env)
|
25
25
|
body = env[:body]
|
26
|
-
env[:body] =
|
26
|
+
env[:body] = JSON.generate(body) if body
|
27
27
|
log(env[:method], env[:url], body)
|
28
28
|
@app.call(env)
|
29
29
|
end
|