acfs 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +0 -1
- data/.travis.yml +1 -3
- data/{LICENSE.txt → LICENSE} +0 -0
- data/README.md +45 -7
- data/acfs.gemspec +3 -0
- data/gemfiles/{Gemfile.rails-3-1 → Gemfile.rails-4-0} +2 -2
- data/lib/acfs.rb +49 -5
- data/lib/acfs/adapter/typhoeus.rb +42 -0
- data/lib/acfs/collection.rb +22 -0
- data/lib/acfs/middleware/base.rb +21 -0
- data/lib/acfs/middleware/json_decoder.rb +15 -0
- data/lib/acfs/middleware/print.rb +23 -0
- data/lib/acfs/model.rb +25 -12
- data/lib/acfs/{attributes.rb → model/attributes.rb} +36 -40
- data/lib/acfs/model/attributes/integer.rb +18 -0
- data/lib/acfs/model/attributes/string.rb +18 -0
- data/lib/acfs/{initialization.rb → model/initialization.rb} +4 -1
- data/lib/acfs/model/loadable.rb +18 -0
- data/lib/acfs/model/locatable.rb +24 -0
- data/lib/acfs/model/query_methods.rb +66 -0
- data/lib/acfs/model/relations.rb +10 -0
- data/lib/acfs/model/service.rb +29 -0
- data/lib/acfs/request.rb +33 -0
- data/lib/acfs/request/callbacks.rb +46 -0
- data/lib/acfs/response.rb +21 -0
- data/lib/acfs/response/formats.rb +12 -0
- data/lib/acfs/service.rb +30 -0
- data/lib/acfs/version.rb +1 -1
- data/spec/acfs/middleware/json_decoder_spec.rb +45 -0
- data/spec/acfs/request/callbacks_spec.rb +34 -0
- data/spec/acfs/request_spec.rb +71 -0
- data/spec/acfs_spec.rb +69 -0
- data/spec/{attributes_spec.rb → model/attributes_spec.rb} +25 -12
- data/spec/model/initialization_spec.rb +30 -0
- data/spec/model/locatable_spec.rb +15 -0
- data/spec/service_spec.rb +37 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/service.rb +24 -0
- metadata +89 -23
- data/lib/acfs/attributes/integer.rb +0 -15
- data/lib/acfs/attributes/string.rb +0 -15
- data/lib/acfs/client.rb +0 -15
- data/spec/client_spec.rb +0 -12
- data/spec/initialization_spec.rb +0 -22
- data/spec/support/my_client.rb +0 -12
@@ -0,0 +1,18 @@
|
|
1
|
+
module Acfs::Model
|
2
|
+
module Attributes
|
3
|
+
|
4
|
+
# Integer attribute type. Use it in your model as an attribute type:
|
5
|
+
#
|
6
|
+
# class User
|
7
|
+
# include Acfs::Model
|
8
|
+
# attribute :name, :integer
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
module Integer # :nodoc:
|
12
|
+
|
13
|
+
def self.cast(obj)
|
14
|
+
obj.to_i
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Acfs::Model
|
2
|
+
module Attributes
|
3
|
+
|
4
|
+
# String attribute type. Use it in your model as an attribute type:
|
5
|
+
#
|
6
|
+
# class User
|
7
|
+
# include Acfs::Model
|
8
|
+
# attribute :name, :string
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
module String # :nodoc:
|
12
|
+
|
13
|
+
def self.cast(obj)
|
14
|
+
obj.to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Acfs::Model
|
2
|
+
|
3
|
+
module Loadable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
# Check if model is loaded or if request is still queued.
|
7
|
+
#
|
8
|
+
def loaded?
|
9
|
+
!!@loaded
|
10
|
+
end
|
11
|
+
|
12
|
+
# Mark model as loaded.
|
13
|
+
#
|
14
|
+
def loaded!
|
15
|
+
@loaded = true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Acfs::Model
|
2
|
+
|
3
|
+
# Provide methods for generation URLs for resources.
|
4
|
+
#
|
5
|
+
# Example
|
6
|
+
# class User
|
7
|
+
# service AccountService # With base URL `http://acc.svr`
|
8
|
+
# end
|
9
|
+
# User.url #=> "http://acc.svr/users"
|
10
|
+
# User.url(5) #=> "http://acc.svr/users/5"
|
11
|
+
#
|
12
|
+
module Locatable
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
# Return URL for this resource.
|
18
|
+
#
|
19
|
+
def url(suffix = nil)
|
20
|
+
service.url_for(self, suffix: suffix)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Acfs::Model
|
2
|
+
|
3
|
+
# Methods providing the query interface for finding resouces.
|
4
|
+
#
|
5
|
+
# Example
|
6
|
+
# class MyUser
|
7
|
+
# include Acfs::Model
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# MyUser.find(5) # Find single resource
|
11
|
+
# MyUser.all # Full or partial collection of
|
12
|
+
# # resources
|
13
|
+
# Comment.where(user: user.id) # Collection with additional parameter
|
14
|
+
# # to filter resources
|
15
|
+
#
|
16
|
+
module QueryMethods
|
17
|
+
extend ActiveSupport::Concern
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
|
21
|
+
# Try to load a resource by given id.
|
22
|
+
#
|
23
|
+
# Example
|
24
|
+
# User.find(5) # Will query `http://base.url/users/5`
|
25
|
+
#
|
26
|
+
def find(id, options = {}, &block)
|
27
|
+
model = self.new
|
28
|
+
|
29
|
+
request = case id
|
30
|
+
when Hash
|
31
|
+
Acfs::Request.new url, params: id
|
32
|
+
else
|
33
|
+
Acfs::Request.new url(id.to_s)
|
34
|
+
end
|
35
|
+
|
36
|
+
service.queue(request) do |response|
|
37
|
+
model.attributes = response.data
|
38
|
+
model.loaded!
|
39
|
+
block.call model unless block.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
model
|
43
|
+
end
|
44
|
+
|
45
|
+
# Try to load all resources.
|
46
|
+
#
|
47
|
+
def all(params = {}, &block)
|
48
|
+
collection = ::Acfs::Collection.new
|
49
|
+
|
50
|
+
service.queue(Acfs::Request.new(url, params: params)) do |response|
|
51
|
+
response.data.each do |obj|
|
52
|
+
collection << self.new.tap do |m|
|
53
|
+
m.attributes = obj
|
54
|
+
m.loaded!
|
55
|
+
end
|
56
|
+
end
|
57
|
+
collection.loaded!
|
58
|
+
block.call collection unless block.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
collection
|
62
|
+
end
|
63
|
+
alias :where :all
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Acfs::Model
|
2
|
+
|
3
|
+
# Included by Acfs::Model. Allows a model to belong to a service.
|
4
|
+
#
|
5
|
+
module Service
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# Link to service this model belongs to. Connection settings like base URL
|
11
|
+
# are fetched from service. Return assigned service if no arguments are given.
|
12
|
+
#
|
13
|
+
# Example
|
14
|
+
# class AccountService < Acfs::Client
|
15
|
+
# self.base_url = 'http://acc.serv.org'
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# class MyUser
|
19
|
+
# service AccountService
|
20
|
+
# end
|
21
|
+
# MyUser.find 5 # Will fetch `http://acc.serv.org/users/5`
|
22
|
+
#
|
23
|
+
def service(klass = nil, options = {})
|
24
|
+
return @service unless klass
|
25
|
+
@service = klass.new options
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/acfs/request.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'acfs/request/callbacks'
|
2
|
+
|
3
|
+
module Acfs
|
4
|
+
|
5
|
+
class Request
|
6
|
+
attr_accessor :body, :format
|
7
|
+
attr_reader :url, :headers, :params, :data
|
8
|
+
|
9
|
+
include Request::Callbacks
|
10
|
+
|
11
|
+
def initialize(url, options = {})
|
12
|
+
@url = URI.parse(url).tap do |url|
|
13
|
+
@data = options.delete(:data) || nil
|
14
|
+
@format = options.delete(:format) || :json
|
15
|
+
@headers = options.delete(:headers) || {}
|
16
|
+
@params = options.delete(:params) || {}
|
17
|
+
|
18
|
+
url.query = nil # params.any? ? params.to_param : nil
|
19
|
+
end.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
def data?
|
23
|
+
!data.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def new(*attrs)
|
28
|
+
return attrs[0] if attrs[0].is_a? self
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Acfs
|
2
|
+
class Request
|
3
|
+
|
4
|
+
# Module containing callback handling for Requests.
|
5
|
+
# Current the only callback type is `on_complete`:
|
6
|
+
#
|
7
|
+
# request = Request.new 'URL'
|
8
|
+
# request.on_complete { |response| ... }
|
9
|
+
#
|
10
|
+
module Callbacks
|
11
|
+
|
12
|
+
# Add a new `on_complete` callback for this request.
|
13
|
+
#
|
14
|
+
# @example Set on_complete.
|
15
|
+
# request.on_complete { |response| print response.body }
|
16
|
+
#
|
17
|
+
# @param [ Block ] block The callback block to execute.
|
18
|
+
#
|
19
|
+
# @yield [ Acfs::Response ]
|
20
|
+
#
|
21
|
+
# @return [ Acfs::Request ] The request itself.
|
22
|
+
#
|
23
|
+
def on_complete(&block)
|
24
|
+
callbacks << block if block_given?
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return array of all callbacks.
|
29
|
+
#
|
30
|
+
# @return [ Array<Block> ] All callbacks.
|
31
|
+
#
|
32
|
+
def callbacks
|
33
|
+
@callbacks ||= []
|
34
|
+
end
|
35
|
+
|
36
|
+
# Trigger all callback for given response.
|
37
|
+
#
|
38
|
+
# @return [ Acfs::Request ] The request itself.
|
39
|
+
#
|
40
|
+
def complete!(response)
|
41
|
+
callbacks.each { |cb| cb.call response }
|
42
|
+
self
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'acfs/response/formats'
|
2
|
+
|
3
|
+
module Acfs
|
4
|
+
|
5
|
+
# This represents a response. In addition to an standard HTTP
|
6
|
+
# it has a field `data` for storing the encoded body.
|
7
|
+
#
|
8
|
+
class Response
|
9
|
+
attr_accessor :data
|
10
|
+
attr_reader :request, :status, :headers, :body
|
11
|
+
|
12
|
+
include Response::Formats
|
13
|
+
|
14
|
+
def initialize(request, status = 200, headers = {}, body = nil)
|
15
|
+
@request = request
|
16
|
+
@status = status
|
17
|
+
@headers = headers
|
18
|
+
@body = body
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/acfs/service.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Acfs
|
2
|
+
|
3
|
+
# Service object.
|
4
|
+
#
|
5
|
+
class Service
|
6
|
+
attr_accessor :options
|
7
|
+
class_attribute :base_url
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def options
|
14
|
+
@options
|
15
|
+
end
|
16
|
+
|
17
|
+
def url_for(resource_class, options = {})
|
18
|
+
options.reverse_merge! self.options
|
19
|
+
|
20
|
+
url = self.class.base_url.to_s
|
21
|
+
url += "/#{(options[:path] || resource_class.name.pluralize.underscore).to_s}"
|
22
|
+
url += "/#{options[:suffix].to_s}" if options[:suffix]
|
23
|
+
url
|
24
|
+
end
|
25
|
+
|
26
|
+
def queue(request, &block)
|
27
|
+
Acfs.queue request, &block
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/acfs/version.rb
CHANGED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Acfs::Middleware::JsonDecoder do
|
4
|
+
let(:data) { [{id: 1, name: "Anon"},{id: 2, name:"John", friends: [ 1 ]}] }
|
5
|
+
let(:body) { data.to_param }
|
6
|
+
let(:headers) { {} }
|
7
|
+
let(:request) { Acfs::Request.new "fubar" }
|
8
|
+
let(:response) { Acfs::Response.new request, 200, headers, body }
|
9
|
+
let(:decoder) { Acfs::Middleware::JsonDecoder.new lambda { |req| req } }
|
10
|
+
|
11
|
+
before do
|
12
|
+
decoder.call request
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'with JSON response' do
|
16
|
+
let(:headers) { { 'Content-Type' => 'application/json' } }
|
17
|
+
let(:body) { data.to_json }
|
18
|
+
|
19
|
+
it 'should decode body data' do
|
20
|
+
request.complete! response
|
21
|
+
|
22
|
+
expect(response.data).to be == data.map(&:stringify_keys)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'with invalid JSON response' do
|
27
|
+
let(:headers) { { 'Content-Type' => 'application/json' } }
|
28
|
+
let(:body) { data.to_json[4..-4] }
|
29
|
+
|
30
|
+
it 'should raise an error' do
|
31
|
+
expect { request.complete! response }.to raise_error(MultiJson::LoadError)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'without JSON response' do
|
36
|
+
let(:headers) { { 'Content-Type' => 'application/text' } }
|
37
|
+
let(:body) { data.to_json }
|
38
|
+
|
39
|
+
it 'should not decode non-JSON encoded responses' do
|
40
|
+
request.complete! response
|
41
|
+
|
42
|
+
expect(response.data).to be_nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Acfs::Request::Callbacks do
|
4
|
+
let(:callback) { lambda { |res| } }
|
5
|
+
let(:request) { Acfs::Request.new('fubar') }
|
6
|
+
|
7
|
+
describe '#on_complete' do
|
8
|
+
it 'should store a given callback' do
|
9
|
+
request.on_complete &callback
|
10
|
+
|
11
|
+
expect(request.callbacks).to have(1).item
|
12
|
+
expect(request.callbacks[0]).to be == callback
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should store multiple callback' do
|
16
|
+
request.on_complete { |res| "abc" }
|
17
|
+
request.on_complete &callback
|
18
|
+
|
19
|
+
expect(request.callbacks).to have(2).item
|
20
|
+
expect(request.callbacks[1]).to be == callback
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#complete!' do
|
25
|
+
let(:response) { Acfs::Response.new(request) }
|
26
|
+
|
27
|
+
it 'should trigger registered callbacks with given response' do
|
28
|
+
callback.should_receive(:call).with(response)
|
29
|
+
|
30
|
+
request.on_complete &callback
|
31
|
+
request.complete! response
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|