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
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
|
[](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
|
|
@@ -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
|