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.
Files changed (45) hide show
  1. data/.gitignore +0 -1
  2. data/.travis.yml +1 -3
  3. data/{LICENSE.txt → LICENSE} +0 -0
  4. data/README.md +45 -7
  5. data/acfs.gemspec +3 -0
  6. data/gemfiles/{Gemfile.rails-3-1 → Gemfile.rails-4-0} +2 -2
  7. data/lib/acfs.rb +49 -5
  8. data/lib/acfs/adapter/typhoeus.rb +42 -0
  9. data/lib/acfs/collection.rb +22 -0
  10. data/lib/acfs/middleware/base.rb +21 -0
  11. data/lib/acfs/middleware/json_decoder.rb +15 -0
  12. data/lib/acfs/middleware/print.rb +23 -0
  13. data/lib/acfs/model.rb +25 -12
  14. data/lib/acfs/{attributes.rb → model/attributes.rb} +36 -40
  15. data/lib/acfs/model/attributes/integer.rb +18 -0
  16. data/lib/acfs/model/attributes/string.rb +18 -0
  17. data/lib/acfs/{initialization.rb → model/initialization.rb} +4 -1
  18. data/lib/acfs/model/loadable.rb +18 -0
  19. data/lib/acfs/model/locatable.rb +24 -0
  20. data/lib/acfs/model/query_methods.rb +66 -0
  21. data/lib/acfs/model/relations.rb +10 -0
  22. data/lib/acfs/model/service.rb +29 -0
  23. data/lib/acfs/request.rb +33 -0
  24. data/lib/acfs/request/callbacks.rb +46 -0
  25. data/lib/acfs/response.rb +21 -0
  26. data/lib/acfs/response/formats.rb +12 -0
  27. data/lib/acfs/service.rb +30 -0
  28. data/lib/acfs/version.rb +1 -1
  29. data/spec/acfs/middleware/json_decoder_spec.rb +45 -0
  30. data/spec/acfs/request/callbacks_spec.rb +34 -0
  31. data/spec/acfs/request_spec.rb +71 -0
  32. data/spec/acfs_spec.rb +69 -0
  33. data/spec/{attributes_spec.rb → model/attributes_spec.rb} +25 -12
  34. data/spec/model/initialization_spec.rb +30 -0
  35. data/spec/model/locatable_spec.rb +15 -0
  36. data/spec/service_spec.rb +37 -0
  37. data/spec/spec_helper.rb +2 -0
  38. data/spec/support/service.rb +24 -0
  39. metadata +89 -23
  40. data/lib/acfs/attributes/integer.rb +0 -15
  41. data/lib/acfs/attributes/string.rb +0 -15
  42. data/lib/acfs/client.rb +0 -15
  43. data/spec/client_spec.rb +0 -12
  44. data/spec/initialization_spec.rb +0 -22
  45. 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
@@ -1,4 +1,7 @@
1
- module Acfs
1
+ module Acfs::Model
2
+
3
+ # Initialization drop-in for pre-4.0 ActiveModel.
4
+ #
2
5
  module Initialization
3
6
 
4
7
  # Initializes a new model with the given +params+.
@@ -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,10 @@
1
+ module Acfs::Model
2
+
3
+ module Relations
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+
8
+ end
9
+ end
10
+ 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
@@ -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
@@ -0,0 +1,12 @@
1
+ module Acfs
2
+ class Response
3
+
4
+ # Quick accessors for format handling.
5
+ module Formats
6
+
7
+ def json?
8
+ headers['Content-Type'] == 'application/json'
9
+ end
10
+ end
11
+ end
12
+ end
@@ -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
@@ -1,7 +1,7 @@
1
1
  module Acfs
2
2
  module VERSION
3
3
  MAJOR = 0
4
- MINOR = 2
4
+ MINOR = 3
5
5
  PATCH = 0
6
6
  STAGE = nil
7
7
 
@@ -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