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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +0 -4
  5. data/Gemfile +1 -1
  6. data/Gemfile.devtools +24 -18
  7. data/README.md +6 -11
  8. data/ashikawa-core.gemspec +7 -6
  9. data/config/flay.yml +2 -2
  10. data/config/flog.yml +2 -1
  11. data/config/reek.yml +68 -84
  12. data/config/rubocop.yml +99 -0
  13. data/config/yardstick.yml +1 -1
  14. data/lib/ashikawa-core/collection.rb +74 -21
  15. data/lib/ashikawa-core/configuration.rb +26 -0
  16. data/lib/ashikawa-core/connection.rb +5 -2
  17. data/lib/ashikawa-core/cursor.rb +28 -16
  18. data/lib/ashikawa-core/database.rb +89 -12
  19. data/lib/ashikawa-core/document.rb +32 -5
  20. data/lib/ashikawa-core/edge.rb +3 -0
  21. data/lib/ashikawa-core/exceptions/client_error.rb +3 -3
  22. data/lib/ashikawa-core/exceptions/server_error.rb +3 -3
  23. data/lib/ashikawa-core/figure.rb +17 -5
  24. data/lib/ashikawa-core/index.rb +4 -0
  25. data/lib/ashikawa-core/key_options.rb +54 -0
  26. data/lib/ashikawa-core/query.rb +39 -74
  27. data/lib/ashikawa-core/request_preprocessor.rb +2 -2
  28. data/lib/ashikawa-core/response_preprocessor.rb +21 -12
  29. data/lib/ashikawa-core/transaction.rb +113 -0
  30. data/lib/ashikawa-core/version.rb +1 -1
  31. data/spec/acceptance/basic_spec.rb +40 -12
  32. data/spec/acceptance/index_spec.rb +2 -1
  33. data/spec/acceptance/query_spec.rb +18 -17
  34. data/spec/acceptance/transactions_spec.rb +30 -0
  35. data/spec/fixtures/collections/all.json +90 -30
  36. data/spec/fixtures/cursor/edges.json +23 -0
  37. data/spec/setup/arangodb.sh +7 -6
  38. data/spec/unit/collection_spec.rb +89 -13
  39. data/spec/unit/connection_spec.rb +23 -14
  40. data/spec/unit/cursor_spec.rb +15 -4
  41. data/spec/unit/database_spec.rb +58 -17
  42. data/spec/unit/document_spec.rb +24 -4
  43. data/spec/unit/edge_spec.rb +1 -1
  44. data/spec/unit/exception_spec.rb +4 -2
  45. data/spec/unit/figure_spec.rb +17 -10
  46. data/spec/unit/index_spec.rb +1 -1
  47. data/spec/unit/key_options_spec.rb +25 -0
  48. data/spec/unit/query_spec.rb +1 -1
  49. data/spec/unit/spec_helper.rb +20 -2
  50. data/spec/unit/transaction_spec.rb +153 -0
  51. data/tasks/adjustments.rake +23 -14
  52. metadata +31 -41
  53. data/.rvmrc +0 -1
  54. data/config/roodi.yml +0 -17
  55. data/spec/spec_helper.rb +0 -27
data/config/yardstick.yml CHANGED
@@ -1,2 +1,2 @@
1
1
  ---
2
- threshold: 98.2
2
+ threshold: 100
@@ -5,7 +5,9 @@ require "ashikawa-core/cursor"
5
5
  require "ashikawa-core/query"
6
6
  require "ashikawa-core/status"
7
7
  require "ashikawa-core/figure"
8
+ require "ashikawa-core/key_options"
8
9
  require "forwardable"
10
+ require "equalizer"
9
11
 
10
12
  module Ashikawa
11
13
  module Core
@@ -13,6 +15,8 @@ module Ashikawa
13
15
  class Collection
14
16
  extend Forwardable
15
17
 
