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