acfs 0.14.0 → 0.15.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +57 -14
- data/acfs.gemspec +1 -0
- data/lib/acfs.rb +10 -13
- data/lib/acfs/adapter/base.rb +28 -0
- data/lib/acfs/adapter/typhoeus.rb +10 -14
- data/lib/acfs/errors.rb +2 -0
- data/lib/acfs/global.rb +17 -0
- data/lib/acfs/model.rb +2 -0
- data/lib/acfs/model/operational.rb +19 -0
- data/lib/acfs/model/persistence.rb +2 -17
- data/lib/acfs/model/query_methods.rb +9 -23
- data/lib/acfs/operation.rb +60 -0
- data/lib/acfs/runner.rb +76 -0
- data/lib/acfs/service.rb +0 -2
- data/lib/acfs/service/middleware.rb +3 -3
- data/lib/acfs/stub.rb +85 -0
- data/lib/acfs/version.rb +1 -1
- data/spec/acfs/service/middleware_spec.rb +49 -0
- data/spec/acfs/stub_spec.rb +96 -0
- data/spec/acfs_spec.rb +11 -12
- data/spec/spec_helper.rb +2 -1
- data/spec/support/response.rb +1 -0
- metadata +26 -3
- data/lib/acfs/service/request_handler.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5bd82876ac05bd67b6a8d95a8d9e0779eb7d1c1
|
4
|
+
data.tar.gz: 5234ed3bfb459596c3cc0533bec51a67013f0b69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e10f336df5c116c5155ab4c51dbbe8a746ce3235b87180c69a5a1651ed285c75abeccd219b95fc0dda1a5e65bd9ccd3bfa74a42f672ae9de60d8ec1f9f99f14a
|
7
|
+
data.tar.gz: 8631fdb5b179084f4f54ca953aa750f6b70242c5ebcd6aa6c52f97836663dd88e3d98c6dc30239c9144a63718913d3bac4041530a3aaccebedf8567f4404ef4e
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -2,12 +2,9 @@
|
|
2
2
|
|
3
3
|
[](http://badge.fury.io/rb/acfs) [](https://travis-ci.org/jgraichen/acfs) [](https://coveralls.io/r/jgraichen/acfs) [](https://codeclimate.com/github/jgraichen/acfs) [](https://gemnasium.com/jgraichen/acfs)
|
4
4
|
|
5
|
-
Acfs is a library to develop API client libraries for single services within a
|
6
|
-
larger service oriented application.
|
5
|
+
Acfs is a library to develop API client libraries for single services within a larger service oriented application.
|
7
6
|
|
8
|
-
Acfs covers model and service abstraction, convenient query and filter methods,
|
9
|
-
full middleware stack for pre-processing requests and responses on a per service
|
10
|
-
level and automatic request queuing and parallel processing. See Usage for more.
|
7
|
+
Acfs covers model and service abstraction, convenient query and filter methods, full middleware stack for pre-processing requests and responses on a per service level and automatic request queuing and parallel processing. See Usage for more.
|
11
8
|
|
12
9
|
## Installation
|
13
10
|
|
@@ -15,8 +12,7 @@ Add this line to your application's Gemfile:
|
|
15
12
|
|
16
13
|
gem 'acfs', '~> 0.14.0'
|
17
14
|
|
18
|
-
**Note:** Acfs is under development. I'll try to avoid changes to the public
|
19
|
-
API but internal APIs may change quite often.
|
15
|
+
**Note:** Acfs is under development. I'll try to avoid changes to the public API but internal APIs may change quite often.
|
20
16
|
|
21
17
|
And then execute:
|
22
18
|
|
@@ -42,8 +38,7 @@ class UserService < Acfs::Service
|
|
42
38
|
end
|
43
39
|
```
|
44
40
|
|
45
|
-
This specifies where the `UserService` is located. You can now create some
|
46
|
-
models representing resources served by the `UserService`.
|
41
|
+
This specifies where the `UserService` is located. You can now create some models representing resources served by the `UserService`.
|
47
42
|
|
48
43
|
```ruby
|
49
44
|
class User
|
@@ -63,8 +58,7 @@ class User
|
|
63
58
|
end
|
64
59
|
```
|
65
60
|
|
66
|
-
The service and model classes can be shipped as a gem or git submodule to be
|
67
|
-
included by the frontend application(s).
|
61
|
+
The service and model classes can be shipped as a gem or git submodule to be included by the frontend application(s).
|
68
62
|
|
69
63
|
You can use the model there:
|
70
64
|
|
@@ -87,8 +81,7 @@ Acfs.run # Will request `http://users.myapp.org/users`
|
|
87
81
|
@users #=> [<User>, ...]
|
88
82
|
```
|
89
83
|
|
90
|
-
If you need multiple resources or dependent resources first define a "plan"
|
91
|
-
how they can be loaded:
|
84
|
+
If you need multiple resources or dependent resources first define a "plan" how they can be loaded:
|
92
85
|
|
93
86
|
```ruby
|
94
87
|
@user = User.find(5) do |user|
|
@@ -141,6 +134,57 @@ Acfs has basic update support using `PUT` requests:
|
|
141
134
|
@user.persisted? # => true
|
142
135
|
```
|
143
136
|
|
137
|
+
## Stubbing
|
138
|
+
|
139
|
+
You can stub resources in applications using an Acfs service client:
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
# Enable stubs in spec helper
|
143
|
+
Acfs::Stub.enable
|
144
|
+
|
145
|
+
before do
|
146
|
+
Acfs::Stub.resource MyUser, :read, with: { id: 1 }, return: { id: 1, name: 'John Smith', age: 32 }
|
147
|
+
Acfs::Stub.resource MyUser, :read, with: { id: 2 }, raise: :not_found
|
148
|
+
Acfs::Stub.resource Session, :create, with: { ident: 'john@exmaple.org', password: 's3cr3t' }, return: { id: 'longhash', user: 1 }
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should find user number one' do
|
152
|
+
user = MyUser.find 1
|
153
|
+
Acfs.run
|
154
|
+
|
155
|
+
expect(user.id).to be == 1
|
156
|
+
expect(user.name).to be == 'John Smith'
|
157
|
+
expect(user.age).to be == 32
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should not find user number two' do
|
161
|
+
MyUser.find 3
|
162
|
+
|
163
|
+
expect { Acfs.run }.to raise_error(Acfs::ResourceNotFound)
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'should allow stub resource creation' do
|
167
|
+
session = Session.create! ident: 'john@exmaple.org', password: 's3cr3t'
|
168
|
+
|
169
|
+
expect(session.id).to be == 'longhash'
|
170
|
+
expect(session.user).to be == 1
|
171
|
+
end
|
172
|
+
```
|
173
|
+
|
174
|
+
By default Acfs raises an error when a non stubbed resource should be requested. You can switch of the behavior:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
before do
|
178
|
+
Acfs::Stub.allow_requests = true
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'should find user number one' do
|
182
|
+
user = MyUser.find 1
|
183
|
+
Acfs.run # Would have raised Acfs::RealRequestNotAllowedError
|
184
|
+
# Will run real request to user service instead.
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
144
188
|
## Roadmap
|
145
189
|
|
146
190
|
* Update
|
@@ -157,7 +201,6 @@ Acfs has basic update support using `PUT` requests:
|
|
157
201
|
Modified Headers, conflict detection, ...
|
158
202
|
* Pagination? Filtering? (If service API provides such features.)
|
159
203
|
* Messaging Queue support for services and models
|
160
|
-
* Allow stubbing of resources as objects for testing services.
|
161
204
|
* Documentation
|
162
205
|
|
163
206
|
## Contributing
|
data/acfs.gemspec
CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_runtime_dependency 'actionpack'
|
24
24
|
spec.add_runtime_dependency 'multi_json'
|
25
25
|
spec.add_runtime_dependency 'typhoeus'
|
26
|
+
spec.add_runtime_dependency 'rack'
|
26
27
|
|
27
28
|
spec.add_development_dependency 'bundler', '~> 1.3'
|
28
29
|
end
|
data/lib/acfs.rb
CHANGED
@@ -1,16 +1,24 @@
|
|
1
1
|
require 'active_support'
|
2
|
-
require 'active_support/core_ext'
|
2
|
+
require 'active_support/core_ext/class'
|
3
|
+
require 'active_support/core_ext/hash'
|
4
|
+
require 'active_support/core_ext/module'
|
5
|
+
|
3
6
|
require 'acfs/version'
|
4
7
|
require 'acfs/errors'
|
8
|
+
require 'acfs/global'
|
5
9
|
|
6
10
|
module Acfs
|
7
11
|
extend ActiveSupport::Autoload
|
12
|
+
extend Global
|
8
13
|
|
9
14
|
autoload :Collection
|
10
15
|
autoload :Model
|
11
16
|
autoload :Request
|
12
17
|
autoload :Response
|
13
18
|
autoload :Service
|
19
|
+
autoload :Stub
|
20
|
+
autoload :Operation
|
21
|
+
autoload :Runner
|
14
22
|
|
15
23
|
module Middleware
|
16
24
|
extend ActiveSupport::Autoload
|
@@ -27,19 +35,8 @@ module Acfs
|
|
27
35
|
module Adapter
|
28
36
|
extend ActiveSupport::Autoload
|
29
37
|
|
38
|
+
autoload :Base
|
30
39
|
autoload :Typhoeus
|
31
40
|
end
|
32
|
-
|
33
|
-
class << self
|
34
|
-
|
35
|
-
# Run all queued
|
36
|
-
def run
|
37
|
-
adapter.run
|
38
|
-
end
|
39
|
-
|
40
|
-
def adapter
|
41
|
-
@adapter ||= Adapter::Typhoeus.new
|
42
|
-
end
|
43
|
-
end
|
44
41
|
end
|
45
42
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Acfs::Adapter
|
2
|
+
|
3
|
+
# Base adapter handling operation queuing
|
4
|
+
# and processing.
|
5
|
+
#
|
6
|
+
class Base
|
7
|
+
|
8
|
+
# Start processing queued requests.
|
9
|
+
#
|
10
|
+
def start
|
11
|
+
end
|
12
|
+
|
13
|
+
# Abort running and queued requests.
|
14
|
+
#
|
15
|
+
def abort
|
16
|
+
end
|
17
|
+
|
18
|
+
# Run request right now skipping queue.
|
19
|
+
#
|
20
|
+
def run(_)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Enqueue request to be run later.
|
24
|
+
#
|
25
|
+
def queue(_)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -5,26 +5,22 @@ module Acfs
|
|
5
5
|
|
6
6
|
# Adapter for Typhoeus.
|
7
7
|
#
|
8
|
-
class Typhoeus
|
8
|
+
class Typhoeus < Base
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
return hydra.run unless request
|
10
|
+
def start
|
11
|
+
hydra.run
|
12
|
+
end
|
14
13
|
|
15
|
-
|
14
|
+
def abort
|
15
|
+
hydra.abort
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
def queue(req)
|
21
|
-
hydra.queue convert_request(req)
|
18
|
+
def run(request)
|
19
|
+
convert_request(request).run
|
22
20
|
end
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
def clear
|
27
|
-
hydra.abort
|
22
|
+
def queue(request)
|
23
|
+
hydra.queue convert_request request
|
28
24
|
end
|
29
25
|
|
30
26
|
protected
|
data/lib/acfs/errors.rb
CHANGED
data/lib/acfs/global.rb
ADDED
data/lib/acfs/model.rb
CHANGED
@@ -5,6 +5,7 @@ require 'acfs/model/dirty'
|
|
5
5
|
require 'acfs/model/loadable'
|
6
6
|
require 'acfs/model/locatable'
|
7
7
|
require 'acfs/model/persistence'
|
8
|
+
require 'acfs/model/operational'
|
8
9
|
require 'acfs/model/query_methods'
|
9
10
|
require 'acfs/model/relations'
|
10
11
|
require 'acfs/model/service'
|
@@ -30,6 +31,7 @@ module Acfs
|
|
30
31
|
include Model::Loadable
|
31
32
|
include Model::Persistence
|
32
33
|
include Model::Locatable
|
34
|
+
include Model::Operational
|
33
35
|
include Model::QueryMethods
|
34
36
|
include Model::Relations
|
35
37
|
include Model::Service
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Acfs::Model
|
2
|
+
|
3
|
+
# Provide methods for creating and processing CRUD operations and
|
4
|
+
# handling responses. That includes error handling as well as
|
5
|
+
# handling stubbed resources.
|
6
|
+
#
|
7
|
+
# Should only be used internal.
|
8
|
+
#
|
9
|
+
module Operational
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
delegate :operation, to: :'self.class'
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def operation(action, opts = {}, &block)
|
15
|
+
Acfs.runner.process ::Acfs::Operation.new self, action, opts, &block
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -52,16 +52,9 @@ module Acfs
|
|
52
52
|
|
53
53
|
opts[:data] = attributes unless opts[:data]
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
if response.success?
|
58
|
-
update_with response.data
|
59
|
-
else
|
60
|
-
self.class.raise! response
|
61
|
-
end
|
55
|
+
operation (new? ? :create : :update), opts do |data|
|
56
|
+
update_with data
|
62
57
|
end
|
63
|
-
|
64
|
-
self.class.service.run request
|
65
58
|
end
|
66
59
|
|
67
60
|
module ClassMethods
|
@@ -96,14 +89,6 @@ module Acfs
|
|
96
89
|
self.attributes = data
|
97
90
|
loaded!
|
98
91
|
end
|
99
|
-
|
100
|
-
def create_request(opts = {})
|
101
|
-
Acfs::Request.new self.class.url, method: :post, data: opts[:data]
|
102
|
-
end
|
103
|
-
|
104
|
-
def put_request(opts = {})
|
105
|
-
Acfs::Request.new url, method: :put, data: opts[:data]
|
106
|
-
end
|
107
92
|
end
|
108
93
|
end
|
109
94
|
end
|
@@ -37,8 +37,8 @@ module Acfs::Model
|
|
37
37
|
def all(params = {}, &block)
|
38
38
|
collection = ::Acfs::Collection.new
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
operation :list, params: params do |data|
|
41
|
+
data.each do |obj|
|
42
42
|
collection << self.new.tap do |m|
|
43
43
|
m.attributes = obj
|
44
44
|
m.loaded!
|
@@ -47,37 +47,23 @@ module Acfs::Model
|
|
47
47
|
collection.loaded!
|
48
48
|
block.call collection unless block.nil?
|
49
49
|
end
|
50
|
-
service.queue request
|
51
50
|
|
52
51
|
collection
|
53
52
|
end
|
54
53
|
alias :where :all
|
55
54
|
|
56
|
-
def raise!(response)
|
57
|
-
case response.code
|
58
|
-
when 404
|
59
|
-
raise ::Acfs::ResourceNotFound.new response: response
|
60
|
-
when 422
|
61
|
-
raise ::Acfs::InvalidResource.new response: response, errors: response.data['errors']
|
62
|
-
else
|
63
|
-
raise ::Acfs::ErroneousResponse.new response: response
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
55
|
private
|
68
56
|
def find_single(id, opts, &block)
|
69
57
|
model = self.new
|
70
58
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
59
|
+
opts[:params] ||= {}
|
60
|
+
opts[:params].merge!({ id: id })
|
61
|
+
|
62
|
+
operation :read, opts do |data|
|
63
|
+
model.attributes = data
|
64
|
+
model.loaded!
|
65
|
+
block.call model unless block.nil?
|
79
66
|
end
|
80
|
-
service.queue request
|
81
67
|
|
82
68
|
model
|
83
69
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Acfs
|
2
|
+
|
3
|
+
# Describes a CRUD operation. Handle request creation and response
|
4
|
+
# processing as well as error handling and stubbing.
|
5
|
+
#
|
6
|
+
class Operation
|
7
|
+
attr_reader :action, :params, :resource, :data, :callback
|
8
|
+
delegate :service, to: :resource
|
9
|
+
|
10
|
+
def initialize(resource, action, opts = {}, &block)
|
11
|
+
@resource = resource
|
12
|
+
@action = action.to_sym
|
13
|
+
@params = opts[:params] || {}
|
14
|
+
@data = opts[:data] || {}
|
15
|
+
@callback = block
|
16
|
+
|
17
|
+
raise ArgumentError, 'ID parameter required for READ, UPDATE and DELETE operation.' if single? and id.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
def single?
|
21
|
+
[:read, :update, :delete].include? action
|
22
|
+
end
|
23
|
+
|
24
|
+
def synchronous?
|
25
|
+
[:update, :delete, :create].include? action
|
26
|
+
end
|
27
|
+
|
28
|
+
def id
|
29
|
+
@id ||= params.delete(:id) || data[:id]
|
30
|
+
end
|
31
|
+
|
32
|
+
def url
|
33
|
+
single? ? resource.url(id) : resource.url
|
34
|
+
end
|
35
|
+
|
36
|
+
def method
|
37
|
+
{ read: :get, list: :get, update: :put, create: :post, delete: :delete }[action]
|
38
|
+
end
|
39
|
+
|
40
|
+
def request
|
41
|
+
request = ::Acfs::Request.new url, method: method, params: params, data: data
|
42
|
+
request.on_complete do |response|
|
43
|
+
handle_failure response unless response.success?
|
44
|
+
callback.call response.data
|
45
|
+
end
|
46
|
+
request
|
47
|
+
end
|
48
|
+
|
49
|
+
def handle_failure(response)
|
50
|
+
case response.code
|
51
|
+
when 404
|
52
|
+
raise ::Acfs::ResourceNotFound.new response: response
|
53
|
+
when 422
|
54
|
+
raise ::Acfs::InvalidResource.new response: response, errors: response.data.try(:[], 'errors')
|
55
|
+
else
|
56
|
+
raise ::Acfs::ErroneousResponse.new response: response
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/acfs/runner.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'acfs/service/middleware'
|
2
|
+
|
3
|
+
module Acfs
|
4
|
+
|
5
|
+
class Runner
|
6
|
+
include Service::Middleware
|
7
|
+
attr_reader :adapter
|
8
|
+
|
9
|
+
def initialize(adapter)
|
10
|
+
@adapter = adapter
|
11
|
+
@running = false
|
12
|
+
end
|
13
|
+
|
14
|
+
# Process an operation. Synchronous operations will be run
|
15
|
+
# and parallel operations will be queued.
|
16
|
+
#
|
17
|
+
def process(op)
|
18
|
+
op.synchronous? ? run(op) : enqueue(op)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Run operation right now skipping queue.
|
22
|
+
#
|
23
|
+
def run(op)
|
24
|
+
op_request(op) { |req| adapter.run req }
|
25
|
+
end
|
26
|
+
|
27
|
+
# List of current queued operations.
|
28
|
+
#
|
29
|
+
def queue
|
30
|
+
@queue ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
# Enqueue operation to be run later.
|
34
|
+
#
|
35
|
+
def enqueue(op)
|
36
|
+
if running?
|
37
|
+
op_request(op) { |req| adapter.queue req }
|
38
|
+
else
|
39
|
+
queue << op
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return true if queued operations are currently processed.
|
44
|
+
#
|
45
|
+
def running?
|
46
|
+
@running
|
47
|
+
end
|
48
|
+
|
49
|
+
# Start processing queued operations.
|
50
|
+
#
|
51
|
+
def start
|
52
|
+
enqueue_operations
|
53
|
+
|
54
|
+
@running = true
|
55
|
+
adapter.start
|
56
|
+
@running = false
|
57
|
+
end
|
58
|
+
|
59
|
+
def clear
|
60
|
+
queue.clear
|
61
|
+
adapter.abort
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def enqueue_operations
|
66
|
+
while (op = queue.shift)
|
67
|
+
op_request(op) { |req| adapter.queue req }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def op_request(op)
|
72
|
+
return if Acfs::Stub.enabled? and Acfs::Stub.stubbed(op)
|
73
|
+
yield prepare op.service.prepare op.request
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/acfs/service.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'acfs/service/request_handler'
|
2
1
|
require 'acfs/service/middleware'
|
3
2
|
|
4
3
|
module Acfs
|
@@ -9,7 +8,6 @@ module Acfs
|
|
9
8
|
attr_accessor :options
|
10
9
|
class_attribute :base_url
|
11
10
|
|
12
|
-
include Service::RequestHandler
|
13
11
|
include Service::Middleware
|
14
12
|
|
15
13
|
def initialize(options = {})
|
@@ -8,8 +8,8 @@ module Acfs
|
|
8
8
|
module Middleware
|
9
9
|
extend ActiveSupport::Concern
|
10
10
|
|
11
|
-
def prepare(
|
12
|
-
self.class.middleware.call
|
11
|
+
def prepare(request) # :nodoc:
|
12
|
+
self.class.middleware.call request
|
13
13
|
end
|
14
14
|
|
15
15
|
module ClassMethods
|
@@ -40,7 +40,7 @@ module Acfs
|
|
40
40
|
#
|
41
41
|
def clear
|
42
42
|
@middleware = nil
|
43
|
-
@middlewares =
|
43
|
+
@middlewares = []
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
data/lib/acfs/stub.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
3
|
+
module Acfs
|
4
|
+
|
5
|
+
# Global handler for stubbing resources.
|
6
|
+
#
|
7
|
+
class Stub
|
8
|
+
ACTIONS = [ :read, :create, :update, :delete, :list ]
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
# Stub a resource with given handler block. An already created handler
|
13
|
+
# for same resource class will be overridden.
|
14
|
+
#
|
15
|
+
def resource(klass, action, opts = {}, &block)
|
16
|
+
action = action.to_sym
|
17
|
+
raise ArgumentError, "Unknown action `#{action}`." unless ACTIONS.include? action
|
18
|
+
|
19
|
+
stubs[klass] ||= {}
|
20
|
+
stubs[klass][action] ||= []
|
21
|
+
stubs[klass][action] << opts
|
22
|
+
end
|
23
|
+
|
24
|
+
def allow_requests=(allow)
|
25
|
+
@allow_requests = allow ? true : false
|
26
|
+
end
|
27
|
+
|
28
|
+
def allow_requests?
|
29
|
+
@allow_requests ||= false
|
30
|
+
end
|
31
|
+
|
32
|
+
def enabled?
|
33
|
+
@enabled ||= false
|
34
|
+
end
|
35
|
+
|
36
|
+
def enable; @enabled = true end
|
37
|
+
def disable; @enabled = false end
|
38
|
+
|
39
|
+
# Clear all stubs.
|
40
|
+
#
|
41
|
+
def clear(klass = nil)
|
42
|
+
klass.nil? ? stubs.clear : stubs[klass].try(:clear)
|
43
|
+
end
|
44
|
+
|
45
|
+
def stubs
|
46
|
+
@stubs ||= {}
|
47
|
+
end
|
48
|
+
|
49
|
+
def stub_for(op)
|
50
|
+
return false unless (classes = stubs[op.resource])
|
51
|
+
return false unless (actions = classes[op.action])
|
52
|
+
|
53
|
+
params = op.params
|
54
|
+
params.merge! id: op.id unless op.id.nil?
|
55
|
+
actions.select! { |stub| stub[:with] == params || stub[:with] == op.data }
|
56
|
+
actions.first
|
57
|
+
end
|
58
|
+
|
59
|
+
def stubbed(op)
|
60
|
+
stub = stub_for op
|
61
|
+
unless stub
|
62
|
+
return false if allow_requests?
|
63
|
+
raise RealRequestNotAllowedError, "No stub found for `#{op.resource.name}` with params `#{op.params}`"
|
64
|
+
end
|
65
|
+
|
66
|
+
if (data = stub[:return])
|
67
|
+
op.callback.call data
|
68
|
+
elsif (err = stub[:raise])
|
69
|
+
raise_error op, err, stub[:return]
|
70
|
+
else
|
71
|
+
raise ArgumentError, 'Unsupported stub.'
|
72
|
+
end
|
73
|
+
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def raise_error(op, name, data)
|
79
|
+
raise name if name.is_a? Class
|
80
|
+
|
81
|
+
op.handle_failure ::Acfs::Response.new nil, status: Rack::Utils.status_code(name), data: data
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/acfs/version.rb
CHANGED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestMiddleware < Acfs::Middleware::Base
|
4
|
+
end
|
5
|
+
|
6
|
+
describe Acfs::Service::Middleware do
|
7
|
+
let(:srv_class) { Class.new(Acfs::Service) }
|
8
|
+
let(:options) { {} }
|
9
|
+
let(:service) { srv_class.new options }
|
10
|
+
let(:middleware) { TestMiddleware }
|
11
|
+
|
12
|
+
describe '.use' do
|
13
|
+
let(:options) { { abc: 'cde' } }
|
14
|
+
|
15
|
+
it 'should add middleware to list' do
|
16
|
+
srv_class.use middleware
|
17
|
+
|
18
|
+
expect(srv_class.instance_variable_get(:@middlewares)).to include(middleware)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should add middleware to stack' do
|
22
|
+
srv_class.use middleware
|
23
|
+
|
24
|
+
expect(srv_class.middleware).to be_a(middleware)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should instantiate middleware object' do
|
28
|
+
middleware.should_receive(:new).with(anything, options)
|
29
|
+
|
30
|
+
srv_class.use middleware, options
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '.clear' do
|
35
|
+
before { srv_class.use middleware }
|
36
|
+
|
37
|
+
it 'should clear middleware list' do
|
38
|
+
srv_class.clear
|
39
|
+
|
40
|
+
expect(srv_class.instance_variable_get(:@middlewares)).to be_empty
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should reset middleware stack' do
|
44
|
+
srv_class.clear
|
45
|
+
|
46
|
+
expect(srv_class.instance_variable_get(:@middleware)).to be_nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class SpecialCustomError < StandardError; end
|
4
|
+
|
5
|
+
describe Acfs::Stub do
|
6
|
+
let(:stub) { Class.new(Acfs::Stub) }
|
7
|
+
|
8
|
+
before(:all) { Acfs::Stub.enable }
|
9
|
+
after(:all) { Acfs::Stub.disable }
|
10
|
+
|
11
|
+
before do
|
12
|
+
Acfs::Stub.allow_requests = false
|
13
|
+
|
14
|
+
#Acfs::Stub.read(MyUser).with(id: 5).and_return({ id: 5, name: 'John', age: 32 })
|
15
|
+
#Acfs::Stub.read(MyUser).with(id: 6).and_raise(:not_found)
|
16
|
+
#Acfs::Stub.create(MyUser).with(name: '', age: 12).and_return(:invalid, errors: { name: [ 'must be present ']})
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '.resource' do
|
20
|
+
context 'with read action' do
|
21
|
+
before do
|
22
|
+
Acfs::Stub.resource MyUser, :read, with: { id: 1 }, return: { id: 1, name: 'John Smith', age: 32 }
|
23
|
+
Acfs::Stub.resource MyUser, :read, with: { id: 2 }, raise: SpecialCustomError
|
24
|
+
Acfs::Stub.resource MyUser, :read, with: { id: 3 }, raise: :not_found
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should allow to stub resource reads' do
|
28
|
+
user = MyUser.find 1
|
29
|
+
Acfs.run
|
30
|
+
|
31
|
+
expect(user.id).to be == 1
|
32
|
+
expect(user.name).to be == 'John Smith'
|
33
|
+
expect(user.age).to be == 32
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with error' do
|
37
|
+
it 'should allow to raise errors' do
|
38
|
+
MyUser.find 2
|
39
|
+
|
40
|
+
expect { Acfs.run }.to raise_error(SpecialCustomError)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should allow to raise symbolic errors' do
|
44
|
+
MyUser.find 3
|
45
|
+
|
46
|
+
expect { Acfs.run }.to raise_error(Acfs::ResourceNotFound)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'with create action' do
|
52
|
+
before do
|
53
|
+
Acfs::Stub.resource Session, :create, with: { ident: 'john@exmaple.org', password: 's3cr3t' }, return: { id: 'longhash', user: 1 }
|
54
|
+
Acfs::Stub.resource Session, :create, with: { ident: 'john@exmaple.org', password: 'wrong' }, raise: 422
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should allow stub resource creation' do
|
58
|
+
session = Session.create! ident: 'john@exmaple.org', password: 's3cr3t'
|
59
|
+
|
60
|
+
expect(session.id).to be == 'longhash'
|
61
|
+
expect(session.user).to be == 1
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should allow to raise error' do
|
65
|
+
expect {
|
66
|
+
Session.create! ident: 'john@exmaple.org', password: 'wrong'
|
67
|
+
}.to raise_error(::Acfs::InvalidResource)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '.allow_requests=' do
|
73
|
+
context 'when enabled' do
|
74
|
+
before do
|
75
|
+
Acfs::Stub.allow_requests = true
|
76
|
+
stub_request(:get, 'http://users.example.org/users/2').to_return response({ id: 2, name: 'John', age: 26 })
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should allow real requests' do
|
80
|
+
@user = MyUser.find 2
|
81
|
+
expect { Acfs.run }.to_not raise_error
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'when disabled' do
|
86
|
+
before do
|
87
|
+
Acfs::Stub.allow_requests = false
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should allow real requests' do
|
91
|
+
@user = MyUser.find 2
|
92
|
+
expect { Acfs.run }.to raise_error(Acfs::RealRequestNotAllowedError)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/spec/acfs_spec.rb
CHANGED
@@ -1,19 +1,18 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe
|
3
|
+
describe 'Acfs' do
|
4
4
|
|
5
5
|
before do
|
6
|
-
|
7
|
-
stub_request(:get,
|
8
|
-
stub_request(:get,
|
9
|
-
stub_request(:get,
|
10
|
-
stub_request(:get,
|
11
|
-
stub_request(:get,
|
12
|
-
stub_request(:get, "http://comments.example.org/comments?user=2").to_return response([{ id: 1, text: "Comment #1"}, { id: 2, text: "Comment #2" }])
|
6
|
+
stub_request(:get, 'http://users.example.org/users').to_return response([{ id: 1, name: 'Anon', age: 12 }, { id: 2, name: 'John', age: 26 }])
|
7
|
+
stub_request(:get, 'http://users.example.org/users/2').to_return response({ id: 2, name: 'John', age: 26 })
|
8
|
+
stub_request(:get, 'http://users.example.org/users/3').to_return response({ id: 3, name: 'Miraculix', age: 122 })
|
9
|
+
stub_request(:get, 'http://users.example.org/users/100').to_return response({ id:100, name: 'Jimmy', age: 45 })
|
10
|
+
stub_request(:get, 'http://users.example.org/users/2/friends').to_return response([{ id: 1, name: 'Anon', age: 12 }])
|
11
|
+
stub_request(:get, 'http://comments.example.org/comments?user=2').to_return response([{ id: 1, text: 'Comment #1' }, { id: 2, text: 'Comment #2' }])
|
13
12
|
end
|
14
13
|
|
15
14
|
it 'should update single resource synchronously' do
|
16
|
-
stub = stub_request(:put,
|
15
|
+
stub = stub_request(:put, 'http://users.example.org/users/2')
|
17
16
|
.to_return { |request| { body: request.body, headers: {'Content-Type' => request.headers['Content-Type']}} }
|
18
17
|
|
19
18
|
@user = MyUser.find 2
|
@@ -22,7 +21,7 @@ describe "Acfs" do
|
|
22
21
|
expect(@user).to_not be_changed
|
23
22
|
expect(@user).to be_persisted
|
24
23
|
|
25
|
-
@user.name =
|
24
|
+
@user.name = 'Johnny'
|
26
25
|
|
27
26
|
expect(@user).to be_changed
|
28
27
|
expect(@user).to_not be_persisted
|
@@ -35,7 +34,7 @@ describe "Acfs" do
|
|
35
34
|
end
|
36
35
|
|
37
36
|
it 'should create a single resource synchronously' do
|
38
|
-
stub = stub_request(:post,
|
37
|
+
stub = stub_request(:post, 'http://users.example.org/sessions').to_return response({id: 'sessionhash', user: 1})
|
39
38
|
|
40
39
|
session = Session.create ident: 'Anon'
|
41
40
|
|
@@ -104,7 +103,7 @@ describe "Acfs" do
|
|
104
103
|
end
|
105
104
|
|
106
105
|
it 'should load associated resources' do
|
107
|
-
pending
|
106
|
+
pending 'TODO: Implement high level feature'
|
108
107
|
|
109
108
|
@user = MyUser.find(2) do |user|
|
110
109
|
@friends = user.friends.all
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/response.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acfs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Graichen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-05-
|
11
|
+
date: 2013-05-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - '>='
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rack
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: bundler
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -113,9 +127,11 @@ files:
|
|
113
127
|
- gemfiles/Gemfile.rails-3-2
|
114
128
|
- gemfiles/Gemfile.rails-4-0
|
115
129
|
- lib/acfs.rb
|
130
|
+
- lib/acfs/adapter/base.rb
|
116
131
|
- lib/acfs/adapter/typhoeus.rb
|
117
132
|
- lib/acfs/collection.rb
|
118
133
|
- lib/acfs/errors.rb
|
134
|
+
- lib/acfs/global.rb
|
119
135
|
- lib/acfs/middleware/base.rb
|
120
136
|
- lib/acfs/middleware/json_decoder.rb
|
121
137
|
- lib/acfs/middleware/json_encoder.rb
|
@@ -132,18 +148,21 @@ files:
|
|
132
148
|
- lib/acfs/model/initialization.rb
|
133
149
|
- lib/acfs/model/loadable.rb
|
134
150
|
- lib/acfs/model/locatable.rb
|
151
|
+
- lib/acfs/model/operational.rb
|
135
152
|
- lib/acfs/model/persistence.rb
|
136
153
|
- lib/acfs/model/query_methods.rb
|
137
154
|
- lib/acfs/model/relations.rb
|
138
155
|
- lib/acfs/model/service.rb
|
156
|
+
- lib/acfs/operation.rb
|
139
157
|
- lib/acfs/request.rb
|
140
158
|
- lib/acfs/request/callbacks.rb
|
141
159
|
- lib/acfs/response.rb
|
142
160
|
- lib/acfs/response/formats.rb
|
143
161
|
- lib/acfs/response/status.rb
|
162
|
+
- lib/acfs/runner.rb
|
144
163
|
- lib/acfs/service.rb
|
145
164
|
- lib/acfs/service/middleware.rb
|
146
|
-
- lib/acfs/
|
165
|
+
- lib/acfs/stub.rb
|
147
166
|
- lib/acfs/version.rb
|
148
167
|
- spec/acfs/middleware/json_decoder_spec.rb
|
149
168
|
- spec/acfs/middleware/msgpack_decoder_spec.rb
|
@@ -158,7 +177,9 @@ files:
|
|
158
177
|
- spec/acfs/request_spec.rb
|
159
178
|
- spec/acfs/response/formats_spec.rb
|
160
179
|
- spec/acfs/response/status_spec.rb
|
180
|
+
- spec/acfs/service/middleware_spec.rb
|
161
181
|
- spec/acfs/service_spec.rb
|
182
|
+
- spec/acfs/stub_spec.rb
|
162
183
|
- spec/acfs_spec.rb
|
163
184
|
- spec/spec_helper.rb
|
164
185
|
- spec/support/response.rb
|
@@ -201,7 +222,9 @@ test_files:
|
|
201
222
|
- spec/acfs/request_spec.rb
|
202
223
|
- spec/acfs/response/formats_spec.rb
|
203
224
|
- spec/acfs/response/status_spec.rb
|
225
|
+
- spec/acfs/service/middleware_spec.rb
|
204
226
|
- spec/acfs/service_spec.rb
|
227
|
+
- spec/acfs/stub_spec.rb
|
205
228
|
- spec/acfs_spec.rb
|
206
229
|
- spec/spec_helper.rb
|
207
230
|
- spec/support/response.rb
|
@@ -1,27 +0,0 @@
|
|
1
|
-
module Acfs
|
2
|
-
class Service
|
3
|
-
|
4
|
-
# Methods to queue or executed request through this service.
|
5
|
-
#
|
6
|
-
module RequestHandler
|
7
|
-
|
8
|
-
# Queue a new request on global adapter queue.
|
9
|
-
#
|
10
|
-
def queue(request)
|
11
|
-
Acfs.adapter.queue prepare request
|
12
|
-
end
|
13
|
-
|
14
|
-
# Executes a request now.
|
15
|
-
#
|
16
|
-
def run(request)
|
17
|
-
Acfs.adapter.run prepare request
|
18
|
-
end
|
19
|
-
|
20
|
-
# Prepares a request to be processed through this service.
|
21
|
-
#
|
22
|
-
def prepare(request)
|
23
|
-
request
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|