ddy_remote_resource 0.4.11 → 1.0.0.rc1

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/Guardfile +3 -0
  4. data/lib/remote_resource.rb +77 -34
  5. data/lib/remote_resource/base.rb +20 -8
  6. data/lib/remote_resource/connection.rb +26 -5
  7. data/lib/remote_resource/connection_options.rb +5 -3
  8. data/lib/remote_resource/querying/finder_methods.rb +3 -3
  9. data/lib/remote_resource/querying/persistence_methods.rb +17 -12
  10. data/lib/remote_resource/request.rb +96 -62
  11. data/lib/remote_resource/response.rb +5 -1
  12. data/lib/remote_resource/rest.rb +13 -17
  13. data/lib/remote_resource/url_naming.rb +4 -10
  14. data/lib/remote_resource/url_naming_determination.rb +1 -3
  15. data/lib/remote_resource/util.rb +64 -0
  16. data/lib/remote_resource/version.rb +1 -1
  17. data/remote_resource.gemspec +2 -2
  18. data/spec/fixtures/text_file.txt +1 -0
  19. data/spec/integration/all_spec.rb +166 -0
  20. data/spec/integration/collection_prefix_spec.rb +99 -0
  21. data/spec/integration/create_spec.rb +181 -0
  22. data/spec/integration/destroy_spec.rb +252 -0
  23. data/spec/integration/find_by_spec.rb +168 -0
  24. data/spec/integration/find_spec.rb +139 -0
  25. data/spec/integration/headers_spec.rb +222 -0
  26. data/spec/integration/naming_spec.rb +138 -0
  27. data/spec/integration/save_spec.rb +320 -0
  28. data/spec/integration/update_attributes_spec.rb +221 -0
  29. data/spec/integration/where_spec.rb +152 -0
  30. data/spec/lib/extensions/ethon/easy/queryable_spec.rb +4 -4
  31. data/spec/lib/remote_resource/base_spec.rb +54 -110
  32. data/spec/lib/remote_resource/builder_spec.rb +1 -1
  33. data/spec/lib/remote_resource/collection_spec.rb +1 -1
  34. data/spec/lib/remote_resource/connection_options_spec.rb +20 -17
  35. data/spec/lib/remote_resource/connection_spec.rb +36 -27
  36. data/spec/lib/remote_resource/querying/finder_methods_spec.rb +199 -72
  37. data/spec/lib/remote_resource/querying/persistence_methods_spec.rb +228 -220
  38. data/spec/lib/remote_resource/request_spec.rb +313 -342
  39. data/spec/lib/remote_resource/response_spec.rb +9 -3
  40. data/spec/lib/remote_resource/rest_spec.rb +11 -11
  41. data/spec/lib/remote_resource/url_naming_determination_spec.rb +1 -1
  42. data/spec/lib/remote_resource/url_naming_spec.rb +7 -22
  43. data/spec/lib/remote_resource/util_spec.rb +56 -0
  44. data/spec/lib/remote_resource/version_spec.rb +2 -3
  45. data/spec/spec_helper.rb +37 -0
  46. metadata +33 -22
  47. data/lib/remote_resource/http_errors.rb +0 -33
  48. data/lib/remote_resource/response_handeling.rb +0 -48
@@ -26,6 +26,10 @@ module RemoteResource
26
26
  original_response.response_code
27
27
  end
28
28
 
29
+ def response_headers
30
+ original_response.headers
31
+ end
32
+
29
33
  def sanitized_response_body
30
34
  return empty_hash if response_body.blank?
31
35
  return empty_hash if parsed_response_body.blank?
@@ -75,4 +79,4 @@ module RemoteResource
75
79
  end
76
80
 
77
81
  end
78
- end
82
+ end
@@ -1,28 +1,24 @@
1
1
  module RemoteResource
2
2
  module REST
3
- extend ActiveSupport::Concern
4
3
 
5
- ACTIONS = [:get, :put, :patch, :post, :delete]
6
-
7
- module ClassMethods
4
+ def get(attributes = {}, connection_options = {})
5
+ RemoteResource::Request.new(self, __method__, attributes, connection_options).perform
6
+ end
8
7
 
9
- RemoteResource::REST::ACTIONS.each do |action|
10
- define_method action do |*args|
11
- attributes = args[0] || {}
12
- connection_options = args[1] || {}
8
+ def put(attributes = {}, connection_options = {})
9
+ RemoteResource::Request.new(self, __method__, attributes, connection_options).perform
10
+ end
13
11
 
