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.
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