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
@@ -1,5 +1,5 @@
1
1
  require "faraday"
2
- require "multi_json"
2
+ require "json"
3
3
  require "ashikawa-core/exceptions/client_error"
4
4
  require "ashikawa-core/exceptions/client_error/resource_not_found"
5
5
  require "ashikawa-core/exceptions/client_error/resource_not_found/index_not_found"
@@ -51,10 +51,10 @@ module Ashikawa
51
51
  # @api private
52
52
  def resource_not_found_for(env)
53
53
  raise case env[:url].path
54
- when /\A\/_api\/document/ then Ashikawa::Core::DocumentNotFoundException
55
- when /\A\/_api\/collection/ then Ashikawa::Core::CollectionNotFoundException
56
- when /\A\/_api\/index/ then Ashikawa::Core::IndexNotFoundException
57
- else Ashikawa::Core::ResourceNotFound
54
+ when /\A\/_api\/document/ then Ashikawa::Core::DocumentNotFoundException
55
+ when /\A\/_api\/collection/ then Ashikawa::Core::CollectionNotFoundException
56
+ when /\A\/_api\/index/ then Ashikawa::Core::IndexNotFoundException
57
+ else Ashikawa::Core::ResourceNotFound
58
58
  end
59
59
  end
60
60
 
@@ -64,9 +64,9 @@ module Ashikawa
64
64
  # @return [Hash] The parsed body
65
65
  # @api private
66
66
  def parse_json(env)
67
- raise MultiJson::LoadError unless json_content_type?(env[:response_headers]["content-type"])
68
- MultiJson.load(env[:body])
69
- rescue MultiJson::LoadError
67
+ raise JSON::ParserError unless json_content_type?(env[:response_headers]["content-type"])
68
+ JSON.parse(env[:body])
69
+ rescue JSON::ParserError
70
70
  raise Ashikawa::Core::JsonError
71
71
  end
72
72
 
@@ -85,12 +85,11 @@ module Ashikawa
85
85
  # @return [nil]
86
86
  # @api private
87
87
  def handle_status(env)
88
- status = env[:status]
89
- case status
88
+ case env[:status]
90
89
  when BadSyntaxStatus then raise Ashikawa::Core::BadSyntax
91
90
  when ResourceNotFoundErrorError then raise resource_not_found_for(env)
92
- when ClientErrorStatuses then raise Ashikawa::Core::ClientError, status
93
- when ServerErrorStatuses then raise Ashikawa::Core::ServerError, status
91
+ when ClientErrorStatuses then raise Ashikawa::Core::ClientError, error(env[:body])
92
+ when ServerErrorStatuses then raise Ashikawa::Core::ServerError, error(env[:body])
94
93
  end
95
94
  end
96
95
 
@@ -103,6 +102,16 @@ module Ashikawa
103
102
  @logger.info("#{env[:status]} #{env[:body]}")
104
103
  nil
105
104
  end
105
+
106
+ # Read the error message for the request
107
+ #
108
+ # @param [String] The raw body of the request
109
+ # @return [String] The formatted error message
110
+ # @api private
111
+ def error(body)
112
+ parsed_body = JSON.parse(body)
113
+ "#{parsed_body["errorNum"]}: #{parsed_body["errorMessage"]}"
114
+ end
106
115
  end
107
116
 
108
117
  Faraday.register_middleware :response,
