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
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/{LICENSE.txt → LICENSE}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Acfs - *API
|
1
|
+
# Acfs - *API client for services*
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/acfs.png)](http://badge.fury.io/rb/acfs) [![Build Status](https://travis-ci.org/jgraichen/acfs.png?branch=master)](https://travis-ci.org/jgraichen/acfs) [![Coverage Status](https://coveralls.io/repos/jgraichen/acfs/badge.png?branch=master)](https://coveralls.io/r/jgraichen/acfs) [![Code Climate](https://codeclimate.com/github/jgraichen/acfs.png)](https://codeclimate.com/github/jgraichen/acfs) [![Dependency Status](https://gemnasium.com/jgraichen/acfs.png)](https://gemnasium.com/jgraichen/acfs)
|
4
4
|
|
@@ -10,7 +10,10 @@ TODO: Write a gem description
|
|
10
10
|
|
11
11
|
Add this line to your application's Gemfile:
|
12
12
|
|
13
|
-
gem 'acfs'
|
13
|
+
gem 'acfs', '0.3.0'
|
14
|
+
|
15
|
+
**Note:** Acfs is under development. API may change anytime. No semantic versioning until version `1.0`. Version `1.0`
|
16
|
+
does not mean complete feature set but stable basic code base.
|
14
17
|
|
15
18
|
And then execute:
|
16
19
|
|
@@ -22,13 +25,11 @@ Or install it yourself as:
|
|
22
25
|
|
23
26
|
## Usage
|
24
27
|
|
25
|
-
|
26
|
-
|
27
|
-
### Acfs::Attributes
|
28
|
+
### Attributes
|
28
29
|
|
29
30
|
```ruby
|
30
31
|
class MyModel
|
31
|
-
include Acfs::
|
32
|
+
include Acfs::Model
|
32
33
|
|
33
34
|
attribute :name, :string
|
34
35
|
attribute :age, :integer, default: 15
|
@@ -41,12 +42,43 @@ mo.name # => "Johnny"
|
|
41
42
|
mo.age = '13'
|
42
43
|
mo.age # => 13
|
43
44
|
mo.attributes # => { "name" => "Johnny", "age" => 13 }
|
45
|
+
```
|
46
|
+
|
47
|
+
### Service, Model & Collection
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class MyService < Acfs::Service
|
51
|
+
self.base_url = 'http://acc.srv'
|
52
|
+
end
|
53
|
+
|
54
|
+
class User
|
55
|
+
include Acfs::Model
|
56
|
+
service MyService
|
57
|
+
|
58
|
+
attribute :id, :integer
|
59
|
+
end
|
44
60
|
|
61
|
+
@user = User.find 14
|
62
|
+
|
63
|
+
@user.loaded? #=> false
|
64
|
+
|
65
|
+
Acfs.run # This will run all queued request as parallel as possible.
|
66
|
+
# For @user the following URL will be requested:
|
67
|
+
# `http://acc.srv/users/14`
|
68
|
+
|
69
|
+
@model.name # => "..."
|
70
|
+
|
71
|
+
@users = User.all
|
72
|
+
@users.loaded? #=> false
|
73
|
+
|
74
|
+
Acfs.run # Will request `http://acc.srv/users`
|
75
|
+
|
76
|
+
@users #=> [<User>, ...]
|
45
77
|
```
|
46
78
|
|
47
79
|
## TODO
|
48
80
|
|
49
|
-
* Library
|
81
|
+
* Develop Library
|
50
82
|
* Documentation
|
51
83
|
|
52
84
|
## Contributing
|
@@ -58,3 +90,9 @@ mo.attributes # => { "name" => "Johnny", "age" => 13 }
|
|
58
90
|
5. Commit your changes (`git commit -am 'Add some feature'`)
|
59
91
|
6. Push to the branch (`git push origin my-new-feature`)
|
60
92
|
7. Create new Pull Request
|
93
|
+
|
94
|
+
## License
|
95
|
+
|
96
|
+
MIT License
|
97
|
+
|
98
|
+
Copyright (c) 2013 Jan Graichen. MIT license, see LICENSE for more details.
|
data/acfs.gemspec
CHANGED
@@ -20,8 +20,11 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'activesupport'
|
22
22
|
spec.add_runtime_dependency 'activemodel'
|
23
|
+
spec.add_runtime_dependency 'multi_json'
|
24
|
+
spec.add_runtime_dependency 'typhoeus'
|
23
25
|
|
24
26
|
spec.add_development_dependency 'bundler', '~> 1.3'
|
27
|
+
spec.add_development_dependency 'webmock', '~> 1.7'
|
25
28
|
spec.add_development_dependency 'rake'
|
26
29
|
spec.add_development_dependency 'rspec'
|
27
30
|
spec.add_development_dependency 'guard-rspec'
|
data/lib/acfs.rb
CHANGED
@@ -1,14 +1,58 @@
|
|
1
1
|
require 'active_support'
|
2
|
+
require 'active_support/core_ext'
|
2
3
|
require 'acfs/version'
|
3
4
|
|
4
5
|
module Acfs
|
5
6
|
extend ActiveSupport::Autoload
|
6
7
|
|
7
|
-
autoload :
|
8
|
-
autoload :Base
|
9
|
-
|
8
|
+
autoload :Collection
|
10
9
|
autoload :Model
|
11
|
-
autoload :
|
12
|
-
autoload :
|
10
|
+
autoload :Request
|
11
|
+
autoload :Response
|
12
|
+
autoload :Service
|
13
|
+
|
14
|
+
module Middleware
|
15
|
+
extend ActiveSupport::Autoload
|
16
|
+
|
17
|
+
autoload :Base
|
18
|
+
autoload :Print
|
19
|
+
autoload :JsonDecoder
|
20
|
+
end
|
21
|
+
|
22
|
+
module Adapter
|
23
|
+
extend ActiveSupport::Autoload
|
24
|
+
|
25
|
+
autoload :Typhoeus
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
|
30
|
+
# Run all queued
|
31
|
+
def run
|
32
|
+
adapter.run
|
33
|
+
end
|
34
|
+
|
35
|
+
def queue(req, &block)
|
36
|
+
request = middleware.call Request.new(req)
|
37
|
+
request.on_complete &block if block_given?
|
38
|
+
adapter.queue request
|
39
|
+
end
|
40
|
+
|
41
|
+
def adapter
|
42
|
+
@adapter ||= Adapter::Typhoeus.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def middleware
|
46
|
+
@middleware ||= lambda { |request| request }
|
47
|
+
end
|
48
|
+
|
49
|
+
def use(klass, options = {})
|
50
|
+
@middlewares ||= []
|
51
|
+
|
52
|
+
return false if @middlewares.include? klass
|
13
53
|
|
54
|
+
@middlewares << klass
|
55
|
+
@middleware = klass.new(middleware, options)
|
56
|
+
end
|
57
|
+
end
|
14
58
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'typhoeus'
|
2
|
+
|
3
|
+
module Acfs
|
4
|
+
module Adapter
|
5
|
+
|
6
|
+
# Adapter for Typhoeus.
|
7
|
+
#
|
8
|
+
class Typhoeus
|
9
|
+
|
10
|
+
# Run all queued requests.
|
11
|
+
#
|
12
|
+
def run
|
13
|
+
hydra.run
|
14
|
+
end
|
15
|
+
|
16
|
+
# Add a new request or URL to the queue.
|
17
|
+
#
|
18
|
+
def queue(req)
|
19
|
+
hydra.queue convert_request(req)
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
def hydra
|
24
|
+
@hydra ||= ::Typhoeus::Hydra.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def convert_request(req)
|
28
|
+
request = ::Typhoeus::Request.new req.url, params: req.params, headers: req.headers, body: req.body
|
29
|
+
|
30
|
+
request.on_complete do |response|
|
31
|
+
req.complete! convert_response(req, response)
|
32
|
+
end
|
33
|
+
|
34
|
+
request
|
35
|
+
end
|
36
|
+
|
37
|
+
def convert_response(request, response)
|
38
|
+
Acfs::Response.new(request, response.code, response.headers, response.body)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
require 'acfs/model/loadable'
|
4
|
+
|
5
|
+
module Acfs
|
6
|
+
|
7
|
+
class Collection < ::Delegator
|
8
|
+
include Model::Loadable
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super([])
|
12
|
+
end
|
13
|
+
|
14
|
+
def __getobj__
|
15
|
+
@models
|
16
|
+
end
|
17
|
+
|
18
|
+
def __setobj__(obj)
|
19
|
+
@models = obj
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Acfs
|
2
|
+
module Middleware
|
3
|
+
|
4
|
+
# A base middleware that does not modify request or response.
|
5
|
+
# Can be used as super class for custom middleware implementations.
|
6
|
+
#
|
7
|
+
class Base
|
8
|
+
attr_reader :app, :options
|
9
|
+
|
10
|
+
def initialize(app, options = {})
|
11
|
+
@app = app
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(request)
|
16
|
+
request.on_complete { |res| response(res) } if respond_to? :response
|
17
|
+
app.call(request)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module Acfs
|
4
|
+
module Middleware
|
5
|
+
|
6
|
+
# A middleware to automatically decode JSON responses.
|
7
|
+
#
|
8
|
+
class JsonDecoder < Base
|
9
|
+
|
10
|
+
def response(response)
|
11
|
+
response.data = ::MultiJson.load(response.body) if response.json?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Acfs
|
2
|
+
module Middleware
|
3
|
+
|
4
|
+
# Print resquests and response on terminal
|
5
|
+
#
|
6
|
+
class Print < Base
|
7
|
+
|
8
|
+
def call(req)
|
9
|
+
puts '-' * 80
|
10
|
+
puts req.inspect
|
11
|
+
puts '-' * 80
|
12
|
+
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def response(res)
|
17
|
+
puts '-' * 80
|
18
|
+
puts res.inspect
|
19
|
+
puts '-' * 80
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/acfs/model.rb
CHANGED
@@ -1,22 +1,35 @@
|
|
1
1
|
require 'active_model'
|
2
2
|
|
3
|
+
require 'acfs/model/attributes'
|
4
|
+
require 'acfs/model/loadable'
|
5
|
+
require 'acfs/model/locatable'
|
6
|
+
require 'acfs/model/query_methods'
|
7
|
+
require 'acfs/model/relations'
|
8
|
+
require 'acfs/model/service'
|
9
|
+
|
3
10
|
module Acfs
|
4
11
|
module Model
|
5
|
-
|
6
|
-
base.class_eval do
|
7
|
-
if ActiveModel::VERSION::MAJOR >= 4
|
8
|
-
include ActiveModel::Model
|
9
|
-
else
|
10
|
-
extend ActiveModel::Naming
|
11
|
-
extend ActiveModel::Translation
|
12
|
-
include ActiveModel::Conversion
|
13
|
-
include ActiveModel::Validations
|
12
|
+
extend ActiveSupport::Concern
|
14
13
|
|
15
|
-
|
16
|
-
|
14
|
+
included do
|
15
|
+
if ActiveModel::VERSION::MAJOR >= 4
|
16
|
+
include ActiveModel::Model
|
17
|
+
else
|
18
|
+
extend ActiveModel::Naming
|
19
|
+
extend ActiveModel::Translation
|
20
|
+
include ActiveModel::Conversion
|
21
|
+
include ActiveModel::Validations
|
17
22
|
|
18
|
-
|
23
|
+
require 'acfs/model/initialization'
|
24
|
+
include Model::Initialization
|
19
25
|
end
|
26
|
+
|
27
|
+
include Model::Attributes
|
28
|
+
include Model::Loadable
|
29
|
+
include Model::Locatable
|
30
|
+
include Model::QueryMethods
|
31
|
+
include Model::Relations
|
32
|
+
include Model::Service
|
20
33
|
end
|
21
34
|
end
|
22
35
|
end
|
@@ -1,12 +1,11 @@
|
|
1
|
-
module Acfs
|
1
|
+
module Acfs::Model
|
2
2
|
|
3
|
-
# == Acfs
|
3
|
+
# == Acfs Attributes
|
4
4
|
#
|
5
5
|
# Allows to specify attributes of a class with default values and type safety.
|
6
6
|
#
|
7
7
|
# class User
|
8
|
-
# include Acfs::
|
9
|
-
#
|
8
|
+
# include Acfs::Model
|
10
9
|
# attribute :name, :string, default: 'Anon'
|
11
10
|
# attribute :age, :integer
|
12
11
|
# attribute :special, My::Special::Type
|
@@ -16,30 +15,31 @@ module Acfs
|
|
16
15
|
# type casted when set.
|
17
16
|
#
|
18
17
|
module Attributes
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
extend ActiveSupport::Concern
|
19
|
+
|
20
|
+
def initialize(*attrs) # :nodoc:
|
21
|
+
self.class.attributes.each { |k, v| public_send :"#{k}=", v }
|
22
|
+
super *attrs
|
24
23
|
end
|
25
24
|
|
26
|
-
|
25
|
+
# Returns ActiveModel compatible list of attributes and values.
|
26
|
+
#
|
27
|
+
# class User
|
28
|
+
# include Acfs::Model
|
29
|
+
# attribute :name, type: String, default: 'Anon'
|
30
|
+
# end
|
31
|
+
# user = User.new(name: 'John')
|
32
|
+
# user.attributes # => { "name" => "John" }
|
33
|
+
#
|
34
|
+
def attributes
|
35
|
+
self.class.attributes.keys.inject({}) { |h, k| h[k.to_s] = public_send k; h }
|
36
|
+
end
|
27
37
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
# Returns ActiveModel compatible list of attributes and values.
|
34
|
-
#
|
35
|
-
# class User
|
36
|
-
# attribute :name, type: String, default: 'Anon'
|
37
|
-
# end
|
38
|
-
# user = User.new(name: 'John')
|
39
|
-
# user.attributes # => { "name" => "John" }
|
40
|
-
#
|
41
|
-
def attributes
|
42
|
-
self.class.attributes.keys.inject({}) { |h, k| h[k.to_s] = send k; h }
|
38
|
+
# Update all attributes with given hash.
|
39
|
+
#
|
40
|
+
def attributes=(attributes)
|
41
|
+
self.attributes.each do |key, _|
|
42
|
+
send :"#{key}=", attributes[key] if attributes.has_key? key
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
@@ -49,29 +49,26 @@ module Acfs
|
|
49
49
|
# setter for given attribute name. Existing methods will be overridden.
|
50
50
|
#
|
51
51
|
# class User
|
52
|
-
#
|
52
|
+
# include Acfs::Model
|
53
|
+
# attribute :name, :string, default: 'Anon'
|
53
54
|
# end
|
54
55
|
#
|
55
|
-
# Available types can be found in `Acfs::Attributes::*`.
|
56
|
+
# Available types can be found in `Acfs::Model::Attributes::*`.
|
56
57
|
#
|
57
|
-
def attribute(
|
58
|
-
opts = attrs.extract_options!
|
59
|
-
type = opts.delete(:type) || :string
|
60
|
-
|
58
|
+
def attribute(name, type, opts = {})
|
61
59
|
if type.is_a? Symbol or type.is_a? String
|
62
|
-
type = "::Acfs::Attributes::#{type.to_s.classify}".constantize
|
60
|
+
type = "::Acfs::Model::Attributes::#{type.to_s.classify}".constantize
|
63
61
|
end
|
64
62
|
|
65
|
-
|
66
|
-
define_attribute attr.to_sym, type, opts
|
67
|
-
end
|
63
|
+
define_attribute name.to_sym, type, opts
|
68
64
|
end
|
69
65
|
|
70
66
|
# Return list of possible attributes and default values for this model class.
|
71
67
|
#
|
72
68
|
# class User
|
73
|
-
#
|
74
|
-
# attribute :
|
69
|
+
# include Acfs::Model
|
70
|
+
# attribute :name, :string
|
71
|
+
# attribute :age, :integer, default: 25
|
75
72
|
# end
|
76
73
|
# User.attributes # => { "name": nil, "age": 25 }
|
77
74
|
#
|
@@ -79,10 +76,9 @@ module Acfs
|
|
79
76
|
@attributes ||= {}
|
80
77
|
end
|
81
78
|
|
82
|
-
|
83
79
|
private
|
84
80
|
def define_attribute(name, type, opts = {}) # :nodoc:
|
85
|
-
@attributes
|
81
|
+
@attributes ||= {}
|
86
82
|
@attributes[name] = type.cast opts.has_key?(:default) ? opts[:default] : nil
|
87
83
|
|
88
84
|
self.send :define_method, name do
|
@@ -101,5 +97,5 @@ end
|
|
101
97
|
#
|
102
98
|
Dir[File.dirname(__FILE__) + "/attributes/*.rb"].sort.each do |path|
|
103
99
|
filename = File.basename(path)
|
104
|
-
require "acfs/attributes/#{filename}"
|
100
|
+
require "acfs/model/attributes/#{filename}"
|
105
101
|
end
|