ddy_remote_resource 0.4.11 → 1.0.0.rc1

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