ddy_remote_resource 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +182 -0
- data/Rakefile +7 -0
- data/lib/extensions/ethon/easy/queryable.rb +36 -0
- data/lib/remote_resource.rb +64 -0
- data/lib/remote_resource/base.rb +126 -0
- data/lib/remote_resource/builder.rb +53 -0
- data/lib/remote_resource/collection.rb +31 -0
- data/lib/remote_resource/connection.rb +24 -0
- data/lib/remote_resource/connection_options.rb +41 -0
- data/lib/remote_resource/http_errors.rb +33 -0
- data/lib/remote_resource/querying/finder_methods.rb +34 -0
- data/lib/remote_resource/querying/persistence_methods.rb +38 -0
- data/lib/remote_resource/request.rb +106 -0
- data/lib/remote_resource/response.rb +69 -0
- data/lib/remote_resource/response_handeling.rb +48 -0
- data/lib/remote_resource/rest.rb +29 -0
- data/lib/remote_resource/url_naming.rb +34 -0
- data/lib/remote_resource/url_naming_determination.rb +39 -0
- data/lib/remote_resource/version.rb +3 -0
- data/remote_resource.gemspec +32 -0
- data/spec/lib/extensions/ethon/easy/queryable_spec.rb +135 -0
- data/spec/lib/remote_resource/base_spec.rb +388 -0
- data/spec/lib/remote_resource/builder_spec.rb +245 -0
- data/spec/lib/remote_resource/collection_spec.rb +148 -0
- data/spec/lib/remote_resource/connection_options_spec.rb +124 -0
- data/spec/lib/remote_resource/connection_spec.rb +61 -0
- data/spec/lib/remote_resource/querying/finder_methods_spec.rb +105 -0
- data/spec/lib/remote_resource/querying/persistence_methods_spec.rb +174 -0
- data/spec/lib/remote_resource/request_spec.rb +594 -0
- data/spec/lib/remote_resource/response_spec.rb +196 -0
- data/spec/lib/remote_resource/rest_spec.rb +98 -0
- data/spec/lib/remote_resource/url_naming_determination_spec.rb +225 -0
- data/spec/lib/remote_resource/url_naming_spec.rb +72 -0
- data/spec/lib/remote_resource/version_spec.rb +8 -0
- data/spec/spec_helper.rb +4 -0
- metadata +242 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
module RemoteResource
|
2
|
+
module Builder
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
|
7
|
+
def build_resource_from_response(response)
|
8
|
+
build_resource response.sanitized_response_body, response_hash(response)
|
9
|
+
end
|
10
|
+
|
11
|
+
def build_resource(collection, response_hash = {})
|
12
|
+
if collection.is_a? Hash
|
13
|
+
new collection.merge response_hash
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def build_collection_from_response(response)
|
18
|
+
build_collection response.sanitized_response_body, response_hash(response)
|
19
|
+
end
|
20
|
+
|
21
|
+
def build_collection(collection, response_hash = {})
|
22
|
+
if collection.is_a? Array
|
23
|
+
RemoteResource::Collection.new self, collection, response_hash
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def response_hash(response_object)
|
30
|
+
{ _response: response_object }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def rebuild_resource_from_response(response)
|
35
|
+
rebuild_resource response.sanitized_response_body, response_hash(response)
|
36
|
+
end
|
37
|
+
|
38
|
+
def rebuild_resource(collection, response_hash = {})
|
39
|
+
if collection.is_a? Hash
|
40
|
+
self.attributes = collection.merge(response_hash)
|
41
|
+
else
|
42
|
+
self.attributes = response_hash
|
43
|
+
end and self
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def response_hash(response_object)
|
49
|
+
self.class.send :response_hash, response_object
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module RemoteResource
|
2
|
+
class Collection
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
delegate :[], :at, :reverse, :size, to: :to_a
|
6
|
+
|
7
|
+
attr_reader :resource_klass, :resources_collection, :_response
|
8
|
+
|
9
|
+
def initialize(resource_klass, resources_collection, response_hash)
|
10
|
+
@resource_klass = resource_klass
|
11
|
+
@resources_collection = resources_collection
|
12
|
+
@response_hash = response_hash
|
13
|
+
@_response = response_hash[:_response]
|
14
|
+
end
|
15
|
+
|
16
|
+
def each
|
17
|
+
if resources_collection.is_a? Array
|
18
|
+
resources_collection.each { |element| yield resource_klass.new element.merge(@response_hash) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def empty?
|
23
|
+
resources_collection.blank?
|
24
|
+
end
|
25
|
+
|
26
|
+
def success?
|
27
|
+
_response.success?
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module RemoteResource
|
2
|
+
module Connection
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :content_type, :default_headers, :extra_headers, instance_accessor: false
|
7
|
+
|
8
|
+
self.content_type = '.json'
|
9
|
+
self.default_headers = { "Accept" => "application/json" }
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
def connection
|
15
|
+
Typhoeus::Request
|
16
|
+
end
|
17
|
+
|
18
|
+
def headers
|
19
|
+
self.default_headers.merge self.extra_headers || {}
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module RemoteResource
|
2
|
+
class ConnectionOptions
|
3
|
+
|
4
|
+
attr_reader :base_class
|
5
|
+
|
6
|
+
def initialize(base_class)
|
7
|
+
@base_class = base_class
|
8
|
+
self.send :initialize_connection_options
|
9
|
+
end
|
10
|
+
|
11
|
+
def merge(options = {})
|
12
|
+
options.each do |option, value|
|
13
|
+
self.public_send "#{option}=", value
|
14
|
+
end and self
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_hash
|
18
|
+
RemoteResource::Base::OPTIONS.each_with_object(Hash.new) do |option, hash|
|
19
|
+
hash[option] = self.public_send option
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def reload
|
24
|
+
initialize_connection_options
|
25
|
+
end
|
26
|
+
|
27
|
+
def reload!
|
28
|
+
reload and self
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def initialize_connection_options
|
34
|
+
RemoteResource::Base::OPTIONS.each do |option|
|
35
|
+
self.class.send :attr_accessor, option
|
36
|
+
self.public_send "#{option}=", base_class.public_send(option)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module RemoteResource
|
2
|
+
module HTTPErrors
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def raise_http_errors(response)
|
7
|
+
case response.response_code
|
8
|
+
when 301, 302, 303, 307 then raise RemoteResource::HTTPRedirectionError, response
|
9
|
+
when 400 then raise RemoteResource::HTTPBadRequest, response
|
10
|
+
when 401 then raise RemoteResource::HTTPUnauthorized, response
|
11
|
+
when 403 then raise RemoteResource::HTTPForbidden, response
|
12
|
+
when 404 then raise RemoteResource::HTTPNotFound, response
|
13
|
+
when 405 then raise RemoteResource::HTTPMethodNotAllowed, response
|
14
|
+
when 406 then raise RemoteResource::HTTPNotAcceptable, response
|
15
|
+
when 408 then raise RemoteResource::HTTPRequestTimeout, response
|
16
|
+
when 409 then raise RemoteResource::HTTPConflict, response
|
17
|
+
when 410 then raise RemoteResource::HTTPGone, response
|
18
|
+
when 418 then raise RemoteResource::HTTPTeapot, response
|
19
|
+
when 444 then raise RemoteResource::HTTPNoResponse, response
|
20
|
+
when 494 then raise RemoteResource::HTTPRequestHeaderTooLarge, response
|
21
|
+
when 495 then raise RemoteResource::HTTPCertError, response
|
22
|
+
when 496 then raise RemoteResource::HTTPNoCert, response
|
23
|
+
when 497 then raise RemoteResource::HTTPToHTTPS, response
|
24
|
+
when 499 then raise RemoteResource::HTTPClientClosedRequest, response
|
25
|
+
when 400..499 then raise RemoteResource::HTTPClientError, response
|
26
|
+
when 500..599 then raise RemoteResource::HTTPServerError, response
|
27
|
+
else
|
28
|
+
raise RemoteResource::HTTPError, response
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module RemoteResource
|
2
|
+
module Querying
|
3
|
+
module FinderMethods
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
|
8
|
+
def find(id, connection_options = {})
|
9
|
+
connection_options.merge! no_params: true
|
10
|
+
response = RemoteResource::Request.new(self, :get, { id: id }, connection_options).perform
|
11
|
+
build_resource_from_response response
|
12
|
+
end
|
13
|
+
|
14
|
+
def find_by(params, connection_options = {})
|
15
|
+
response = RemoteResource::Request.new(self, :get, params, connection_options).perform
|
16
|
+
build_resource_from_response response
|
17
|
+
end
|
18
|
+
|
19
|
+
def all(connection_options = {})
|
20
|
+
connection_options.merge! collection: true
|
21
|
+
response = RemoteResource::Request.new(self, :get, {}, connection_options).perform
|
22
|
+
build_collection_from_response response
|
23
|
+
end
|
24
|
+
|
25
|
+
def where(params, connection_options = {})
|
26
|
+
connection_options.merge! collection: true
|
27
|
+
response = RemoteResource::Request.new(self, :get, params, connection_options).perform
|
28
|
+
build_collection_from_response response
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RemoteResource
|
2
|
+
module Querying
|
3
|
+
module PersistenceMethods
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
|
8
|
+
def create(attributes = {}, connection_options = {})
|
9
|
+
resource = new attributes
|
10
|
+
response = RemoteResource::Request.new(self, :post, attributes, connection_options).perform
|
11
|
+
resource.handle_response response
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def update_attributes(attributes = {}, connection_options = {})
|
16
|
+
rebuild_resource attributes
|
17
|
+
attributes.reverse_merge! id: id
|
18
|
+
create_or_update attributes, connection_options
|
19
|
+
success? ? self : false
|
20
|
+
end
|
21
|
+
|
22
|
+
def save(connection_options = {})
|
23
|
+
create_or_update self.attributes, connection_options
|
24
|
+
success? ? self : false
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_or_update(attributes = {}, connection_options = {})
|
28
|
+
if attributes.has_key? :id
|
29
|
+
response = RemoteResource::Request.new(self, :patch, attributes, connection_options).perform
|
30
|
+
else
|
31
|
+
response = RemoteResource::Request.new(self, :post, attributes, connection_options).perform
|
32
|
+
end
|
33
|
+
handle_response response
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module RemoteResource
|
2
|
+
class Request
|
3
|
+
include RemoteResource::HTTPErrors
|
4
|
+
|
5
|
+
attr_reader :resource, :rest_action, :attributes
|
6
|
+
|
7
|
+
def initialize(resource, rest_action, attributes = {}, connection_options = {})
|
8
|
+
@resource = resource
|
9
|
+
@rest_action = rest_action.to_sym
|
10
|
+
@attributes = attributes
|
11
|
+
@connection_options = connection_options
|
12
|
+
@original_connection_options = connection_options.dup
|
13
|
+
end
|
14
|
+
|
15
|
+
def connection
|
16
|
+
resource_klass.connection
|
17
|
+
end
|
18
|
+
|
19
|
+
def connection_options
|
20
|
+
@connection_options.reverse_merge! threaded_connection_options
|
21
|
+
@connection_options.reverse_merge! resource.connection_options.to_hash
|
22
|
+
end
|
23
|
+
|
24
|
+
def original_connection_options
|
25
|
+
@original_connection_options.reverse_merge! threaded_connection_options
|
26
|
+
end
|
27
|
+
|
28
|
+
def perform
|
29
|
+
case rest_action
|
30
|
+
when :get
|
31
|
+
response = connection.public_send rest_action, determined_request_url, params: determined_params, headers: determined_headers
|
32
|
+
when :put, :patch, :post
|
33
|
+
response = connection.public_send rest_action, determined_request_url, body: determined_attributes, headers: determined_headers
|
34
|
+
else
|
35
|
+
raise RemoteResource::RESTActionUnknown, "for action: '#{rest_action}'"
|
36
|
+
end
|
37
|
+
|
38
|
+
if response.success? || response.response_code == 422
|
39
|
+
RemoteResource::Response.new response, connection_options
|
40
|
+
else
|
41
|
+
raise_http_errors response
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def determined_request_url
|
46
|
+
id = attributes[:id].presence
|
47
|
+
base_url = original_connection_options[:base_url].presence || determined_url_naming.base_url(id)
|
48
|
+
content_type = connection_options[:content_type]
|
49
|
+
|
50
|
+
"#{base_url}#{content_type}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def determined_params
|
54
|
+
no_params = connection_options[:no_params].eql? true
|
55
|
+
no_attributes = connection_options[:no_attributes].eql? true
|
56
|
+
params = connection_options[:params].presence || {}
|
57
|
+
|
58
|
+
if no_params
|
59
|
+
nil
|
60
|
+
elsif no_attributes
|
61
|
+
params
|
62
|
+
else
|
63
|
+
attributes.merge! params
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def determined_attributes
|
68
|
+
no_attributes = connection_options[:no_attributes].eql? true
|
69
|
+
root_element = connection_options[:root_element].presence
|
70
|
+
|
71
|
+
if no_attributes
|
72
|
+
{}
|
73
|
+
elsif root_element
|
74
|
+
pack_up_attributes attributes, root_element
|
75
|
+
else
|
76
|
+
attributes
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def determined_headers
|
81
|
+
headers = original_connection_options[:headers].presence || {}
|
82
|
+
|
83
|
+
(connection_options[:default_headers].presence ||
|
84
|
+
resource.connection_options.headers.merge(headers)).reverse_merge RemoteResource::Base.global_headers
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def threaded_connection_options
|
90
|
+
resource.try(:threaded_connection_options) || {}
|
91
|
+
end
|
92
|
+
|
93
|
+
def determined_url_naming
|
94
|
+
RemoteResource::UrlNamingDetermination.new resource_klass, original_connection_options
|
95
|
+
end
|
96
|
+
|
97
|
+
def resource_klass
|
98
|
+
resource.is_a?(Class) ? resource : resource.class
|
99
|
+
end
|
100
|
+
|
101
|
+
def pack_up_attributes(attributes, root_element)
|
102
|
+
Hash[root_element.to_s, attributes]
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module RemoteResource
|
2
|
+
class Response
|
3
|
+
|
4
|
+
attr_reader :original_response, :original_request
|
5
|
+
private :original_response, :original_request
|
6
|
+
|
7
|
+
def initialize(response, connection_options = {})
|
8
|
+
@original_response = response
|
9
|
+
@original_request = response.request
|
10
|
+
@connection_options = connection_options
|
11
|
+
end
|
12
|
+
|
13
|
+
def success?
|
14
|
+
original_response.success?
|
15
|
+
end
|
16
|
+
|
17
|
+
def unprocessable_entity?
|
18
|
+
response_code == 422
|
19
|
+
end
|
20
|
+
|
21
|
+
def response_body
|
22
|
+
original_response.body
|
23
|
+
end
|
24
|
+
|
25
|
+
def response_code
|
26
|
+
original_response.response_code
|
27
|
+
end
|
28
|
+
|
29
|
+
def sanitized_response_body
|
30
|
+
return empty_hash if response_body.blank?
|
31
|
+
return empty_hash if parsed_response_body.blank?
|
32
|
+
|
33
|
+
unpacked_parsed_response_body
|
34
|
+
end
|
35
|
+
|
36
|
+
def error_messages_response_body
|
37
|
+
return empty_hash if response_body.blank?
|
38
|
+
return empty_hash if parsed_response_body.blank?
|
39
|
+
|
40
|
+
return parsed_response_body["errors"] if parsed_response_body.try :has_key?, "errors"
|
41
|
+
return unpacked_parsed_response_body["errors"] if unpacked_parsed_response_body.try :has_key?, "errors"
|
42
|
+
|
43
|
+
empty_hash
|
44
|
+
end
|
45
|
+
|
46
|
+
def parsed_response_body
|
47
|
+
@parsed_response_body ||= JSON.parse response_body
|
48
|
+
rescue JSON::ParserError
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def empty_hash
|
55
|
+
{}
|
56
|
+
end
|
57
|
+
|
58
|
+
def unpacked_parsed_response_body
|
59
|
+
root_element = @connection_options[:root_element].presence
|
60
|
+
|
61
|
+
if root_element
|
62
|
+
parsed_response_body[root_element.to_s]
|
63
|
+
else
|
64
|
+
parsed_response_body
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module RemoteResource
|
2
|
+
class ResponseHandeling
|
3
|
+
|
4
|
+
attr_reader :resource, :response, :connection_options
|
5
|
+
|
6
|
+
def initialize(resource, response, connection_options = {})
|
7
|
+
@resource = resource
|
8
|
+
@response = response
|
9
|
+
@connection_options = connection_options
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform
|
13
|
+
if response.success?
|
14
|
+
if resource.respond_to? :new
|
15
|
+
resource.build_resource_from_response response
|
16
|
+
else
|
17
|
+
resource
|
18
|
+
end
|
19
|
+
elsif errors?
|
20
|
+
if resource.respond_to? :new
|
21
|
+
#
|
22
|
+
else
|
23
|
+
# assign response
|
24
|
+
resource.assign_errors response.parsed_response_body, root_element
|
25
|
+
end
|
26
|
+
else
|
27
|
+
# nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def errors?
|
32
|
+
parsed_response_body = response.parsed_response_body
|
33
|
+
|
34
|
+
if parsed_response_body
|
35
|
+
parsed_response_body.has_key?("errors") || parsed_response_body.fetch(root_element.to_s, nil).has_key?("errors")
|
36
|
+
else
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def root_element
|
44
|
+
connection_options[:root_element].presence || resource.connection_options.root_element.presence
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|