18
+ include Equalizer.new(:id, :name, :content_type, :database)
19
+
16
20
  CONTENT_TYPES = {
17
21
  2 => :document,
18
22
  3 => :edge
@@ -205,6 +209,17 @@ module Ashikawa
205
209
  send_information_to_server(:properties, :waitForSync, new_value)
206
210
  end
207
211
 
212
+ # Get information about the type of keys of this collection
213
+ #
214
+ # @return [KeyOptions]
215
+ # @api public
216
+ # @example Check if this collection has autoincrementing keys
217
+ # collection = Ashikawa::Core::Collection.new(database, raw_collection)
218
+ # collection.key_options.type # => "autoincrement"
219
+ def key_options
220
+ KeyOptions.new(get_information_from_server(:properties, :keyOptions))
221
+ end
222
+
208
223
  # Returns the number of documents in the collection
209
224
  #
210
225
  # @return [Fixnum] Number of documents
@@ -326,29 +341,56 @@ module Ashikawa
326
341
  send_command_to_server(:truncate)
327
342
  end
328
343
 
329
- # Fetch a certain document by its ID
344
+ # Fetch a certain document by its key
330
345
  #
331
- # @param [Integer] document_id the id of the document
346
+ # @param [Integer] document_key the key of the document
332
347
  # @raise [DocumentNotFoundException] If the requested document was not found
333
348
  # @return Document
334
349
  # @api public
335
- # @example Fetch the document with the ID 12345
336
- # document = collection[12345]
337
- def [](document_id)
338
- response = send_request_for_content_id(document_id)
350
+ # @example Fetch the document with the key 12345
351
+ # document = collection.fetch(12345)
352
+ def fetch(document_key)
353
+ response = send_request_for_content_key(document_key)
339
354
  @content_class.new(@database, response)
340
355
  end
341
356
 
342
- # Replace a document by its ID
357
+ # Fetch a certain document by its key, return nil if the document does not exist
343
358
  #
344
- # @param [Integer] document_id the id of the document
359
+ # @param [Integer] document_key the id of the document
360
+ # @return Document
361
+ # @api public
362
+ # @example Fetch the document with the key 12345
363
+ # document = collection[12345]
364
+ def [](document_key)
365
+ fetch(document_key)
366
+ rescue DocumentNotFoundException
367
+ nil
368
+ end
369
+
370
+ # Replace a document by its key
371
+ #
372
+ # @param [Integer] document_key the key of the document
345
373
  # @param [Hash] raw_document the data you want to replace it with
346
374
  # @return [Hash] parsed JSON response from the server
347
375
  # @api public
348
- # @example Replace the document with the ID 12345
349
- # collection[12345] = document
350
- def []=(document_id, raw_document)
351
- send_request_for_content_id(document_id, :put => raw_document)
376
+ # @example Replace the document with the key 12345
377
+ # collection.replace(12345, document)
378
+ def replace(document_key, raw_document)
379
+ send_request_for_content_key(document_key, :put => raw_document)
380
+ end
381
+
382
+ # Replace a document by its key
383
+ #
384
+ # @param [Integer] document_key the key of the document
385
+ # @param [Hash] raw_document the data you want to replace it with
386
+ # @return [Hash] parsed JSON response from the server
387
+ # @api public
388
+ # @deprecated Use {#replace} instead.
389
+ # @example Replace the document with the key 12345
390
+ # collection.replace(12345, document)
391
+ def []=(document_key, raw_document)
392
+ warn "`[]=` is deprecated, please use `replace`"
393
+ replace(document_key, raw_document)
352
394
  end
353
395
 
354
396
  # Create a new document with given attributes
@@ -361,10 +403,21 @@ module Ashikawa
361
403
  def create_document(attributes)
362
404
  raise "Can't create a document in an edge collection" if @content_type == :edge
363
405
  response = send_request_for_content(:post => attributes)
364
- Document.new(@database, response)
406
+ Document.new(@database, response).refresh!
365
407
  end
366
408
 
367
- alias :<< :create_document
409
+ # Create a new document with given attributes
410
+ #
411
+ # @param [Hash] attributes
412
+ # @return [Document] The created document
413
+ # @api public
414
+ # @deprecated Use {#create_document} instead.
415
+ # @example Create a new document from raw data
416
+ # collection << attributes
417
+ def <<(attributes)
418
+ warn "`<<` is deprecated, please use `create_document`"
419
+ create_document(attributes)
420
+ end
368
421
 
369
422
  # Create a new edge between two documents with certain attributes
370
423
  #
@@ -378,7 +431,7 @@ module Ashikawa
378
431
  def create_edge(from, to, attributes)
379
432
  raise "Can't create an edge in a document collection" if @content_type == :document
380
433
  response = send_request("edge?collection=#{@id}&from=#{from.id}&to=#{to.id}", :post => attributes)
381
- Edge.new(@database, response)
434
+ Edge.new(@database, response).refresh!
382
435
  end
383
436
 
384
437
  # Add an index to the collection
@@ -408,8 +461,8 @@ module Ashikawa
408
461
  # people = database['people']
409
462
  # people.index(1244) #=> #<Index: id=1244...>
410
463
  def index(id)
411
- server_response = send_request("index/#{@name}/#{id}")
412
- Index.new(self, server_response)
464
+ response = send_request("index/#{@name}/#{id}")
465
+ Index.new(self, response)
413
466
  end
414
467
 
415
468
  # Get all indices
@@ -486,7 +539,7 @@ module Ashikawa
486
539
  #
487
540
  # @return [String] Response from the server
488
541
  # @api private
489
- def send_request_for_this_collection(path, method={})
542
+ def send_request_for_this_collection(path, method = {})
490
543
  send_request("collection/#{id}/#{path}", method)
491
544
  end
492
545
 
@@ -503,14 +556,14 @@ module Ashikawa
503
556
  self
504
557
  end
505
558
 
506
- # Send a request for the content with the given id
559
+ # Send a request for the content with the given key
507
560
  #
508
561
  # @param [Integer] document_id The id of the document
509
562
  # @param [Hash] opts The options for the request
510
563
  # @return [Hash] parsed JSON response from the server
511
564
  # @api private
512
- def send_request_for_content_id(document_id, opts = {})
513
- send_request("#{@content_type}/#{@id}/#{document_id}", opts)
565
+ def send_request_for_content_key(document_key, opts = {})
566
+ send_request("#{@content_type}/#{@id}/#{document_key}", opts)
514
567
  end
515
568
 
516
569
  # Send a request for the content of this collection
@@ -0,0 +1,26 @@
1
+ module Ashikawa
2
+ module Core
3
+ # Configuration of Ashikawa::Core
4
+ class Configuration < Struct.new(:url, :connection, :logger, :adapter)
5
+ # The URL of the database instance
6
+ # @api private
7
+ # @return String
8
+ attr_accessor :url
9
+
10
+ # The Connection object
11
+ # @api private
12
+ # @return Connection
13
+ attr_accessor :connection
14
+
15
+ # The logger instance
16
+ # @api private
17
+ # @return Object
18
+ attr_accessor :logger
19
+
20
+ # The HTTP adapter instance
21
+ # @api private
22
+ # @return Object
23
+ attr_accessor :adapter
24
+ end
25
+ end
26
+ end
@@ -2,6 +2,7 @@ require "forwardable"
2
2
  require "faraday"
3
3
  require "null_logger"
4
4
  require "uri"
5
+ require "equalizer"
5
6
  require "ashikawa-core/request_preprocessor"
6
7
  require "ashikawa-core/response_preprocessor"
7
8
 
@@ -11,6 +12,8 @@ module Ashikawa
11
12
  class Connection
12
13
  extend Forwardable
13
14
 
15
+ include Equalizer.new(:host, :scheme, :port)
16
+
14
17
  # The host part of the connection
15
18
  #
16
19
  # @!method host
@@ -55,7 +58,7 @@ module Ashikawa
55
58
  @connection = Faraday.new("#{api_string}/_api") do |connection|
56
59
  connection.request :ashikawa_request, logger
57
60
  connection.response :ashikawa_response, logger
58
- connection.adapter *adapter
61
+ connection.adapter(*adapter)
59
62
  end
60
63
  end
61
64
 
@@ -113,7 +116,7 @@ module Ashikawa
113
116
  # @return [Symbol] The HTTP verb used
114
117
  # @api private
115
118
  def http_verb(params)
116
- [:post, :put, :delete].find { |method_name|
119
+ [:post, :put, :delete].detect { |method_name|
117
120
  params.has_key?(method_name)
118
121
  } || :get
119
122
  end
@@ -1,4 +1,6 @@
1
1
  require 'ashikawa-core/document'
2
+ require 'ashikawa-core/edge'
3
+ require 'equalizer'
2
4
 
3
5
  module Ashikawa
4
6
  module Core
@@ -7,6 +9,8 @@ module Ashikawa
7
9
  class Cursor
8
10
  include Enumerable
9
11
 
12
+ include Equalizer.new(:id)
13
+
10
14
  # The ID of the cursor
11
15
  # @return [String]
12
16
  # @api public
@@ -54,7 +58,7 @@ module Ashikawa
54
58
 
55
59
  begin
56
60
  @current.each do |raw_document|
57
- yield Document.new(@database, raw_document)
61
+ yield parse_raw_document(raw_document)
58
62
  end
59
63
  end while next_batch
60
64
  nil
@@ -72,6 +76,28 @@ module Ashikawa
72
76
 
73
77
  private
74
78
 
79
+ # Parse a raw document and return a Document or Edge for it
80
+ #
81
+ # @param [Hash] raw_document
82
+ # @return Document | Edge
83
+ # @api private
84
+ def parse_raw_document(raw_document)
85
+ detect_document_class_for(raw_document).new(@database, raw_document)
86
+ end
87
+
88
+ # Detect if a raw document is a document or edge and return the class
89
+ #
90
+ # @param [Hash] raw_document
91
+ # @return class
92
+ # @api private
93
+ def detect_document_class_for(raw_document)
94
+ if raw_document.has_key?("_from") && raw_document.has_key?("_to")
95
+ Edge
96
+ else
97
+ Document
98
+ end
99
+ end
100
+
75
101
  # Pull the raw data from the cursor into this object
76
102
  #
77
103
  # @param [Hash] raw_cursor
@@ -80,11 +106,7 @@ module Ashikawa
80
106
  def parse_raw_cursor(raw_cursor)
81
107
  @id = raw_cursor['id']
82
108
  @has_more = raw_cursor['hasMore']
83
- if raw_cursor['result']
84
- parse_documents_cursor(raw_cursor)
85
- elsif raw_cursor['document']
86
- parse_document_cursor(raw_cursor)
87
- end
109
+ parse_documents_cursor(raw_cursor)
88
110
  self
89
111
  end
90
112
 
@@ -98,16 +120,6 @@ module Ashikawa
98
120
  @length = raw_cursor['count'].to_i if raw_cursor.has_key?('count')
99
121
  end
100
122
 
101
- # Parse the cursor for a single document
102
- #
103
- # @param [Hash] raw_cursor
104
- # @return self
105
- # @api private
106
- def parse_document_cursor(raw_cursor)
107
- @current = [raw_cursor['document']]
108
- @length = 1
109
- end
110
-
111
123
  # Get a new batch from the server
112
124
  #
113
125
  # @return [Boolean] Is there a next batch?
@@ -2,13 +2,13 @@ require "ashikawa-core/exceptions/client_error/resource_not_found/collection_not
2
2
  require "ashikawa-core/collection"
3
3
  require "ashikawa-core/connection"
4
4
  require "ashikawa-core/cursor"
5
+ require "ashikawa-core/configuration"
6
+ require "ashikawa-core/transaction"
5
7
  require "forwardable"
8
+ require "equalizer"
6
9
 
7
10
  module Ashikawa
8
11
  module Core
9
- # Configuration of Ashikawa::Core
10
- Configuration = Struct.new(:url, :connection, :logger, :adapter)
11
-
12
12
  # An ArangoDB database
13
13
  class Database
14
14
  COLLECTION_TYPES = {
@@ -18,6 +18,8 @@ module Ashikawa
18
18
 
19
19
  extend Forwardable
20
20
 
21
+ include Equalizer.new(:host, :port, :scheme)
22
+
21
23
  # Delegate sending requests to the connection
22
24
  def_delegator :@connection, :send_request
23
25
  def_delegator :@connection, :host
@@ -43,13 +45,13 @@ module Ashikawa
43
45
  # config.adapter = my_adapter
44
46
  # config.logger = my_logger
45
47
  # end
46
- def initialize()
48
+ def initialize
47
49
  configuration = Ashikawa::Core::Configuration.new
48
50
  yield(configuration)
49
51
  @connection = configuration.connection || setup_new_connection(configuration.url, configuration.logger, configuration.adapter)
50
52
  end
51
53
 
52
- # Returns a list of all collections defined in the database
54
+ # Returns a list of all non-system collections defined in the database
53
55
  #
54
56
  # @return [Array<Collection>]
55
57
  # @api public
@@ -59,8 +61,18 @@ module Ashikawa
59
61
  # database["b"]
60
62
  # database.collections # => [ #<Collection name="a">, #<Collection name="b">]
61
63
  def collections
62
- response = send_request("collection")
63
- response["collections"].map { |collection| Ashikawa::Core::Collection.new(self, collection) }
64
+ all_collections_where { |collection| !collection["name"].start_with?("_") }
65
+ end
66
+
67
+ # Returns a list of all system collections defined in the database
68
+ #
69
+ # @return [Array<Collection>]
70
+ # @api public
71
+ # @example Get an Array containing the Collections in the database
72
+ # database = Ashikawa::Core::Database.new("http://localhost:8529")
73
+ # database.system_collections # => [ #<Collection name="_a">, #<Collection name="_b">]
74
+ def system_collections
75
+ all_collections_where { |collection| collection["name"].start_with?("_") }
64
76
  end
65
77
 
66
78
  # Create a Collection based on name
@@ -73,11 +85,8 @@ module Ashikawa
73
85
  # @example Create a new, volatile collection
74
86
  # database = Ashikawa::Core::Database.new("http://localhost:8529")
75
87
  # database.create_collection("a", :isVolatile => true) # => #<Collection name="a">
76
- def create_collection(collection_identifier, opts={})
77
- params = { :name => collection_identifier }
78
- params[:isVolatile] = true if opts[:is_volatile] == true
79
- params[:type] = COLLECTION_TYPES[opts[:content_type]] if opts.has_key?(:content_type)
80
- response = send_request("collection", :post => params)
88
+ def create_collection(collection_identifier, opts = {})
89
+ response = send_request("collection", :post => translate_params(collection_identifier, opts))
81
90
  Ashikawa::Core::Collection.new(self, response)
82
91
  end
83
92
 
@@ -115,6 +124,22 @@ module Ashikawa
115
124
  Query.new(self)
116
125
  end
117
126
 
127
+ # Create a new Transaction for this database
128
+ #
129
+ # @param [String] action The JS action you want to execute
130
+ # @options collections [Array<String>] :read The collections you want to read from
131
+ # @options collections [Array<String>] :write The collections you want to write to
132
+ # @return [Object] The result of the transaction
133
+ # @api public
134
+ # @example Create a new Transaction
135
+ # transaction = database.create_transaction("function () { return 5; }", :read => ["collection_1"])
136
+ # transaction.execute #=> 5
137
+ def create_transaction(action, collections)
138
+ Ashikawa::Core::Transaction.new(self, action, collections)
139
+ end
140
+
141
+ private
142
+
118
143
  # Setup the connection object
119
144
  #
120
145
  # @param [String] url
@@ -129,6 +154,58 @@ module Ashikawa
129
154
  :adapter => adapter
130
155
  })
131
156
  end
157
+
158
+ # Parse a raw collection
159
+ #
160
+ # @param [Array] raw_collections
161
+ # @return [Array]
162
+ # @api private
163
+ def parse_raw_collections(raw_collections)
164
+ raw_collections.map { |collection|
165
+ Ashikawa::Core::Collection.new(self, collection)
166
+ }
167
+ end
168
+
169
+ # Translate the key options into the required format
170
+ #
171
+ # @param [Hash] key_options
172
+ # @return [Hash]
173
+ # @api private
174
+ def translate_key_options(key_options)
175
+ {
176
+ :type => key_options[:type].to_s,
177
+ :offset => key_options[:offset],
178
+ :increment => key_options[:increment],
179
+ :allowUserKeys => key_options[:allow_user_keys]
180
+ }
181
+ end
182
+
183
+ # Translate the params into the required format
184
+ #
185
+ # @param [String] collection_identifier
186
+ # @param [Hash] opts
187
+ # @return [Hash]
188
+ # @api private
189
+ def translate_params(collection_identifier, opts)
190
+ params = { :name => collection_identifier }
191
+ params[:isVolatile] = true if opts[:is_volatile] == true
192
+ params[:type] = COLLECTION_TYPES[opts[:content_type]] if opts.has_key?(:content_type)
193
+ params[:keyOptions] = translate_key_options(opts[:key_options]) if opts.has_key?(:key_options)
194
+ params
195
+ end
196
+
197
+ # Get all collections that fulfill a certain criteria
198
+ #
199
+ # @yield [raw_collection] Yields the raw collections so you can decide which to keep
200
+ # @yieldparam [raw_collection] A raw collection
201
+ # @yieldreturn [Boolean] Should the collection be kept
202
+ # @return [Array<Collection>]
203
+ # @api private
204
+ def all_collections_where(&block)
205
+ raw_collections = send_request("collection")["collections"]
206
+ raw_collections.keep_if(&block)
207
+ parse_raw_collections(raw_collections)
208
+ end
132
209
  end
133
210
  end
134
211
  end