14
- RemoteResource::Request.new(self, action, attributes, connection_options).perform
15
- end
16
- end
12
+ def patch(attributes = {}, connection_options = {})
13
+ RemoteResource::Request.new(self, __method__, attributes, connection_options).perform
17
14
  end
18
15
 
19
- RemoteResource::REST::ACTIONS.each do |action|
20
- define_method action do |*args|
21
- attributes = args[0] || {}
22
- connection_options = args[1] || {}
16
+ def post(attributes = {}, connection_options = {})
17
+ RemoteResource::Request.new(self, __method__, attributes, connection_options).perform
18
+ end
23
19
 
24
- RemoteResource::Request.new(self, action, attributes, connection_options).perform
25
- end
20
+ def delete(attributes = {}, connection_options = {})
21
+ RemoteResource::Request.new(self, __method__, attributes, connection_options).perform
26
22
  end
27
23
 
28
24
  end
@@ -10,25 +10,19 @@ module RemoteResource
10
10
 
11
11
  module ClassMethods
12
12
 
13
- def app_host(app, env = 'development')
14
- CONFIG[env.to_sym][:apps][app.to_sym]
13
+ def app_host(*_)
14
+ warn '[DEPRECATION] `.app_host` is deprecated. Please use a different method to determine the site.'
15
15
  end
16
16
 
17
17
  def base_url
18
- determined_url_naming.base_url
18
+ warn '[DEPRECATION] `.base_url` is deprecated. Please use the connection_options[:base_url] when querying instead.'
19
19
  end
20
20
 
21
21
  def use_relative_model_naming?
22
22
  true
23
23
  end
24
24
 
25
- private
26
-
27
- def determined_url_naming
28
- RemoteResource::UrlNamingDetermination.new self
29
- end
30
-
31
25
  end
32
26
 
33
27
  end
34
- end
28
+ end
@@ -14,9 +14,7 @@ module RemoteResource
14
14
  path_prefix = connection_options.fetch(:path_prefix, resource_klass.path_prefix)
15
15
  path_postfix = connection_options.fetch(:path_postfix, resource_klass.path_postfix)
16
16
 
17
- id = "/#{id}" if id.present?
18
-
19
- "#{site}#{version.presence}#{path_prefix.presence}#{collection_prefix(check_collection_options)}/#{url_safe_relative_name}#{id}#{path_postfix.presence}"
17
+ File.join(site.to_s, version.to_s, path_prefix.to_s, collection_prefix(check_collection_options).to_s, url_safe_relative_name, id.to_s, path_postfix.to_s).chomp('/')
20
18
  end
21
19
 
22
20
  def collection_prefix(check_collection_options)
