ddy_remote_resource 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
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