@@ -0,0 +1,113 @@
1
+ module Ashikawa
2
+ module Core
3
+ # A JavaScript Transaction on the database
4
+ class Transaction
5
+ # The collections the transaction writes to
6
+ #
7
+ # @return [Array<String>]
8
+ # @api public
9
+ # @example Get the collections that the transaction writes to
10
+ # transaction.write_collections # => ["collection_1"]
11
+ def write_collections
12
+ @request_parameters[:collections][:write]
13
+ end
14
+
15
+ # The collections the transaction reads from
16
+ #
17
+ # @return [Array<String>]
18
+ # @api public
19
+ # @example Get the collections that the transaction reads from
20
+ # transaction.read_collections # => ["collection_1"]
21
+ def read_collections
22
+ @request_parameters[:collections][:read]
23
+ end
24
+
25
+ # If set to true, the transaction will write all data to disk before returning
26
+ #
27
+ # @return [Boolean]
28
+ # @api public
29
+ # @example Check, if the transaction waits for sync
30
+ # transaction.wait_for_sync #=> false
31
+ def wait_for_sync
32
+ @request_parameters[:waitForSync]
33
+ end
34
+
35
+ # If set to true, the transaction will write all data to disk before returning
36
+ #
37
+ # @param [Boolean] wait_for_sync
38
+ # @api public
39
+ # @example Activate wait sync
40
+ # transaction.wait_for_sync = true
41
+ def wait_for_sync=(wait_for_sync)
42
+ @request_parameters[:waitForSync] = wait_for_sync
43
+ end
44
+
45
+ # An optional numeric value used to set a timeout for waiting on collection locks
46
+ #
47
+ # @return [Integer]
48
+ # @api public
49
+ # @example Check how long the lock timeout is
50
+ # transaction.lock_timeout # => 30
51
+ def lock_timeout
52
+ @request_parameters[:lockTimeout]
53
+ end
54
+
55
+ # An optional numeric value used to set a timeout for waiting on collection locks
56
+ #
57
+ # @param [Integer] lock_timeout
58
+ # @api public
59
+ # @example Set the lock timeout to 30
60
+ # transaction.lock_timeout = 30
61
+ def lock_timeout=(timeout)
62
+ @request_parameters[:lockTimeout] = timeout
63
+ end
64
+
65
+ # Initialize a Transaction
66
+ #
67
+ # @param [Database] database
68
+ # @param [String] action An action written in JavaScript
69
+ # @option options [Array<String>] :write The collections you want to write to
70
+ # @option options [Array<String>] :read The collections you want to read from
71
+ # @api public
72
+ # @example Create a Transaction
73
+ # transaction = Ashikawa::Core::Transaction.new(database, "function () { return 5; }",
74
+ # :read => ["collection_1"]
75
+ def initialize(database, action, options)
76
+ @database = database
77
+ @request_parameters = {
78
+ :action => action,
79
+ :collections => parse_options(options),
80
+ :waitForSync => false
81
+ }
82
+ end
83
+
84
+ # Execute the transaction
85
+ #
86
+ # @param [Object] action_params The parameters for the defined action
87
+ # @return Object The result of the transaction
88
+ # @api public
89
+ # @example Run a Transaction
90
+ # transaction.execute({ :a => 5 })
91
+ def execute(action_parameters = :no_params_provided)
92
+ @request_parameters[:params] = action_parameters unless action_parameters == :no_params_provided
93
+ response = @database.send_request("transaction", :post => @request_parameters)
94
+ response["result"]
95
+ end
96
+
97
+ private
98
+
99
+ # Parse the read and write collections from the options
100
+ #
101
+ # @option options [Array<String>] :write The collections you want to write to
102
+ # @option options [Array<String>] :read The collections you want to read from
103
+ # @return [Hash]
104
+ # @api private
105
+ def parse_options(options)
106
+ collections = {}
107
+ collections[:write] = options[:write] if options.has_key? :write
108
+ collections[:read] = options[:read] if options.has_key? :read
109
+ collections
110
+ end
111
+ end
112
+ end
113
+ end
@@ -1,6 +1,6 @@
1
1
  module Ashikawa
2
2
  module Core
3
3
  # Current version of Ashikawa::Core
4
- VERSION = "0.7.2"
4
+ VERSION = "0.8.0"
5
5
  end
6
6
  end
@@ -38,6 +38,20 @@ describe "Basics" do
38
38
  subject["volatile_collection"].volatile?.should be_true
39
39
  end
40
40
 
41
+ it "should create an autoincrementing collection" do
42
+ subject.create_collection("autoincrement_collection", :is_volatile => true, :key_options => {
43
+ :type => :autoincrement,
44
+ :increment => 10,
45
+ :allow_user_keys => false
46
+ })
47
+ key_options = subject["autoincrement_collection"].key_options
48
+
49
+ key_options.type.should == "autoincrement"
50
+ key_options.offset.should == 0
51
+ key_options.increment.should == 10
52
+ key_options.allow_user_keys.should == false
53
+ end
54
+
41
55
  it "should be possible to create an edge collection" do
42
56
  subject.create_collection("edge_collection", :content_type => :edge)
43
57
  subject["edge_collection"].content_type.should == :edge
@@ -55,6 +69,10 @@ describe "Basics" do
55
69
  subject[my_collection.id].name.should == "test_collection"
