ddy_remote_resource 0.4.2
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.
- 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
|