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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +3 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +182 -0
  10. data/Rakefile +7 -0
  11. data/lib/extensions/ethon/easy/queryable.rb +36 -0
  12. data/lib/remote_resource.rb +64 -0
  13. data/lib/remote_resource/base.rb +126 -0
  14. data/lib/remote_resource/builder.rb +53 -0
  15. data/lib/remote_resource/collection.rb +31 -0
  16. data/lib/remote_resource/connection.rb +24 -0
  17. data/lib/remote_resource/connection_options.rb +41 -0
  18. data/lib/remote_resource/http_errors.rb +33 -0
  19. data/lib/remote_resource/querying/finder_methods.rb +34 -0
  20. data/lib/remote_resource/querying/persistence_methods.rb +38 -0
  21. data/lib/remote_resource/request.rb +106 -0
  22. data/lib/remote_resource/response.rb +69 -0
  23. data/lib/remote_resource/response_handeling.rb +48 -0
  24. data/lib/remote_resource/rest.rb +29 -0
  25. data/lib/remote_resource/url_naming.rb +34 -0
  26. data/lib/remote_resource/url_naming_determination.rb +39 -0
  27. data/lib/remote_resource/version.rb +3 -0
  28. data/remote_resource.gemspec +32 -0
  29. data/spec/lib/extensions/ethon/easy/queryable_spec.rb +135 -0
  30. data/spec/lib/remote_resource/base_spec.rb +388 -0
  31. data/spec/lib/remote_resource/builder_spec.rb +245 -0
  32. data/spec/lib/remote_resource/collection_spec.rb +148 -0
  33. data/spec/lib/remote_resource/connection_options_spec.rb +124 -0
  34. data/spec/lib/remote_resource/connection_spec.rb +61 -0
  35. data/spec/lib/remote_resource/querying/finder_methods_spec.rb +105 -0
  36. data/spec/lib/remote_resource/querying/persistence_methods_spec.rb +174 -0
  37. data/spec/lib/remote_resource/request_spec.rb +594 -0
  38. data/spec/lib/remote_resource/response_spec.rb +196 -0
  39. data/spec/lib/remote_resource/rest_spec.rb +98 -0
  40. data/spec/lib/remote_resource/url_naming_determination_spec.rb +225 -0
  41. data/spec/lib/remote_resource/url_naming_spec.rb +72 -0
  42. data/spec/lib/remote_resource/version_spec.rb +8 -0
  43. data/spec/spec_helper.rb +4 -0
  44. 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