56
70
  end
57
71
 
72
+ it "should be possible to list all system collections" do
73
+ subject.system_collections.length.should > 0
74
+ end
75
+
58
76
  it "should be possible to load and unload collections" do
59
77
  my_collection = subject["test_collection"]
60
78
  my_collection.status.loaded?.should be_true
@@ -89,8 +107,8 @@ describe "Basics" do
89
107
  it "should be possible to get information about the number of documents" do
90
108
  empty_collection = subject["empty_collection"]
91
109
  empty_collection.length.should == 0
92
- empty_collection << { :name => "testname", :age => 27}
93
- empty_collection << { :name => "anderer name", :age => 28}
110
+ empty_collection.create_document({ :name => "testname", :age => 27})
111
+ empty_collection.create_document({ :name => "anderer name", :age => 28})
94
112
  empty_collection.length.should == 2
95
113
  empty_collection.truncate!
96
114
  empty_collection.length.should == 0
@@ -104,7 +122,7 @@ describe "Basics" do
104
122
  document["name"] = "Other Dude"
105
123
  document.save
106
124
 
107
- collection[document_key]["name"].should == "Other Dude"
125
+ collection.fetch(document_key)["name"].should == "Other Dude"
108
126
  end
109
127
 
110
128
  it "should be possible to access and create documents from a collection" do
@@ -112,10 +130,10 @@ describe "Basics" do
112
130
 
113
131
  document = collection.create_document(:name => "The Dude", :bowling => true)
114
132
  document_key = document.key
115
- collection[document_key]["name"].should == "The Dude"
133
+ collection.fetch(document_key)["name"].should == "The Dude"
116
134
 
117
- collection[document_key] = { :name => "Other Dude", :bowling => true }
118
- collection[document_key]["name"].should == "Other Dude"
135
+ collection.replace(document_key, { :name => "Other Dude", :bowling => true })
136
+ collection.fetch(document_key)["name"].should == "Other Dude"
119
137
  end
120
138
 
121
139
  it "should be possible to create an edge between two documents" do
@@ -126,7 +144,7 @@ describe "Basics" do
126
144
  b = nodes.create_document({:name => "b"})
127
145
  e = edges.create_edge(a, b, {:name => "fance_edge"})
128
146
 
129
- e = edges[e.key]
147
+ e = edges.fetch(e.key)
130
148
  e.from_id.should == a.id
131
149
  e.to_id.should == b.id
132
150
  end
@@ -145,22 +163,32 @@ describe "Basics" do
145
163
  it "should be possible to manipulate documents and save them" do
146
164
  subject["name"] = "Jeffrey Lebowski"
147
165
  subject["name"].should == "Jeffrey Lebowski"
148
- collection[document_key]["name"].should == "The Dude"
166
+ collection.fetch(document_key)["name"].should == "The Dude"
149
167
  subject.save
150
- collection[document_key]["name"].should == "Jeffrey Lebowski"
168
+ collection.fetch(document_key)["name"].should == "Jeffrey Lebowski"
151
169
  end
152
170
 
153
171
  it "should be possible to delete a document" do
154
- collection[document_key].delete
172
+ collection.fetch(document_key).delete
155
173
  expect {
156
- collection[document_key]
174
+ collection.fetch(document_key)
157
175
  }.to raise_exception Ashikawa::Core::DocumentNotFoundException
158
176
  end
159
177
 
160
178
  it "should not be possible to delete a document that doesn't exist" do
161
179
  expect {
162
- collection[123].delete
180
+ collection.fetch(123).delete
163
181
  }.to raise_exception Ashikawa::Core::DocumentNotFoundException
164
182
  end
183
+
184
+ it "should be possible to refresh a document" do
185
+ changed_document = collection.fetch(document_key)
186
+ changed_document["name"] = "New Name"
187
+ changed_document.save
188
+
189
+ subject["name"].should == "The Dude"
190
+ subject.refresh!
191
+ subject["name"].should == "New Name"
192
+ end
165
193
  end
166
194
  end
@@ -1,7 +1,8 @@
1
1
  require 'acceptance/spec_helper'
2
2
 
3
3
  describe "Indices" do
