acfs 0.2.0 → 0.3.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.
- 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
|