@@ -0,0 +1,64 @@
1
+ module RemoteResource
2
+ module Util
3
+
4
+ FILTERED = '[FILTERED]'.freeze
5
+
6
+ def self.filter_params(query_string_or_json_body, filtered_params:)
7
+ filtered = query_string_or_json_body
8
+ filtered_params.each do |filtered_param|
9
+ filtered = filtered.to_s.gsub(/(?<="#{filtered_param}":|#{filtered_param}=)(.*?)(?=,|}|&|$)/, FILTERED)
10
+ end
11
+ filtered
12
+ end
13
+
14
+ def self.encode_params_to_query(params)
15
+ if params.is_a?(String)
16
+ pairs = [params]
17
+ else
18
+ pairs = recursively_generate_query(params, nil)
19
+ end
20
+
21
+ URI.encode_www_form(pairs)
22
+ end
23
+
24
+ # This method is based on the monkey patched method:
25
+ # Ethon::Easy::Queryable#recursively_generate_pairs
26
+ #
27
+ # The monkey patch was needed to pass Array
28
+ # params without an index.
29
+ #
30
+ # The problem is described in typhoeus/typhoeus issue #320:
31
+ # https://github.com/typhoeus/typhoeus/issues/320
32
+ #
33
+ # The fix is described in dylanfareed/ethon commit 548033a:
34
+ # https://github.com/dylanfareed/ethon/commit/548033a8557a48203b7d49f3f98812bd79bc05e4
35
+ #
36
+ def self.recursively_generate_query(component, prefix, pairs = [])
37
+ case component
38
+ when Hash
39
+ component.each do |key, value|
40
+ key = prefix.nil? ? key : "#{prefix}[#{key}]"
41
+
42
+ if value.respond_to?(:each)
43
+ recursively_generate_query(value, key, pairs)
44
+ else
45
+ pairs.push([key, value.to_s])
46
+ end
47
+ end
48
+ when Array
49
+ component.each do |value|
50
+ key = "#{prefix}[]"
51
+
52
+ if value.respond_to?(:each)
53
+ recursively_generate_query(value, key, pairs)
54
+ else
55
+ pairs.push([key, value.to_s])
56
+ end
57
+ end
58
+ end
59
+
60
+ pairs
61
+ end
62
+
63
+ end
64
+ end
@@ -1,3 +1,3 @@
1
1
  module RemoteResource
2
- VERSION = '0.4.11'
2
+ VERSION = '1.0.0.rc1'.freeze
3
3
  end
@@ -22,10 +22,10 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency 'rake', '~> 10.4'
23
23
  spec.add_development_dependency 'rspec', '~> 3.1'
24
24
  spec.add_development_dependency 'pry', '~> 0.10'
25
- spec.add_development_dependency 'webmock', '~> 1.24'
25
+ # spec.add_development_dependency 'webmock', '~> 1.24'
26
26
  spec.add_development_dependency 'guard', '~> 2.14'
27
27
  spec.add_development_dependency 'guard-rspec', '~> 4.7'
28
- spec.add_development_dependency 'terminal-notifier-guard', '~> 1.6.1'
28
+ spec.add_development_dependency 'terminal-notifier-guard', '~> 1.6'
29
29
 
30
30
  spec.add_runtime_dependency 'activesupport', '~> 4.1'
31
31
  spec.add_runtime_dependency 'activemodel', '~> 4.1'
@@ -0,0 +1 @@
1
+ Hello world!
@@ -0,0 +1,166 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe '.all' do
4
+
5
+ class Post
6
+ include RemoteResource::Base
7
+
8
+ self.site = 'https://www.example.com'
9
+ self.collection = true
10
+ self.root_element = :data
11
+
12
+ attribute :title, String
13
+ attribute :body, String
14
+ attribute :created_at, Time
15
+ end
16
+
17
+ let(:response_body) do
18
+ {
19
+ data: [
20
+ {
21
+ id: 12,
22
+ title: 'Lorem Ipsum',
23
+ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
24
+ created_at: Time.new(2015, 10, 4, 9, 30, 0),
25
+ },
26
+ {
27
+ id: 14,
28
+ title: 'Mauris Purus',
29
+ body: 'Mauris purus urna, ultrices et suscipit ut, faucibus eget mauris.',
30
+ created_at: Time.new(2015, 12, 11, 11, 32, 0),
31
+ },
32
+ {
33
+ id: 16,
34
+ title: 'Vestibulum Commodo',
35
+ body: 'Vestibulum commodo fringilla suscipit.',
36
+ created_at: Time.new(2016, 2, 6, 18, 45, 0),
37
+ },
38
+ ]
39
+ }
40
+ end
41
+
42
+ let(:expected_default_headers) do
43
+ { 'Accept' => 'application/json', 'User-Agent' => "RemoteResource #{RemoteResource::VERSION}" }
44
+ end
45
+
46
+ describe 'default behaviour' do
47
+ let!(:expected_request) do
48
+ mock_request = stub_request(:get, 'https://www.example.com/posts.json')
49
+ mock_request.with(query: nil, body: nil, headers: expected_default_headers)
50
+ mock_request.to_return(status: 200, body: response_body.to_json)
51
+ mock_request
52
+ end
53
+
54
+ it 'performs the correct HTTP GET request' do
55
+ Post.all
56
+ expect(expected_request).to have_been_requested
57
+ end
58
+
59
+ it 'builds the correct collection of resources' do
60
+ posts = Post.all
61
+
62
+ aggregate_failures do
63
+ expect(posts).to respond_to :each
64
+ expect(posts).to all(be_a(Post))
65
+ expect(posts.size).to eql 3
66
+
67
+ expect(posts[0].id).to eql 12
68
+ expect(posts[0].title).to eql 'Lorem Ipsum'
69
+ expect(posts[0].body).to eql 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
70
+ expect(posts[0].created_at).to eql Time.new(2015, 10, 4, 9, 30, 0)
71
+ expect(posts[1].id).to eql 14
72
+ expect(posts[1].title).to eql 'Mauris Purus'
73
+ expect(posts[1].body).to eql 'Mauris purus urna, ultrices et suscipit ut, faucibus eget mauris.'
74
+ expect(posts[1].created_at).to eql Time.new(2015, 12, 11, 11, 32, 0)
75
+ expect(posts[2].id).to eql 16
76
+ expect(posts[2].title).to eql 'Vestibulum Commodo'
77
+ expect(posts[2].body).to eql 'Vestibulum commodo fringilla suscipit.'
78
+ expect(posts[2].created_at).to eql Time.new(2016, 2, 6, 18, 45, 0)
79
+ end
80
+ end
81
+ end
82
+
83
+ describe 'with connection_options[:params]' do
84
+ let!(:expected_request) do
85
+ mock_request = stub_request(:get, 'https://www.example.com/posts.json')
86
+ mock_request.with(query: { pseudonym: 'pseudonym' }, body: nil, headers: expected_default_headers)
87
+ mock_request.to_return(status: 200, body: response_body.to_json)
88
+ mock_request
89
+ end
90
+
91
+ it 'performs the correct HTTP GET request' do
92
+ Post.all(params: { pseudonym: 'pseudonym' })
93
+ expect(expected_request).to have_been_requested
94
+ end
95
+ end
96
+
97
+ describe 'with connection_options[:headers]' do
98
+ let!(:expected_request) do
99
+ mock_request = stub_request(:get, 'https://www.example.com/posts.json')
100
+ mock_request.with(query: nil, body: nil, headers: expected_default_headers.merge({ 'X-Pseudonym' => 'pseudonym' }))
101
+ mock_request.to_return(status: 200, body: response_body.to_json)
102
+ mock_request
103
+ end
104
+
105
+ it 'performs the correct HTTP GET request' do
106
+ Post.all(headers: { 'X-Pseudonym' => 'pseudonym' })
107
+ expect(expected_request).to have_been_requested
108
+ end
109
+ end
110
+
111
+ describe 'with a 404 response' do
112
+ let!(:expected_request) do
113
+ mock_request = stub_request(:get, 'https://www.example.com/posts.json')
114
+ mock_request.with(query: { pseudonym: 'pseudonym' }, body: nil, headers: expected_default_headers.merge({ 'X-Pseudonym' => 'pseudonym' }))
115
+ mock_request.to_return(status: 404)
116
+ mock_request
117
+ end
118
+
119
+ it 'raises the not found error' do
120
+ expect { Post.all(params: { pseudonym: 'pseudonym' }, headers: { 'X-Pseudonym' => 'pseudonym' }) }.to raise_error RemoteResource::HTTPNotFound
121
+ end
122
+
123
+ it 'adds metadata to the raised error' do
124
+ begin
125
+ Post.all(params: { pseudonym: 'pseudonym' }, headers: { 'X-Pseudonym' => 'pseudonym' })
126
+ rescue RemoteResource::HTTPNotFound => error
127
+ aggregate_failures do
128
+ expect(error.message).to eql 'HTTP request failed for Post with response_code=404 with http_action=get with request_url=https://www.example.com/posts.json'
129
+ expect(error.request_url).to eql 'https://www.example.com/posts.json'
130
+ expect(error.response_code).to eql 404
131
+ expect(error.request_query).to eql(RemoteResource::Util.encode_params_to_query({ pseudonym: 'pseudonym' }))
132
+ expect(error.request_headers).to eql(expected_default_headers.merge({ 'X-Pseudonym' => 'pseudonym' }))
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ describe 'with a 500 response' do
139
+ let!(:expected_request) do
140
+ mock_request = stub_request(:get, 'https://www.example.com/posts.json')
141
+ mock_request.with(query: { pseudonym: 'pseudonym' }, body: nil, headers: expected_default_headers.merge({ 'X-Pseudonym' => 'pseudonym' }))
142
+ mock_request.to_return(status: 500)
143
+ mock_request
144
+ end
145
+
146
+ it 'raises the server error' do
147
+ expect { Post.all(params: { pseudonym: 'pseudonym' }, headers: { 'X-Pseudonym' => 'pseudonym' }) }.to raise_error RemoteResource::HTTPServerError
148
+ end
149
+
150
+ it 'adds metadata to the raised error' do
151
+ begin
152
+ Post.all(params: { pseudonym: 'pseudonym' }, headers: { 'X-Pseudonym' => 'pseudonym' })
153
+ rescue RemoteResource::HTTPServerError => error
154
+ aggregate_failures do
155
+ expect(error.message).to eql 'HTTP request failed for Post with response_code=500 with http_action=get with request_url=https://www.example.com/posts.json'
156
+ expect(error.request_url).to eql 'https://www.example.com/posts.json'
157
+ expect(error.response_code).to eql 500
158
+ expect(error.request_query).to eql(RemoteResource::Util.encode_params_to_query({ pseudonym: 'pseudonym' }))
159
+ expect(error.request_headers).to eql(expected_default_headers.merge({ 'X-Pseudonym' => 'pseudonym' }))
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+
166
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'connection_options[:collection_prefix] and connection_options[:collection_options]' do
4
+
5
+ class Post
6
+ include RemoteResource::Base
7
+
8
+ self.site = 'https://www.example.com'
9
+ self.collection = true
10
+ self.root_element = :data
11
+
12
+ attribute :title, String
13
+ attribute :body, String
14
+ attribute :featured, Boolean
15
+ attribute :created_at, Time
16
+ end
17
+
18
+ class Comment
19
+ include RemoteResource::Base
20
+
21
+ self.site = 'https://www.example.com'
22
+ self.collection = true
23
+ self.collection_prefix = '/posts/:post_id'
24
+ self.root_element = :data
25
+
26
+ attribute :body, String
27
+ attribute :commented_at, Time
28
+ end
29
+
30
+ describe 'connection_options[:collection_prefix] defined on class' do
31
+ let(:response_body) do
32
+ {
33
+ data: {
34
+ id: 18,
35
+ body: 'Very interesting comment',
36
+ commented_at: Time.new(2016, 12, 8, 11, 38, 0)
37
+ }
38
+ }
39
+ end
40
+
41
+ let!(:expected_request) do
42
+ mock_request = stub_request(:get, 'https://www.example.com/posts/12/comments/18.json')
43
+ mock_request.to_return(status: 200, body: response_body.to_json)
44
+ mock_request
45
+ end
46
+
47
+ it 'performs the correct HTTP GET request' do
48
+ Comment.find(18, collection_options: { post_id: 12 })
49
+ expect(expected_request).to have_been_requested
50
+ end
51
+
52
+ it 'builds the correct resource' do
53
+ comment = Comment.find(18, collection_options: { post_id: 12 })
54
+
55
+ aggregate_failures do
56
+ expect(comment.id).to eql 18
57
+ expect(comment.body).to eql 'Very interesting comment'
58
+ expect(comment.commented_at).to eql Time.new(2016, 12, 8, 11, 38, 0)
59
+ end
60
+ end
61
+ end
62
+
63
+ describe 'connection_options[:collection_prefix] given as argument' do
64
+ let(:response_body) do
65
+ {
66
+ data: {
67
+ id: 12,
68
+ title: 'Lorem Ipsum',
69
+ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
70
+ featured: true,
71
+ created_at: Time.new(2015, 10, 4, 9, 30, 0),
72
+ }
73
+ }
74
+ end
75
+
76
+ let!(:expected_request) do
77
+ mock_request = stub_request(:get, 'https://www.example.com/users/450/posts/12.json')
78
+ mock_request.to_return(status: 200, body: response_body.to_json)
79
+ mock_request
80
+ end
81
+
82
+ it 'performs the correct HTTP GET request' do
83
+ Post.find(12, collection_prefix: '/users/:user_id', collection_options: { user_id: 450 })
84
+ expect(expected_request).to have_been_requested
85
+ end
86
+
87
+ it 'builds the correct resource' do
88
+ post = Post.find(12, collection_prefix: '/users/:user_id', collection_options: { user_id: 450 })
89
+
90
+ aggregate_failures do
91
+ expect(post.id).to eql 12
92
+ expect(post.title).to eql 'Lorem Ipsum'
93
+ expect(post.body).to eql 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
94
+ expect(post.created_at).to eql Time.new(2015, 10, 4, 9, 30, 0)
95
+ end
96
+ end
97
+ end
98
+
99
+ end