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
@@ -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
  }