api-client 1.5.0 → 1.5.1
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/CHANGELOG.md +6 -1
- data/README.md +21 -11
- data/examples/controllers/user_controller.rb +1 -1
- data/examples/models/user.rb +4 -0
- data/lib/api-client/base.rb +56 -142
- data/lib/api-client/dispatcher.rb +19 -11
- data/lib/api-client/errors.rb +11 -2
- data/lib/api-client/parser.rb +15 -14
- data/lib/api-client/version.rb +1 -1
- data/spec/api-client/base_spec.rb +20 -0
- data/spec/api-client/dispatcher_spec.rb +65 -15
- data/spec/api-client/errors_spec.rb +12 -2
- data/spec/api-client/parser_spec.rb +74 -22
- metadata +8 -7
- data/spec/api-client/base/delete_spec.rb +0 -134
- data/spec/api-client/base/get_spec.rb +0 -134
- data/spec/api-client/base/patch_spec.rb +0 -134
- data/spec/api-client/base/post_spec.rb +0 -134
- data/spec/api-client/base/put_spec.rb +0 -134
data/CHANGELOG.md
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## v1.5.0
|
4
|
+
|
5
|
+
* Handler of arrays as response.
|
6
|
+
* Method to redefine response object added.
|
7
|
+
|
3
8
|
## v1.4.1
|
4
9
|
|
5
10
|
* ActiveModel compatibility for errors added. make all error keys symbolic.
|
6
11
|
|
7
12
|
## v1.4.0
|
8
13
|
|
9
|
-
* functionality to validate on client side using ApiClient::Base added
|
14
|
+
* functionality to validate on client side using ApiClient::Base added.
|
10
15
|
|
11
16
|
## v1.3.0
|
12
17
|
|
data/README.md
CHANGED
@@ -21,32 +21,42 @@ Or install it yourself as:
|
|
21
21
|
|
22
22
|
Add this to your ApplicationController:
|
23
23
|
|
24
|
-
|
24
|
+
```ruby
|
25
|
+
rescue_from ApiClient::Exceptions::NotFound, :with => :not_found
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
def not_found
|
28
|
+
#Specify your own behavior here
|
29
|
+
end
|
30
|
+
```
|
29
31
|
|
30
32
|
You can define a more generic rescue that will work for any error:
|
31
33
|
|
32
|
-
|
34
|
+
```ruby
|
35
|
+
rescue_from ApiClient::Exceptions::Generic, :with => :generic_error
|
36
|
+
```
|
33
37
|
|
34
38
|
On Your model, extend ApiClient::Base
|
35
39
|
|
36
|
-
|
40
|
+
```ruby
|
41
|
+
class User < ApiClient::Base
|
42
|
+
```
|
37
43
|
|
38
44
|
Then, on your action, just put into it:
|
39
45
|
|
40
|
-
|
46
|
+
```ruby
|
47
|
+
@user = User.get("http://api.example.com/user/3")
|
48
|
+
```
|
41
49
|
|
42
50
|
## Advanced Usage
|
43
51
|
|
44
52
|
ApiClient can read api responses with root nodes based on the name of the virtual class.
|
45
53
|
In Some cases, that is not the required behavior. To Redefine it, use remote_object method:
|
46
54
|
|
47
|
-
|
48
|
-
|
49
|
-
|
55
|
+
```ruby
|
56
|
+
class Admin < ApiClient::Base
|
57
|
+
self.remote_object = "user"
|
58
|
+
end
|
59
|
+
```
|
50
60
|
|
51
61
|
## TODO
|
52
62
|
* Add Support to Typhoeus and Faraday
|
@@ -59,4 +69,4 @@ In Some cases, that is not the required behavior. To Redefine it, use remote_obj
|
|
59
69
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
60
70
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
61
71
|
4. Push to the branch (`git push origin my-new-feature`)
|
62
|
-
5. Create new Pull Request
|
72
|
+
5. Create new Pull Request
|
@@ -2,7 +2,7 @@ class UserController < ApplicationController
|
|
2
2
|
# It will works with respond_with.
|
3
3
|
# Your action should looks like any other one: A model with a method call. =D
|
4
4
|
def index
|
5
|
-
@
|
5
|
+
@users = User.get("api.example.com/users")
|
6
6
|
respond_with(@users)
|
7
7
|
end
|
8
8
|
end
|
data/examples/models/user.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
class User < ApiClient::Base
|
2
2
|
# Any of this fields can be called to manage rails form.
|
3
3
|
attr_accessor :id, :email, :password, :password_confirmation
|
4
|
+
|
5
|
+
# Validations will work as well
|
6
|
+
validates :email, :presence => true, :uniqueness => true
|
7
|
+
validates :password, :confirmation => true
|
4
8
|
end
|
data/lib/api-client/base.rb
CHANGED
@@ -1,155 +1,69 @@
|
|
1
1
|
require "active_model"
|
2
2
|
|
3
|
-
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
class
|
10
|
-
|
11
|
-
|
12
|
-
|
3
|
+
module ApiClient
|
4
|
+
# ApiClient::Base provides a way to make easy api requests as well as making possible to use it inside rails.
|
5
|
+
# A possible implementation:
|
6
|
+
# class Car < ApiClient::Base
|
7
|
+
# attr_accessor :color, :name, :year
|
8
|
+
# end
|
9
|
+
# This class will handle Rails form as well as it works with respond_with.
|
10
|
+
class Base
|
11
|
+
include ActiveModel::Validations
|
12
|
+
include ActiveModel::Conversion
|
13
|
+
extend ActiveModel::Naming
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
attributes.each do |name, value|
|
23
|
-
send("#{name}=", value)
|
15
|
+
# Initialize an object based on a hash of attributes.
|
16
|
+
#
|
17
|
+
# @param [Hash] attributes object attributes.
|
18
|
+
# @return [Base] the object initialized.
|
19
|
+
def initialize(attributes = {})
|
20
|
+
attributes.each do |name, value|
|
21
|
+
send("#{name}=", value)
|
22
|
+
end
|
24
23
|
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# Return if a object is persisted on the database or not.
|
28
|
-
#
|
29
|
-
# @return [False] always return false.
|
30
|
-
def persisted?
|
31
|
-
false
|
32
|
-
end
|
33
|
-
|
34
|
-
# Return the hash of errors if existent, otherwise instantiate a new ApiClient::Errors object with self.
|
35
|
-
#
|
36
|
-
# @return [ApiClient::Errors] the validation errors.
|
37
|
-
def errors
|
38
|
-
@errors ||= ApiClient::Errors.new(self)
|
39
|
-
end
|
40
24
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
# Make a get request and returns a new instance with te response attributes.
|
49
|
-
#
|
50
|
-
# @param [String] url of the api request.
|
51
|
-
# @param [Hash] header attributes of the request.
|
52
|
-
# @return [Base] a new object initialized with the response.
|
53
|
-
# @raise [ApiClient::Exceptions::ConnectionRefused] The request was refused by the api.
|
54
|
-
# @raise [ApiClient::Exceptions::Unauthorized] The request requires user authentication.
|
55
|
-
# @raise [ApiClient::Exceptions::Forbidden] The request was refused.
|
56
|
-
# @raise [ApiClient::Exceptions::NotFound] The request uri has not be found.
|
57
|
-
# @raise [ApiClient::Exceptions::InternalServerError] The request was not fulfilled by the api.
|
58
|
-
# @raise [ApiClient::Exceptions::BadGateway] The request was not fulfilled by the api.
|
59
|
-
# @raise [ApiClient::Exceptions::ServiceUnavailable] The api was unable to handle the request.
|
60
|
-
def self.get(url = '', header = {})
|
61
|
-
dispatch { _get(url, header) }
|
62
|
-
end
|
63
|
-
|
64
|
-
# Make a post request and returns a new instance with te response attributes.
|
65
|
-
#
|
66
|
-
# @param [String] url of the api request.
|
67
|
-
# @param [Hash] args attributes of object.
|
68
|
-
# @param [Hash] header attributes of the request.
|
69
|
-
# @return [Base] a new object initialized with the response.
|
70
|
-
# @raise [ApiClient::Exceptions::ConnectionRefused] The request was refused by the api.
|
71
|
-
# @raise [ApiClient::Exceptions::Unauthorized] The request requires user authentication.
|
72
|
-
# @raise [ApiClient::Exceptions::Forbidden] The request was refused.
|
73
|
-
# @raise [ApiClient::Exceptions::NotFound] The request uri has not be found.
|
74
|
-
# @raise [ApiClient::Exceptions::InternalServerError] The request was not fulfilled by the api.
|
75
|
-
# @raise [ApiClient::Exceptions::BadGateway] The request was not fulfilled by the api.
|
76
|
-
# @raise [ApiClient::Exceptions::ServiceUnavailable] The api was unable to handle the request.
|
77
|
-
def self.post(url = '', args = {}, header = {})
|
78
|
-
dispatch { _post(url, args, header) }
|
79
|
-
end
|
80
|
-
|
81
|
-
# Make a put request and returns a new instance with te response attributes.
|
82
|
-
#
|
83
|
-
# @param [String] url of the api request.
|
84
|
-
# @param [Hash] args attributes of object.
|
85
|
-
# @param [Hash] header attributes of the request.
|
86
|
-
# @return [Base] a new object initialized with the response.
|
87
|
-
# @raise [ApiClient::Exceptions::ConnectionRefused] The request was refused by the api.
|
88
|
-
# @raise [ApiClient::Exceptions::Unauthorized] The request requires user authentication.
|
89
|
-
# @raise [ApiClient::Exceptions::Forbidden] The request was refused.
|
90
|
-
# @raise [ApiClient::Exceptions::NotFound] The request uri has not be found.
|
91
|
-
# @raise [ApiClient::Exceptions::InternalServerError] The request was not fulfilled by the api.
|
92
|
-
# @raise [ApiClient::Exceptions::BadGateway] The request was not fulfilled by the api.
|
93
|
-
# @raise [ApiClient::Exceptions::ServiceUnavailable] The api was unable to handle the request.
|
94
|
-
def self.put(url = '', args = {}, header = {})
|
95
|
-
dispatch { _put(url, args, header) }
|
96
|
-
end
|
25
|
+
# Return the Remote Object Name.
|
26
|
+
#
|
27
|
+
# @return [String] a string with the remote object class name.
|
28
|
+
def self.remote_object
|
29
|
+
@remote_object.blank? ? self.to_s.split('::').last.downcase : @remote_object
|
30
|
+
end
|
97
31
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
# @raise [ApiClient::Exceptions::ConnectionRefused] The request was refused by the api.
|
105
|
-
# @raise [ApiClient::Exceptions::Unauthorized] The request requires user authentication.
|
106
|
-
# @raise [ApiClient::Exceptions::Forbidden] The request was refused.
|
107
|
-
# @raise [ApiClient::Exceptions::NotFound] The request uri has not be found.
|
108
|
-
# @raise [ApiClient::Exceptions::InternalServerError] The request was not fulfilled by the api.
|
109
|
-
# @raise [ApiClient::Exceptions::BadGateway] The request was not fulfilled by the api.
|
110
|
-
# @raise [ApiClient::Exceptions::ServiceUnavailable] The api was unable to handle the request.
|
111
|
-
def self.patch(url = '', args = {}, header = {})
|
112
|
-
dispatch { _patch(url, args, header) }
|
113
|
-
end
|
32
|
+
# Set a custom remote object name instead of the virtual class name.
|
33
|
+
#
|
34
|
+
# @param [String] remote_object name.
|
35
|
+
def self.remote_object=(remote_object)
|
36
|
+
@remote_object = remote_object
|
37
|
+
end
|
114
38
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
# @raise [ApiClient::Exceptions::Unauthorized] The request requires user authentication.
|
122
|
-
# @raise [ApiClient::Exceptions::Forbidden] The request was refused.
|
123
|
-
# @raise [ApiClient::Exceptions::NotFound] The request uri has not be found.
|
124
|
-
# @raise [ApiClient::Exceptions::InternalServerError] The request was not fulfilled by the api.
|
125
|
-
# @raise [ApiClient::Exceptions::BadGateway] The request was not fulfilled by the api.
|
126
|
-
# @raise [ApiClient::Exceptions::ServiceUnavailable] The api was unable to handle the request.
|
127
|
-
def self.delete(url = '', header = {})
|
128
|
-
dispatch { _delete(url, header) }
|
129
|
-
end
|
39
|
+
# Return if a object is persisted on the database or not.
|
40
|
+
#
|
41
|
+
# @return [False] always return false.
|
42
|
+
def persisted?
|
43
|
+
false
|
44
|
+
end
|
130
45
|
|
131
|
-
|
46
|
+
# Return the hash of errors if existent, otherwise instantiate a new ApiClient::Errors object with self.
|
47
|
+
#
|
48
|
+
# @return [ApiClient::Errors] the validation errors.
|
49
|
+
def errors
|
50
|
+
@errors ||= Errors.new(self)
|
51
|
+
end
|
132
52
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
53
|
+
# Set the hash of errors, making keys symbolic.
|
54
|
+
#
|
55
|
+
# @param [Hash] errors of the object..
|
56
|
+
def errors=(errors = {})
|
57
|
+
@errors = Errors.new(self).add_errors(Hash[errors.map{|(key,value)| [key.to_sym,value]}])
|
138
58
|
end
|
139
|
-
raise_exception(code)
|
140
|
-
return object.map { |a| new(a) } if object.instance_of?(Array)
|
141
|
-
new(object)
|
142
|
-
end
|
143
59
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
when '503' then raise ApiClient::Exceptions::ServiceUnavailable
|
152
|
-
else return
|
60
|
+
protected
|
61
|
+
|
62
|
+
def self.method_missing(method, *args)
|
63
|
+
return super unless Dispatcher.respond_to?(method)
|
64
|
+
json_object = Parser.response(Dispatcher.send(method, *args), remote_object)
|
65
|
+
return json_object.map { |a| new(a) } if json_object.instance_of?(Array)
|
66
|
+
new(json_object)
|
153
67
|
end
|
154
68
|
end
|
155
|
-
end
|
69
|
+
end
|
@@ -7,9 +7,9 @@ module ApiClient::Dispatcher
|
|
7
7
|
# @param [String] url of the api request.
|
8
8
|
# @param [Hash] header attributes of the request.
|
9
9
|
# @return [HTTP] the response object.
|
10
|
-
def
|
10
|
+
def self.get(url, header = {})
|
11
11
|
initialize_connection(url)
|
12
|
-
@http.get(@uri.path, header)
|
12
|
+
call { @http.get(@uri.path, header) }
|
13
13
|
end
|
14
14
|
|
15
15
|
# Make a post request and returns it.
|
@@ -18,9 +18,9 @@ module ApiClient::Dispatcher
|
|
18
18
|
# @param [Hash] args attributes of object.
|
19
19
|
# @param [Hash] header attributes of the request.
|
20
20
|
# @return [HTTP] the response object.
|
21
|
-
def
|
21
|
+
def self.post(url, args, header = {})
|
22
22
|
initialize_connection(url)
|
23
|
-
@http.post(@uri.path, args.to_json, { 'Content-Type' => 'application/json' }.merge(header))
|
23
|
+
call { @http.post(@uri.path, args.to_json, { 'Content-Type' => 'application/json' }.merge(header)) }
|
24
24
|
end
|
25
25
|
|
26
26
|
# Make a put request and returns it.
|
@@ -29,9 +29,9 @@ module ApiClient::Dispatcher
|
|
29
29
|
# @param [Hash] args attributes of object.
|
30
30
|
# @param [Hash] header attributes of the request.
|
31
31
|
# @return [HTTP] the response object.
|
32
|
-
def
|
32
|
+
def self.put(url, args, header = {})
|
33
33
|
initialize_connection(url)
|
34
|
-
@http.put(@uri.path, args.to_json, { 'Content-Type' => 'application/json' }.merge(header))
|
34
|
+
call { @http.put(@uri.path, args.to_json, { 'Content-Type' => 'application/json' }.merge(header)) }
|
35
35
|
end
|
36
36
|
|
37
37
|
# Make a patch request and returns it.
|
@@ -40,9 +40,9 @@ module ApiClient::Dispatcher
|
|
40
40
|
# @param [Hash] args attributes of object.
|
41
41
|
# @param [Hash] header attributes of the request.
|
42
42
|
# @return [HTTP] the response object.
|
43
|
-
def
|
43
|
+
def self.patch(url, args, header = {})
|
44
44
|
initialize_connection(url)
|
45
|
-
@http.patch(@uri.path, args.to_json, { 'Content-Type' => 'application/json' }.merge(header))
|
45
|
+
call { @http.patch(@uri.path, args.to_json, { 'Content-Type' => 'application/json' }.merge(header)) }
|
46
46
|
end
|
47
47
|
|
48
48
|
# Make a delete request and returns it.
|
@@ -50,15 +50,23 @@ module ApiClient::Dispatcher
|
|
50
50
|
# @param [String] url of the api request.
|
51
51
|
# @param [Hash] header attributes of the request.
|
52
52
|
# @return [HTTP] the response object.
|
53
|
-
def
|
53
|
+
def self.delete(url, header = {})
|
54
54
|
initialize_connection(url)
|
55
|
-
@http.delete(@uri.path, header)
|
55
|
+
call { @http.delete(@uri.path, header) }
|
56
56
|
end
|
57
57
|
|
58
58
|
protected
|
59
59
|
|
60
|
-
def initialize_connection(url = '')
|
60
|
+
def self.initialize_connection(url = '')
|
61
61
|
@uri = URI(url)
|
62
62
|
@http = Net::HTTP.new(@uri.host, @uri.port)
|
63
63
|
end
|
64
|
+
|
65
|
+
def self.call
|
66
|
+
begin
|
67
|
+
yield
|
68
|
+
rescue Errno::ECONNREFUSED
|
69
|
+
raise ApiClient::Exceptions::ConnectionRefused
|
70
|
+
end
|
71
|
+
end
|
64
72
|
end
|
data/lib/api-client/errors.rb
CHANGED
@@ -2,16 +2,25 @@ require "active_model"
|
|
2
2
|
|
3
3
|
# ApiClient::Errors provide extra functionality to ActiveModel::Errors.
|
4
4
|
class ApiClient::Errors < ActiveModel::Errors
|
5
|
+
# Add serveral errors from a hash to the object.
|
6
|
+
#
|
7
|
+
# @return [ApiClient::Errors] The Error object.
|
8
|
+
def add_errors(errors = {})
|
9
|
+
errors.each do |key, value|
|
10
|
+
self.set(key, value)
|
11
|
+
end
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
5
15
|
# Create a hash of attributes with unique validation error messages.
|
6
16
|
#
|
7
17
|
# Example:
|
8
18
|
# user.errors.unique_messages #=> { :name => [ can't be empty and is invalid ]}
|
9
19
|
#
|
10
20
|
# @return [Hash] The hash of attributes with a unique error message.
|
11
|
-
#
|
12
21
|
def unique_messages
|
13
22
|
errors = {}
|
14
23
|
to_hash.each do |attribute, messages| errors[attribute] = messages.join(" and ") end
|
15
24
|
errors
|
16
25
|
end
|
17
|
-
end
|
26
|
+
end
|
data/lib/api-client/parser.rb
CHANGED
@@ -4,28 +4,29 @@ module ApiClient::Parser
|
|
4
4
|
#
|
5
5
|
# @param [HTTP] response HTTP object for the request.
|
6
6
|
# @return [Array] the code and the body parsed.
|
7
|
-
def
|
7
|
+
def self.response(response, remote_object)
|
8
8
|
begin
|
9
9
|
object = JSON.parse(response.body)
|
10
10
|
object = object[remote_object] if object.key?(remote_object)
|
11
11
|
object = object[remote_object.pluralize] if object.key?(remote_object.pluralize)
|
12
12
|
rescue JSON::ParserError
|
13
|
-
object =
|
13
|
+
object = {}
|
14
14
|
end
|
15
|
-
|
15
|
+
raise_exception(response.code)
|
16
|
+
object
|
16
17
|
end
|
17
18
|
|
18
|
-
|
19
|
-
#
|
20
|
-
# @return [String] a string with the remote object class name.
|
21
|
-
def remote_object
|
22
|
-
@remote_object.blank? ? self.to_s.split('::').last.downcase : @remote_object
|
23
|
-
end
|
19
|
+
protected
|
24
20
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
21
|
+
def self.raise_exception(code)
|
22
|
+
case code
|
23
|
+
when '401' then raise ApiClient::Exceptions::Unauthorized
|
24
|
+
when '403' then raise ApiClient::Exceptions::Forbidden
|
25
|
+
when '404' then raise ApiClient::Exceptions::NotFound
|
26
|
+
when '500' then raise ApiClient::Exceptions::InternalServerError
|
27
|
+
when '502' then raise ApiClient::Exceptions::BadGateway
|
28
|
+
when '503' then raise ApiClient::Exceptions::ServiceUnavailable
|
29
|
+
else return
|
30
|
+
end
|
30
31
|
end
|
31
32
|
end
|