restful_resource 0.0.11 → 0.8.0
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 +4 -4
- data/README.md +11 -0
- data/lib/restful_resource/associations.rb +23 -0
- data/lib/restful_resource/base.rb +64 -122
- data/lib/restful_resource/http_client.rb +8 -0
- data/lib/restful_resource/old_base.rb +172 -0
- data/lib/restful_resource/open_object.rb +19 -0
- data/lib/restful_resource/response.rb +9 -0
- data/lib/restful_resource/version.rb +1 -1
- data/lib/restful_resource.rb +10 -4
- data/spec/fixtures.rb +23 -0
- data/spec/restful_resource/associations_spec.rb +32 -0
- data/spec/restful_resource/base_spec.rb +105 -82
- data/spec/restful_resource/old_base_spec.rb +119 -0
- data/spec/restful_resource/open_object_spec.rb +16 -0
- data/spec/restful_resource/rest_client_spec.rb +15 -0
- data/spec/spec_helper.rb +1 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5e4f4e6af3b1bee81026d51b88de88305d854791
|
4
|
+
data.tar.gz: ccc94c88defe5f2867214778ba49b27af23ea42f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b276550299aa86e9574211dd018dd3e86a79c357d79ed05a6bdd1d9830b47e340e904a51ff80caa4c9dd8d32256217200146b183b367cc428e4edd5796f6ef88
|
7
|
+
data.tar.gz: 9c4eb7a4326eac86a7cc23e0c25fe1881ad9e6a968cd4da65f693d80efbc216abbb4c1284a0edccf6e1369f42672f6730fae25a7719ad770754d4b31d0c785d6
|
data/README.md
CHANGED
@@ -27,3 +27,14 @@ TODO: Write usage instructions here
|
|
27
27
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
28
|
4. Push to the branch (`git push origin my-new-feature`)
|
29
29
|
5. Create new Pull Request
|
30
|
+
|
31
|
+
## Planned Features
|
32
|
+
|
33
|
+
### Core
|
34
|
+
- Test that has_many and has_one pick correct class for children (if defined)
|
35
|
+
- Make base_url resilient when missing trailing slash
|
36
|
+
- Implement http authentication
|
37
|
+
|
38
|
+
### Active record style validation
|
39
|
+
|
40
|
+
### Constraints(mandatory fields)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module RestfulResource
|
2
|
+
module Associations
|
3
|
+
def has_many(nested_resource_type)
|
4
|
+
klass = nested_resource_type.to_s.singularize.camelize.safe_constantize
|
5
|
+
klass = OpenStruct if (klass.nil? || (klass.superclass != RestfulResource))
|
6
|
+
|
7
|
+
self.send(:define_method, nested_resource_type) do
|
8
|
+
@inner_object.send(nested_resource_type).map { |obj| klass.new(obj) }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def has_one(nested_resource_type)
|
13
|
+
klass = nested_resource_type.to_s.camelize.safe_constantize
|
14
|
+
klass = OpenStruct if (klass.nil? || klass.superclass != RestfulResource)
|
15
|
+
|
16
|
+
self.send(:define_method, nested_resource_type) do
|
17
|
+
nested_resource = @inner_object.send(nested_resource_type)
|
18
|
+
return nil if nested_resource.nil?
|
19
|
+
klass.new(@inner_object.send(nested_resource_type))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,158 +1,107 @@
|
|
1
1
|
module RestfulResource
|
2
|
-
class Base
|
3
|
-
|
4
|
-
@url = url
|
5
|
-
end
|
6
|
-
|
7
|
-
def self.processed_url_and_params(params={})
|
8
|
-
uri = URI(@url)
|
9
|
-
path = uri.path
|
10
|
-
other_params = params.clone
|
11
|
-
missing_params = []
|
2
|
+
class Base < OpenObject
|
3
|
+
extend RestfulResource::Associations
|
12
4
|
|
13
|
-
|
14
|
-
|
15
|
-
value = other_params.delete(key.to_sym)
|
16
|
-
if value.nil?
|
17
|
-
missing_params << key
|
18
|
-
else
|
19
|
-
path = path.gsub(':'+key.to_s, value.to_s)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
if missing_params.any?
|
24
|
-
raise ParameterMissingError.new(missing_params)
|
25
|
-
end
|
26
|
-
|
27
|
-
uri.path = path
|
28
|
-
[uri.to_s, other_params]
|
29
|
-
end
|
30
|
-
|
31
|
-
def self.url(params={})
|
32
|
-
processed_url_and_params(params).first
|
33
|
-
end
|
34
|
-
|
35
|
-
|
36
|
-
def self.has_one(nested_resource_type)
|
37
|
-
klass = nested_resource_type.to_s.camelize.safe_constantize
|
38
|
-
klass = OpenStruct if (klass.nil? || klass.superclass != RestfulResource)
|
39
|
-
|
40
|
-
self.send(:define_method, nested_resource_type) do
|
41
|
-
nested_resource = @inner_object.send(nested_resource_type)
|
42
|
-
return nil if nested_resource.nil?
|
43
|
-
klass.new(@inner_object.send(nested_resource_type))
|
44
|
-
end
|
5
|
+
def self.http=(http)
|
6
|
+
@@http = http
|
45
7
|
end
|
46
8
|
|
47
|
-
def self.
|
48
|
-
|
49
|
-
klass = OpenStruct if (klass.nil? || (klass.superclass != RestfulResource))
|
50
|
-
|
51
|
-
self.send(:define_method, nested_resource_type) do
|
52
|
-
@inner_object.send(nested_resource_type).map { |obj| klass.new(obj) }
|
53
|
-
end
|
9
|
+
def self.http
|
10
|
+
@@http ||= RestfulResource::HttpClient.new()
|
54
11
|
end
|
55
12
|
|
56
|
-
def
|
57
|
-
@
|
13
|
+
def self.base_url=(url)
|
14
|
+
@base_url = URI.parse(url)
|
58
15
|
end
|
59
16
|
|
60
|
-
def
|
61
|
-
|
62
|
-
@inner_object.send(method)
|
63
|
-
else
|
64
|
-
super(method)
|
65
|
-
end
|
17
|
+
def self.base_url
|
18
|
+
@base_url
|
66
19
|
end
|
67
20
|
|
68
|
-
def
|
69
|
-
|
21
|
+
def self.resource_path(url)
|
22
|
+
@resource_path = url
|
70
23
|
end
|
71
24
|
|
72
|
-
def
|
73
|
-
|
25
|
+
def self.find(id, params={})
|
26
|
+
response = http.get(member_url(id, params))
|
27
|
+
self.new(parse_json(response.body))
|
74
28
|
end
|
75
29
|
|
76
|
-
def self.
|
77
|
-
response =
|
78
|
-
self.
|
30
|
+
def self.where(params={})
|
31
|
+
response = http.get(collection_url(params))
|
32
|
+
self.paginate_response(response)
|
79
33
|
end
|
80
34
|
|
81
|
-
def self.
|
82
|
-
|
83
|
-
response
|
84
|
-
self.new(ActiveSupport::JSON.decode(response))
|
35
|
+
def self.get(params={})
|
36
|
+
response = http.get(collection_url(params))
|
37
|
+
RestfulResource::OpenObject.new(parse_json(response.body))
|
85
38
|
end
|
86
39
|
|
87
|
-
def self.
|
88
|
-
|
89
|
-
result = parse(RestClient.put("#{url}/#{id}", attributes))
|
90
|
-
rescue RestClient::UnprocessableEntity => e
|
91
|
-
errors = parse(e.response)
|
92
|
-
result = attributes.merge(errors: errors)
|
93
|
-
end
|
94
|
-
self.new(result)
|
40
|
+
def self.all
|
41
|
+
self.where
|
95
42
|
end
|
96
43
|
|
97
|
-
def self.
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
errors = parse(e.response)
|
102
|
-
result = attributes.merge(errors: errors)
|
103
|
-
end
|
104
|
-
self.new(result)
|
44
|
+
def self.action(action_name)
|
45
|
+
clone = self.clone
|
46
|
+
clone.action_prefix = action_name
|
47
|
+
clone
|
105
48
|
end
|
106
49
|
|
107
|
-
def self.
|
108
|
-
|
109
|
-
paginate_response(response)
|
50
|
+
def self.action_prefix=(action_prefix)
|
51
|
+
@action_prefix = action_prefix.to_s
|
110
52
|
end
|
111
53
|
|
112
|
-
def
|
113
|
-
|
114
|
-
response = resource.get
|
115
|
-
paginate_response(response)
|
54
|
+
def as_json(options=nil)
|
55
|
+
@inner_object.send(:table).as_json(options)
|
116
56
|
end
|
117
57
|
|
118
|
-
|
119
|
-
|
120
|
-
|
58
|
+
private
|
59
|
+
def self.merge_url_paths(uri, *paths)
|
60
|
+
uri.merge(paths.compact.join('/')).to_s
|
121
61
|
end
|
122
62
|
|
123
|
-
def self.
|
124
|
-
|
63
|
+
def self.member_url(id, params)
|
64
|
+
url = merge_url_paths(superclass.base_url, @resource_path, id, @action_prefix)
|
65
|
+
replace_parameters(url, params)
|
125
66
|
end
|
126
67
|
|
127
|
-
def self.
|
128
|
-
|
68
|
+
def self.collection_url(params)
|
69
|
+
url = merge_url_paths(superclass.base_url, @resource_path, @action_prefix)
|
70
|
+
replace_parameters(url, params)
|
129
71
|
end
|
130
72
|
|
131
|
-
def self.
|
132
|
-
|
133
|
-
|
134
|
-
while page
|
135
|
-
page_data = self.all(page: page);
|
136
|
-
results += page_data
|
137
|
-
page = page_data.next_page
|
73
|
+
def self.new_collection(json)
|
74
|
+
json.map do |element|
|
75
|
+
self.new(element)
|
138
76
|
end
|
139
|
-
|
140
|
-
results
|
141
77
|
end
|
142
78
|
|
143
|
-
def self.
|
79
|
+
def self.parse_json(json)
|
144
80
|
ActiveSupport::JSON.decode(json)
|
145
81
|
end
|
146
82
|
|
147
|
-
def
|
148
|
-
|
149
|
-
|
83
|
+
def self.replace_parameters(url, params)
|
84
|
+
missing_params = []
|
85
|
+
params = params.with_indifferent_access
|
150
86
|
|
151
|
-
|
152
|
-
|
87
|
+
url_params = url.scan(/:([A-Za-z][^\/]*)/).flatten
|
88
|
+
url_params.each do |key|
|
89
|
+
value = params.delete(key)
|
90
|
+
if value.nil?
|
91
|
+
missing_params << key
|
92
|
+
else
|
93
|
+
url = url.gsub(':'+key, value.to_s)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
if missing_params.any?
|
98
|
+
raise ParameterMissingError.new(missing_params)
|
99
|
+
end
|
100
|
+
|
101
|
+
url = url + "?#{params.to_query}" unless params.empty?
|
102
|
+
url
|
153
103
|
end
|
154
104
|
|
155
|
-
private
|
156
105
|
def self.paginate_response(response)
|
157
106
|
links_header = response.headers[:links]
|
158
107
|
links = LinkHeader.parse(links_header)
|
@@ -160,15 +109,8 @@ module RestfulResource
|
|
160
109
|
prev_url = links.find_link(['rel', 'prev']).try(:href)
|
161
110
|
next_url = links.find_link(['rel', 'next']).try(:href)
|
162
111
|
|
163
|
-
array =
|
112
|
+
array = parse_json(response.body).map { |attributes| self.new(attributes) }
|
164
113
|
PaginatedArray.new(array, previous_page_url: prev_url, next_page_url: next_url)
|
165
114
|
end
|
166
|
-
|
167
|
-
def self.create_new_resource(params={})
|
168
|
-
url, other_params = processed_url_and_params(params)
|
169
|
-
url += "?#{other_params.to_query}" if not other_params.empty?
|
170
|
-
resource = RestClient::Resource.new("#{url}")
|
171
|
-
end
|
172
|
-
|
173
115
|
end
|
174
116
|
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module RestfulResource
|
2
|
+
class OldBase
|
3
|
+
def self.url=(url)
|
4
|
+
@url = url
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.processed_url_and_params(params={})
|
8
|
+
url = @url
|
9
|
+
other_params = params.clone
|
10
|
+
missing_params = []
|
11
|
+
|
12
|
+
url_params = url.scan(/:([A-Za-z][^\/]*)/).flatten
|
13
|
+
url_params.each do |key|
|
14
|
+
value = other_params.delete(key.to_sym)
|
15
|
+
if value.nil?
|
16
|
+
missing_params << key
|
17
|
+
else
|
18
|
+
url = url.gsub(':'+key.to_s, value.to_s)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
if missing_params.any?
|
23
|
+
raise ParameterMissingError.new(missing_params)
|
24
|
+
end
|
25
|
+
|
26
|
+
[url, other_params]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.url(params={})
|
30
|
+
processed_url_and_params(params).first
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def self.has_one(nested_resource_type)
|
35
|
+
klass = nested_resource_type.to_s.camelize.safe_constantize
|
36
|
+
klass = OpenStruct if (klass.nil? || klass.superclass != RestfulResource)
|
37
|
+
|
38
|
+
self.send(:define_method, nested_resource_type) do
|
39
|
+
nested_resource = @inner_object.send(nested_resource_type)
|
40
|
+
return nil if nested_resource.nil?
|
41
|
+
klass.new(@inner_object.send(nested_resource_type))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.has_many(nested_resource_type)
|
46
|
+
klass = nested_resource_type.to_s.singularize.camelize.safe_constantize
|
47
|
+
klass = OpenStruct if (klass.nil? || (klass.superclass != RestfulResource))
|
48
|
+
|
49
|
+
self.send(:define_method, nested_resource_type) do
|
50
|
+
@inner_object.send(nested_resource_type).map { |obj| klass.new(obj) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(attributes = {}, hack_for_activeresource = false)
|
55
|
+
@inner_object = OpenStruct.new(attributes)
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_missing(method)
|
59
|
+
if @inner_object.respond_to?(method)
|
60
|
+
@inner_object.send(method)
|
61
|
+
else
|
62
|
+
super(method)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def respond_to?(method, include_private = false)
|
67
|
+
super || @inner_object.respond_to?(method, include_private)
|
68
|
+
end
|
69
|
+
|
70
|
+
def valid?
|
71
|
+
errors.nil? || errors.count == g
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.find(id, url_params={})
|
75
|
+
response = RestClient.get("#{url(url_params)}/#{id}", params: {})
|
76
|
+
self.new(ActiveSupport::JSON.decode(response))
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.get_one(url_params={})
|
80
|
+
resource = create_new_resource(url_params)
|
81
|
+
response = resource.get
|
82
|
+
self.new(ActiveSupport::JSON.decode(response))
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.update_attributes(id, attributes)
|
86
|
+
begin
|
87
|
+
result = parse(RestClient.put("#{url}/#{id}", attributes))
|
88
|
+
rescue RestClient::UnprocessableEntity => e
|
89
|
+
errors = parse(e.response)
|
90
|
+
result = attributes.merge(errors: errors)
|
91
|
+
end
|
92
|
+
self.new(result)
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.create(attributes)
|
96
|
+
begin
|
97
|
+
result = parse(RestClient.post("#{url}", attributes))
|
98
|
+
rescue RestClient::UnprocessableEntity => e
|
99
|
+
errors = parse(e.response)
|
100
|
+
result = attributes.merge(errors: errors)
|
101
|
+
end
|
102
|
+
self.new(result)
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.search(params = {})
|
106
|
+
response = RestClient.get("#{url}/search", params: params)
|
107
|
+
paginate_response(response)
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.all(params = {})
|
111
|
+
resource = create_new_resource(params)
|
112
|
+
response = resource.get
|
113
|
+
paginate_response(response)
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.get(postfix_url = "", params = {})
|
117
|
+
response = RestClient.get("#{url}#{postfix_url}", params: params)
|
118
|
+
paginate_response(response)
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.put(postfix_url = "", params = {})
|
122
|
+
response = RestClient.put("#{url}#{postfix_url}", params)
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.post(postfix_url = "", params = {})
|
126
|
+
response = RestClient.post("#{url}#{postfix_url}", params)
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.all_not_paginated
|
130
|
+
page = 1
|
131
|
+
results = []
|
132
|
+
while page
|
133
|
+
page_data = self.all(page: page);
|
134
|
+
results += page_data
|
135
|
+
page = page_data.next_page
|
136
|
+
end
|
137
|
+
|
138
|
+
results
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.parse(json)
|
142
|
+
ActiveSupport::JSON.decode(json)
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_json(*args)
|
146
|
+
@inner_object.send(:table).to_json(*args)
|
147
|
+
end
|
148
|
+
|
149
|
+
def as_json(*)
|
150
|
+
@inner_object.send(:table).as_json
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
def self.paginate_response(response)
|
155
|
+
links_header = response.headers[:links]
|
156
|
+
links = LinkHeader.parse(links_header)
|
157
|
+
|
158
|
+
prev_url = links.find_link(['rel', 'prev']).try(:href)
|
159
|
+
next_url = links.find_link(['rel', 'next']).try(:href)
|
160
|
+
|
161
|
+
array = ActiveSupport::JSON.decode(response).map { |attributes| self.new(attributes) }
|
162
|
+
PaginatedArray.new(array, previous_page_url: prev_url, next_page_url: next_url)
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.create_new_resource(params={})
|
166
|
+
url, other_params = processed_url_and_params(params)
|
167
|
+
url += "?#{other_params.to_query}" if not other_params.empty?
|
168
|
+
resource = RestClient::Resource.new("#{url}")
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RestfulResource
|
2
|
+
class OpenObject
|
3
|
+
def initialize(attributes = {}, hack_for_activeresource = false)
|
4
|
+
@inner_object = OpenStruct.new(attributes)
|
5
|
+
end
|
6
|
+
|
7
|
+
def method_missing(method)
|
8
|
+
if @inner_object.respond_to?(method)
|
9
|
+
@inner_object.send(method)
|
10
|
+
else
|
11
|
+
super(method)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def respond_to?(method, include_private = false)
|
16
|
+
super || @inner_object.respond_to?(method, include_private)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/restful_resource.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
require 'rack'
|
2
|
+
require 'uri'
|
2
3
|
require 'link_header'
|
3
4
|
require 'restclient'
|
4
5
|
require 'active_support'
|
5
6
|
require 'active_support/all'
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
require_relative "restful_resource/version"
|
8
|
+
require_relative "restful_resource/paginated_array"
|
9
|
+
require_relative "restful_resource/parameter_missing_error"
|
10
|
+
require_relative "restful_resource/open_object"
|
11
|
+
require_relative 'restful_resource/response'
|
12
|
+
require_relative 'restful_resource/http_client'
|
13
|
+
require_relative "restful_resource/associations"
|
14
|
+
require_relative "restful_resource/base"
|
15
|
+
require_relative "restful_resource/old_base"
|
10
16
|
|
data/spec/fixtures.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
class Make < RestfulResource::Base
|
2
|
+
resource_path "makes"
|
3
|
+
has_many :models
|
4
|
+
end
|
5
|
+
|
6
|
+
class Model < RestfulResource::Base
|
7
|
+
has_one :make
|
8
|
+
resource_path "groups/:group_id/makes/:make_slug/models"
|
9
|
+
end
|
10
|
+
|
11
|
+
class BaseA < RestfulResource::Base
|
12
|
+
end
|
13
|
+
|
14
|
+
class BaseB < RestfulResource::Base
|
15
|
+
end
|
16
|
+
|
17
|
+
class TestA < BaseA
|
18
|
+
self.resource_path "testa"
|
19
|
+
end
|
20
|
+
|
21
|
+
class TestB < BaseB
|
22
|
+
self.resource_path "testb"
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe RestfulResource::Associations do
|
4
|
+
describe "#has_many" do
|
5
|
+
it "should add a method to access nested resource" do
|
6
|
+
make = Make.new({
|
7
|
+
name: 'Volkswagen',
|
8
|
+
models:
|
9
|
+
[
|
10
|
+
{name: 'Golf', rrp: 1000},
|
11
|
+
{name: 'Passat', rrp: 3000}
|
12
|
+
]
|
13
|
+
})
|
14
|
+
|
15
|
+
expect(make.models.first.name).to eq 'Golf'
|
16
|
+
expect(make.models.last.name).to eq 'Passat'
|
17
|
+
expect(make.models.first.rrp).to eq 1000
|
18
|
+
expect(make.models.last.rrp).to eq 3000
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#has_one" do
|
23
|
+
it "should add a method to access nested resource" do
|
24
|
+
model = Model.new({
|
25
|
+
name: 'Golf',
|
26
|
+
make: {name: 'Volkswagen'}
|
27
|
+
})
|
28
|
+
|
29
|
+
expect(model.make.name).to eq 'Volkswagen'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,125 +1,148 @@
|
|
1
1
|
require_relative '../spec_helper'
|
2
2
|
|
3
3
|
describe RestfulResource::Base do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
team = Team.find(15)
|
10
|
-
|
11
|
-
expect(team.id).to eq 15
|
12
|
-
expect(team.name).to eq 'Arsenal'
|
13
|
-
end
|
4
|
+
before :each do
|
5
|
+
@mock_http = double("mock_http")
|
6
|
+
RestfulResource::Base.http = @mock_http
|
7
|
+
RestfulResource::Base.base_url = "http://api.carwow.co.uk/"
|
8
|
+
end
|
14
9
|
|
15
|
-
|
16
|
-
|
17
|
-
stub_get('http://api.carwow.co.uk/teams/15/players/1', response)
|
10
|
+
it "should act as an openobject" do
|
11
|
+
object = RestfulResource::Base.new(name: 'David', surname: 'Santoro')
|
18
12
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
expect(player.team_id).to eq 15
|
23
|
-
expect(player.name).to eq 'David'
|
24
|
-
end
|
13
|
+
expect(object.name).to eq 'David'
|
14
|
+
expect(object.surname).to eq 'Santoro'
|
15
|
+
expect { object.age }.to raise_error(NoMethodError)
|
25
16
|
end
|
26
17
|
|
27
|
-
|
28
|
-
it "should return
|
29
|
-
|
18
|
+
describe "#find" do
|
19
|
+
it "should return an object instance for the correct id" do
|
20
|
+
expected_response = RestfulResource::Response.new(body: {id: 12}.to_json)
|
21
|
+
expect_get("http://api.carwow.co.uk/makes/12", expected_response)
|
22
|
+
|
23
|
+
object = Make.find(12)
|
30
24
|
|
31
|
-
expect(
|
25
|
+
expect(object).not_to be_nil
|
26
|
+
expect(object.id).to be 12
|
32
27
|
end
|
33
28
|
|
34
|
-
it "should return
|
35
|
-
|
29
|
+
it "should return an object instance for nested routes" do
|
30
|
+
expected_response = RestfulResource::Response.new(body: {name: 'Golf', price: 15000}.to_json)
|
31
|
+
expect_get("http://api.carwow.co.uk/groups/15/makes/Volkswagen/models/Golf", expected_response)
|
36
32
|
|
37
|
-
|
33
|
+
object = Model.find('Golf', make_slug: 'Volkswagen', group_id: 15)
|
34
|
+
|
35
|
+
expect(object).not_to be_nil
|
36
|
+
expect(object.name).to eq 'Golf'
|
37
|
+
expect(object.price).to eq 15000
|
38
38
|
end
|
39
|
+
end
|
39
40
|
|
40
|
-
|
41
|
-
|
41
|
+
describe "#where" do
|
42
|
+
it "should return an array of objects" do
|
43
|
+
expected_response = RestfulResource::Response.new(body: [{name: 'Golf', price: 15000}, {name: 'Polo', price: 11000}].to_json)
|
44
|
+
expect_get("http://api.carwow.co.uk/groups/15/makes/Volkswagen/models?on_sale=true", expected_response)
|
45
|
+
object = Model.where(make_slug: 'Volkswagen', on_sale: true, group_id: 15)
|
42
46
|
|
43
|
-
|
44
|
-
expect
|
47
|
+
expect(object).not_to be_nil
|
48
|
+
expect(object.length).to eq 2
|
49
|
+
expect(object.first.name).to eq 'Golf'
|
50
|
+
expect(object.first.price).to eq 15000
|
45
51
|
end
|
46
52
|
|
47
|
-
it "should
|
48
|
-
|
53
|
+
it "should provide a paginated result if response contains rest pagination headers" do
|
54
|
+
expected_response = response_with_page_information()
|
55
|
+
expect_get("http://api.carwow.co.uk/groups/15/makes/Volkswagen/models", expected_response)
|
56
|
+
|
57
|
+
models = Model.where(group_id: 15, make_slug: 'Volkswagen')
|
49
58
|
|
50
|
-
expect
|
59
|
+
expect(models.first.name).to eq 'Golf'
|
60
|
+
expect(models.last.name).to eq 'Polo'
|
61
|
+
expect(models.previous_page).to be_nil
|
62
|
+
expect(models.next_page).to eq 2
|
51
63
|
end
|
64
|
+
end
|
52
65
|
|
53
|
-
|
54
|
-
|
66
|
+
describe "#all" do
|
67
|
+
it "should return all items" do
|
68
|
+
expected_response = RestfulResource::Response.new(body: [{name: 'Volkswagen'}, {name: 'Audi'}].to_json)
|
69
|
+
expect_get("http://api.carwow.co.uk/makes", expected_response)
|
70
|
+
makes = Make.all
|
55
71
|
|
56
|
-
expect
|
72
|
+
expect(makes).not_to be_nil
|
73
|
+
expect(makes.length).to eq 2
|
74
|
+
expect(makes.first.name).to eq 'Volkswagen'
|
57
75
|
end
|
58
76
|
end
|
59
77
|
|
60
|
-
|
61
|
-
it "should
|
62
|
-
|
63
|
-
|
78
|
+
describe "#base_url" do
|
79
|
+
it "should be different for each subclass of Base" do
|
80
|
+
BaseA.base_url = "http://a.carwow.co.uk"
|
81
|
+
BaseB.base_url = "http://b.carwow.co.uk"
|
64
82
|
|
65
|
-
|
83
|
+
expect_get('http://a.carwow.co.uk/testa/1', RestfulResource::Response.new())
|
84
|
+
expect_get('http://b.carwow.co.uk/testb/2', RestfulResource::Response.new())
|
66
85
|
|
67
|
-
|
68
|
-
|
69
|
-
expect(teams.first.name).to eq 'Arsenal'
|
70
|
-
expect(teams.last.name).to eq 'Chelsea'
|
86
|
+
TestA.find(1)
|
87
|
+
TestB.find(2)
|
71
88
|
end
|
72
89
|
end
|
73
90
|
|
74
|
-
|
75
|
-
|
91
|
+
describe "#action" do
|
92
|
+
it "should retrieve a resource using a custom action" do
|
93
|
+
expect_get('http://api.carwow.co.uk/makes/15/lazy', RestfulResource::Response.new(body: {name: 'Volk.'}.to_json))
|
76
94
|
|
77
|
-
|
78
|
-
expect(example.surname).to eq 'Santoro'
|
79
|
-
end
|
95
|
+
make = Make.action(:lazy).find(15)
|
80
96
|
|
81
|
-
|
82
|
-
|
97
|
+
expect(make.name).to eq 'Volk.'
|
98
|
+
end
|
83
99
|
|
84
|
-
|
85
|
-
|
100
|
+
it 'should retrieve many resources using a custom action' do
|
101
|
+
expect_get('http://api.carwow.co.uk/makes/available', RestfulResource::Response.new(body: [{name: 'Audi'}, {name: 'Fiat'}].to_json))
|
86
102
|
|
87
|
-
|
88
|
-
player = Player.new({name: 'David', surname: 'Santoro'})
|
89
|
-
|
90
|
-
expect { player.age }.to raise_error(NoMethodError)
|
91
|
-
end
|
103
|
+
make = Make.action(:available).all
|
92
104
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
allow(RestClient::Resource).to receive(:new).with(url).and_return resource
|
105
|
+
expect(make.first.name).to eq 'Audi'
|
106
|
+
expect(make.last.name).to eq 'Fiat'
|
107
|
+
end
|
97
108
|
end
|
98
109
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
end
|
110
|
+
describe "#get" do
|
111
|
+
it "should return an open_object" do
|
112
|
+
expected_response = RestfulResource::Response.new(body: {average_score: 4.3}.to_json)
|
113
|
+
expect_get('http://api.carwow.co.uk/makes/average_score?make_slug%5B%5D=Volkswagen&make_slug%5B%5D=Audi', expected_response)
|
104
114
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
response
|
115
|
+
object = Make.action(:average_score).get(make_slug: ['Volkswagen', 'Audi'])
|
116
|
+
|
117
|
+
expect(object.average_score).to eq 4.3
|
118
|
+
expect(object.class).to eq RestfulResource::OpenObject
|
119
|
+
end
|
111
120
|
end
|
112
|
-
end
|
113
121
|
|
114
|
-
|
115
|
-
|
116
|
-
|
122
|
+
describe ".as_json" do
|
123
|
+
before :each do
|
124
|
+
expected_response = RestfulResource::Response.new(body: [{name: 'Audi', slug: 'Audi-Slug'}, {name: 'Fiat', slug: 'Fiat-Slug'}].to_json)
|
125
|
+
expect_get('http://api.carwow.co.uk/makes', expected_response)
|
117
126
|
|
118
|
-
|
119
|
-
|
127
|
+
@makes = Make.all
|
128
|
+
end
|
120
129
|
|
121
|
-
|
122
|
-
|
130
|
+
it 'should not return inner object table' do
|
131
|
+
expect(@makes.first.as_json).to eq ({'name' => 'Audi', 'slug' => 'Audi-Slug'})
|
132
|
+
end
|
123
133
|
|
134
|
+
it 'should return inner object table on selected fields' do
|
135
|
+
expect(@makes.last.as_json(only: [:name])).to eq ({'name' => 'Fiat'})
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def expect_get(url, response)
|
140
|
+
expect(@mock_http).to receive(:get).with(url).and_return(response)
|
141
|
+
end
|
124
142
|
|
143
|
+
def response_with_page_information
|
144
|
+
RestfulResource::Response.new(body: [{ id: 1, name: 'Golf'}, { id: 2, name: 'Polo' }].to_json,
|
145
|
+
headers: { links: '<http://api.carwow.co.uk/makes/Volkswagen/models.json?page=6>;rel="last",<http://api.carwow.co.uk/makes/Volkswagen/models.json?page=2>;rel="next"'})
|
146
|
+
end
|
147
|
+
end
|
125
148
|
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe RestfulResource::OldBase do
|
4
|
+
context "#find" do
|
5
|
+
it "should retrieve a resource with a simple url" do
|
6
|
+
response = { id: 15, name: 'Arsenal' }.to_json
|
7
|
+
stub_get('http://api.carwow.co.uk/teams/15', response)
|
8
|
+
|
9
|
+
team = Team.find(15)
|
10
|
+
|
11
|
+
expect(team.id).to eq 15
|
12
|
+
expect(team.name).to eq 'Arsenal'
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should retrieve a nested resource" do
|
16
|
+
response = { id: 1, team_id: 15, name: 'David', santoro: 'Santoro' }.to_json
|
17
|
+
stub_get('http://api.carwow.co.uk/teams/15/players/1', response)
|
18
|
+
|
19
|
+
player = Player.find(1, team_id: 15)
|
20
|
+
|
21
|
+
expect(player.id).to eq 1
|
22
|
+
expect(player.team_id).to eq 15
|
23
|
+
expect(player.name).to eq 'David'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "#url" do
|
28
|
+
it "should return the url set if no extra parameters are necessary" do
|
29
|
+
Player.url = 'http://api.carwow.co.uk/players'
|
30
|
+
|
31
|
+
expect(Player.url).to eq 'http://api.carwow.co.uk/players'
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should return the url with the right parameters replaced" do
|
35
|
+
Player.url = 'http://api.carwow.co.uk/teams/:team_id/players'
|
36
|
+
|
37
|
+
expect(Player.url(team_id: 13)).to eq 'http://api.carwow.co.uk/teams/13/players'
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should raise a parameter required exception if parameter needed and not provided" do
|
41
|
+
Player.url = 'http://api.carwow.co.uk/countries/:country_slug/teams/:team_id/players'
|
42
|
+
|
43
|
+
expected_error_message = 'You must pass values for the following parameters: [country_slug, team_id]'
|
44
|
+
expect { Player.url }.to raise_error(RestfulResource::ParameterMissingError, expected_error_message)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should not confuse port number as a parameter" do
|
48
|
+
Player.url = 'http://api.carwow.co.uk:7000/teams/:team_id/players'
|
49
|
+
|
50
|
+
expect { Player.url(team_id: 13) }.not_to raise_error
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "#all" do
|
55
|
+
it "should provide a paginated result if response contains rest pagination headers" do
|
56
|
+
response = response_with_page_information()
|
57
|
+
stub_new_resource('http://api.carwow.co.uk/teams', response)
|
58
|
+
|
59
|
+
teams = Team.all
|
60
|
+
|
61
|
+
expect(teams.previous_page).to be_nil
|
62
|
+
expect(teams.next_page).to eq 2
|
63
|
+
expect(teams.first.name).to eq 'Arsenal'
|
64
|
+
expect(teams.last.name).to eq 'Chelsea'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should act as an openstruct" do
|
69
|
+
example = Player.new(name: 'David', surname: 'Santoro')
|
70
|
+
|
71
|
+
expect(example.name).to eq 'David'
|
72
|
+
expect(example.surname).to eq 'Santoro'
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should use some params for the url and other for the query string" do
|
76
|
+
stub_new_resource('http://api.carwow.co.uk/teams/15/players?name_like=Ars', response_with_page_information)
|
77
|
+
|
78
|
+
players = Player.all(team_id: 15, name_like: 'Ars')
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should raise an error when accessing a field that doesn't exist" do
|
82
|
+
player = Player.new({name: 'David', surname: 'Santoro'})
|
83
|
+
|
84
|
+
expect { player.age }.to raise_error(NoMethodError)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
def stub_new_resource(url, fake_response)
|
89
|
+
resource = instance_double('RestClient::Resource', get: fake_response)
|
90
|
+
allow(RestClient::Resource).to receive(:new).with(url).and_return resource
|
91
|
+
end
|
92
|
+
|
93
|
+
def stub_get(url, fake_response, params = {})
|
94
|
+
expect(RestClient).to receive(:get).
|
95
|
+
with(url, params: params).
|
96
|
+
and_return(fake_response)
|
97
|
+
end
|
98
|
+
|
99
|
+
def response_with_page_information
|
100
|
+
response = [{ id: 1, name: 'Arsenal'}, { id: 2, name: 'Chelsea' }].to_json
|
101
|
+
allow(response).to receive(:headers) {
|
102
|
+
{:links => '<http://api.carwow.co.uk/teams.json?page=6>;rel="last",<http://api.carwow.co.uk/teams.json?page=2>;rel="next"'}
|
103
|
+
}
|
104
|
+
response
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class Team < RestfulResource::OldBase
|
109
|
+
self.url = "http://api.carwow.co.uk/teams"
|
110
|
+
end
|
111
|
+
|
112
|
+
class Player < RestfulResource::OldBase
|
113
|
+
has_one :team
|
114
|
+
|
115
|
+
self.url = "http://api.carwow.co.uk/teams/:team_id/players"
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe RestfulResource::OpenObject do
|
4
|
+
it "should act as an openstruct" do
|
5
|
+
object = RestfulResource::OpenObject.new(name: 'David', surname: 'Santoro')
|
6
|
+
|
7
|
+
expect(object.name).to eq 'David'
|
8
|
+
expect(object.surname).to eq 'Santoro'
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should raise an error when accessing a field that doesn't exist" do
|
12
|
+
object = RestfulResource::OpenObject.new({name: 'David', surname: 'Santoro'})
|
13
|
+
|
14
|
+
expect { object.age }.to raise_error(NoMethodError)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe RestfulResource::Base do
|
4
|
+
context "#get" do
|
5
|
+
it "should throw an exception with wrong url" do
|
6
|
+
expect{RestClient.get("http://www.example.com/djksafjadl", params: {})}.to raise_error
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should throw an exception with wrong url" do
|
10
|
+
r = RestClient::Resource.new("http://www.example.com/djksafjadl")
|
11
|
+
expect{r.get}.to raise_error
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: restful_resource
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Santoro
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-10-
|
11
|
+
date: 2014-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -122,12 +122,22 @@ files:
|
|
122
122
|
- README.md
|
123
123
|
- Rakefile
|
124
124
|
- lib/restful_resource.rb
|
125
|
+
- lib/restful_resource/associations.rb
|
125
126
|
- lib/restful_resource/base.rb
|
127
|
+
- lib/restful_resource/http_client.rb
|
128
|
+
- lib/restful_resource/old_base.rb
|
129
|
+
- lib/restful_resource/open_object.rb
|
126
130
|
- lib/restful_resource/paginated_array.rb
|
127
131
|
- lib/restful_resource/parameter_missing_error.rb
|
132
|
+
- lib/restful_resource/response.rb
|
128
133
|
- lib/restful_resource/version.rb
|
129
134
|
- restful_resource.gemspec
|
135
|
+
- spec/fixtures.rb
|
136
|
+
- spec/restful_resource/associations_spec.rb
|
130
137
|
- spec/restful_resource/base_spec.rb
|
138
|
+
- spec/restful_resource/old_base_spec.rb
|
139
|
+
- spec/restful_resource/open_object_spec.rb
|
140
|
+
- spec/restful_resource/rest_client_spec.rb
|
131
141
|
- spec/spec_helper.rb
|
132
142
|
homepage: http://www.github.com/carwow/restful_resource
|
133
143
|
licenses:
|
@@ -155,5 +165,10 @@ specification_version: 4
|
|
155
165
|
summary: A simple activerecord inspired rest resource base class implemented using
|
156
166
|
rest-client
|
157
167
|
test_files:
|
168
|
+
- spec/fixtures.rb
|
169
|
+
- spec/restful_resource/associations_spec.rb
|
158
170
|
- spec/restful_resource/base_spec.rb
|
171
|
+
- spec/restful_resource/old_base_spec.rb
|
172
|
+
- spec/restful_resource/open_object_spec.rb
|
173
|
+
- spec/restful_resource/rest_client_spec.rb
|
159
174
|
- spec/spec_helper.rb
|