4
- let(:database) { Ashikawa::Core::Database.new do |config|
4
+ let(:database) {
5
+ Ashikawa::Core::Database.new do |config|
5
6
  config.url = ARANGO_HOST
6
7
  end
7
8
  }
@@ -1,7 +1,8 @@
1
1
  require 'acceptance/spec_helper'
2
2
 
3
3
  describe "Queries" do
4
- let(:database) { Ashikawa::Core::Database.new do |config|
4
+ let(:database) {
5
+ Ashikawa::Core::Database.new do |config|
5
6
  config.url = ARANGO_HOST
6
7
  end
7
8
  }
@@ -12,10 +13,10 @@ describe "Queries" do
12
13
  query = "FOR u IN my_collection FILTER u.bowling == true RETURN u"
13
14
  options = { :batch_size => 2, :count => true }
14
15
 
15
- collection << { "name" => "Jeff Lebowski", "bowling" => true }
16
- collection << { "name" => "Walter Sobchak", "bowling" => true }
17
- collection << { "name" => "Donny Kerabatsos", "bowling" => true }
18
- collection << { "name" => "Jeffrey Lebowski", "bowling" => false }
16
+ collection.create_document({ "name" => "Jeff Lebowski", "bowling" => true })
17
+ collection.create_document({ "name" => "Walter Sobchak", "bowling" => true })
18
+ collection.create_document({ "name" => "Donny Kerabatsos", "bowling" => true })
19
+ collection.create_document({ "name" => "Jeffrey Lebowski", "bowling" => false })
19
20
 
20
21
  names = database.query.execute(query, options).map { |person| person["name"] }
21
22
  names.should include "Jeff Lebowski"
@@ -36,36 +37,36 @@ describe "Queries" do
36
37
  before(:each) { subject.truncate! }
37
38
 
38
39
  it "should return all documents of a collection" do
39
- subject << { :name => "testname", :age => 27}
40
+ subject.create_document({ :name => "testname", :age => 27})
40
41
  subject.query.all.first["name"].should == "testname"
41
42
  end
42
43
 
43
44
  it "should be possible to limit and skip results" do
44
- subject << { :name => "test1"}
45
- subject << { :name => "test2"}
46
- subject << { :name => "test3"}
45
+ subject.create_document({ :name => "test1"})
46
+ subject.create_document({ :name => "test2"})
47
+ subject.create_document({ :name => "test3"})
47
48
 
48
49
  subject.query.all(:limit => 2).length.should == 2
49
50
  subject.query.all(:skip => 2).length.should == 1
50
51
  end
51
52
 
52
53
  it "should be possible to query documents by example" do
53
- subject << { "name" => "Random Document" }
54
+ subject.create_document({ "name" => "Random Document" })
54
55
  result = subject.query.by_example :name => "Random Document"
55
56
  result.length.should == 1
56
57
  end
57
58
 
58
59
  it "should be possible to query first document by example" do
59
- subject << { "name" => "Single Document" }
60
+ subject.create_document({ "name" => "Single Document" })
60
61
  result = subject.query.first_example :name => "Single Document"
61
- result.length.should == 1
62
+ result["name"].should == "Single Document"
62
63
  end
63
64
 
64
65
  describe "query by geo coordinates" do
65
66
  before :each do
66
67
  subject.add_index :geo, :on => [:latitude, :longitude]
67
- subject << { "name" => "cologne", "latitude" => 50.948045, "longitude" => 6.961212 }
68
- subject << { "name" => "san francisco", "latitude" => -122.395899, "longitude" => 37.793621 }
68
+ subject.create_document({ "name" => "cologne", "latitude" => 50.948045, "longitude" => 6.961212 })
69
+ subject.create_document({ "name" => "san francisco", "latitude" => -122.395899, "longitude" => 37.793621 })
69
70
  end
70
71
 
71
72
  it "should be possible to query documents near a certain location" do
@@ -83,9 +84,9 @@ describe "Queries" do
83
84
  describe "queries by integer ranges" do
84
85
  before :each do
85
86
  subject.add_index :skiplist, :on => [:age]
86
- subject << { "name" => "Georg", "age" => 12 }
87
- subject << { "name" => "Anne", "age" => 21 }
88
- subject << { "name" => "Jens", "age" => 49 }
87
+ subject.create_document({ "name" => "Georg", "age" => 12 })
88
+ subject.create_document({ "name" => "Anne", "age" => 21 })
89
+ subject.create_document({ "name" => "Jens", "age" => 49 })
89
90
  end
90
91
 
91
92
  it "should be possible to query documents for numbers in a certain range" do
@@ -0,0 +1,30 @@
1
+ require 'acceptance/spec_helper'
2
+
3
+ describe "Transactions" do
4
+ subject {
5
+ Ashikawa::Core::Database.new do |config|
6
+ config.url = ARANGO_HOST
7
+ end
8
+ }
9
+
10
+ before :each do
11
+ subject.collections.each { |collection| collection.delete }
12
+ subject["collection_1"]
13
+ subject["collection_2"]
14
+ subject["collection_3"]
15
+ end
16
+
17
+ let(:js_function) { "function (x) { return x.a; }" }
18
+
19
+ it "should create and execute a transaction" do
20
+ transaction = subject.create_transaction(js_function,
21
+ :write => ["collection_1", "collection_2"],
22
+ :read => ["collection_3"]
23
+ )
24
+
25
+ transaction.wait_for_sync = true
26
+ transaction.lock_timeout = 14
27
+
28
+ transaction.execute({ :a => 5 }).should == 5
29
+ end
30
+ end
@@ -1,32 +1,92 @@
1
1
  {
2
- "collections": [
3
- {
4
- "name": "example_1",
5
- "waitForSync": true,
6
- "id": "60768679",
7
- "status": 3
8
- },
9
- {
10
- "name": "example_2",
11
- "waitForSync": true,
12
- "id": "60768669",
13
- "status": 3
14
- }
15
- ],
16
- "code": 200,
17
- "error": false,
18
- "names":{
19
- "example_1": {
20
- "name": "example_1",
21
- "waitForSync": true,
22
- "id": "60768679",
23
- "status": 3
24
- },
25
- "example_2": {
26
- "name": "example_2",
27
- "waitForSync": true,
28
- "id": "60768669",
29
- "status": 3
30
- }
31
- }
2
+ "collections": [
3
+ {
4
+ "name": "example_1",
5
+ "waitForSync": true,
6
+ "id": "60768679",
7
+ "status": 3
8
+ },
9
+ {
10
+ "name": "example_2",
11
+ "waitForSync": true,
12
+ "id": "60768669",
13
+ "status": 3
14
+ },
15
+ {
16
+ "id": "2703783",
17
+ "name": "_modules",
18
+ "status": 2,
19
+ "type": 2
20
+ },
21
+ {
22
+ "id": "7618983",
23
+ "name": "_aqlfunctions",
24
+ "status":2,
25
+ "type":2
26
+ },
27
+ {
28
+ "id": "10699175",
29
+ "name": "_trx",
30
+ "status": 2,
31
+ "type": 2
32
+ },
33
+ {
34
+ "id": "3490215",
35
+ "name": "_routing",
36
+ "status": 3,
37
+ "type": 2
38
+ },
39
+ {
40
+ "id": "1655207",
41
+ "name": "_graphs",
42
+ "status": 2,
43
+ "type": 2
44
+ }
45
+ ],
46
+ "code": 200,
47
+ "error": false,
48
+ "names": {
49
+ "example_1": {
50
+ "name": "example_1",
51
+ "waitForSync": true,
52
+ "id": "60768679",
53
+ "status": 3
54
+ },
55
+ "example_2": {
56
+ "name": "example_2",
57
+ "waitForSync": true,
58
+ "id": "60768669",
59
+ "status": 3
60
+ },
61
+ "_modules": {
62
+ "id": "2703783",
63
+ "name": "_modules",
64
+ "status": 2,
65
+ "type": 2
66
+ },
67
+ "_aqlfunctions": {
68
+ "id": "7618983",
69
+ "name": "_aqlfunctions",
70
+ "status": 2,
71
+ "type": 2
72
+ },
73
+ "_trx":{
74
+ "id": "10699175",
75
+ "name": "_trx",
76
+ "status": 2,
77
+ "type": 2
78
+ },
79
+ "_routing":{
80
+ "id": "3490215",
81
+ "name": "_routing",
82
+ "status": 3,
83
+ "type": 2
84
+ },
85
+ "_graphs":{
86
+ "id": "1655207",
87
+ "name": "_graphs",
88
+ "status": 2,
89
+ "type": 2
90
+ }
91
+ }
32
92
  }