restful_resource 0.0.11 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb0baa3d62df8c2cf76abb026cf3f3d66f9cdd01
4
- data.tar.gz: 0e623b12bc7f5ea6add379f80ece6eb88f91c2a0
3
+ metadata.gz: 5e4f4e6af3b1bee81026d51b88de88305d854791
4
+ data.tar.gz: ccc94c88defe5f2867214778ba49b27af23ea42f
5
5
  SHA512:
6
- metadata.gz: e98a1b3a5b2d105c6cd520bd3c456b7cdeeef58c0993be275bfa6d90d9bad41ed701d17099268f0eb2a0787948bb1d32ff8bcf23658bd925ca5ffc91171546f8
7
- data.tar.gz: cce5a8ea9f6a743e4e0c2f65954426d744ab03584fb26274dcb4c814124a0f518aa8c9f5005222769191683f35f60b669d8d2fb6273f6b384ce2e379fb39cbcc
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
- def self.url=(url)
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
- path_params = path.scan(/:([A-Za-z][^\/]*)/).flatten
14
- path_params.each do |key|
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.has_many(nested_resource_type)
48
- klass = nested_resource_type.to_s.singularize.camelize.safe_constantize
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 initialize(attributes = {}, hack_for_activeresource = false)
57
- @inner_object = OpenStruct.new(attributes)
13
+ def self.base_url=(url)
14
+ @base_url = URI.parse(url)
58
15
  end
59
16
 
60
- def method_missing(method)
61
- if @inner_object.respond_to?(method)
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 respond_to?(method, include_private = false)
69
- super || @inner_object.respond_to?(method, include_private)
21
+ def self.resource_path(url)
22
+ @resource_path = url
70
23
  end
71
24
 
72
- def valid?
73
- errors.nil? || errors.count == g
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.find(id, url_params={})
77
- response = RestClient.get("#{url(url_params)}/#{id}", params: {})
78
- self.new(ActiveSupport::JSON.decode(response))
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.get_one(url_params={})
82
- resource = create_new_resource(url_params)
83
- response = resource.get
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.update_attributes(id, attributes)
88
- begin
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.create(attributes)
98
- begin
99
- result = parse(RestClient.post("#{url}", attributes))
100
- rescue RestClient::UnprocessableEntity => e
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.search(params = {})
108
- response = RestClient.get("#{url}/search", params: params)
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 self.all(params = {})
113
- resource = create_new_resource(params)
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
- def self.get(postfix_url = "", params = {})
119
- response = RestClient.get("#{url}#{postfix_url}", params: params)
120
- paginate_response(response)
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.put(postfix_url = "", params = {})
124
- response = RestClient.put("#{url}#{postfix_url}", params)
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.post(postfix_url = "", params = {})
128
- response = RestClient.post("#{url}#{postfix_url}", params)
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.all_not_paginated
132
- page = 1
133
- results = []
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.parse(json)
79
+ def self.parse_json(json)
144
80
  ActiveSupport::JSON.decode(json)
145
81
  end
146
82
 
147
- def to_json(*args)
148
- @inner_object.send(:table).to_json(*args)
149
- end
83
+ def self.replace_parameters(url, params)
84
+ missing_params = []
85
+ params = params.with_indifferent_access
150
86
 
151
- def as_json(*)
152
- @inner_object.send(:table).as_json
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 = ActiveSupport::JSON.decode(response).map { |attributes| self.new(attributes) }
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,8 @@
1
+ module RestfulResource
2
+ class HttpClient
3
+ def get(url)
4
+ response = RestClient.get(url, :accept => :json)
5
+ Response.new(body: response.body, headers: response.headers)
6
+ end
7
+ end
8
+ 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
@@ -0,0 +1,9 @@
1
+ module RestfulResource
2
+ class Response
3
+ attr_reader :body, :headers
4
+
5
+ def initialize(body: "{}", headers: {})
6
+ @body, @headers = body, headers
7
+ end
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module RestfulResource
2
- VERSION = "0.0.11"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -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
- require "restful_resource/version"
7
- require "restful_resource/paginated_array"
8
- require "restful_resource/parameter_missing_error"
9
- require "restful_resource/base"
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
- 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
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
- 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)
10
+ it "should act as an openobject" do
11
+ object = RestfulResource::Base.new(name: 'David', surname: 'Santoro')
18
12
 
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
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
- 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'
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(Player.url).to eq 'http://api.carwow.co.uk/players'
25
+ expect(object).not_to be_nil
26
+ expect(object.id).to be 12
32
27
  end
33
28
 
34
- it "should return the url with the right parameters replaced" do
35
- Player.url = 'http://api.carwow.co.uk/teams/:team_id/players'
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
- expect(Player.url(team_id: 13)).to eq 'http://api.carwow.co.uk/teams/13/players'
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
- 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'
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
- 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)
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 not confuse port number as a parameter" do
48
- Player.url = 'http://api.carwow.co.uk:7000/teams/:team_id/players'
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 { Player.url(team_id: 13) }.not_to raise_error
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
- it "should not substitue parameters in the http auth part of the url" do
54
- Player.url = 'http://admin:secret@api.carwow.co.uk:7000/teams/:team_id/players'
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 { Player.url(team_id: 13) }.not_to raise_error
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
- context "#all" do
61
- it "should provide a paginated result if response contains rest pagination headers" do
62
- response = response_with_page_information()
63
- stub_new_resource('http://api.carwow.co.uk/teams', response)
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
- teams = Team.all
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
- expect(teams.previous_page).to be_nil
68
- expect(teams.next_page).to eq 2
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
- it "should act as an openstruct" do
75
- example = Player.new(name: 'David', surname: 'Santoro')
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
- expect(example.name).to eq 'David'
78
- expect(example.surname).to eq 'Santoro'
79
- end
95
+ make = Make.action(:lazy).find(15)
80
96
 
81
- it "should use some params for the url and other for the query string" do
82
- stub_new_resource('http://api.carwow.co.uk/teams/15/players?name_like=Ars', response_with_page_information)
97
+ expect(make.name).to eq 'Volk.'
98
+ end
83
99
 
84
- players = Player.all(team_id: 15, name_like: 'Ars')
85
- end
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
- it "should raise an error when accessing a field that doesn't exist" do
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
- private
94
- def stub_new_resource(url, fake_response)
95
- resource = instance_double('RestClient::Resource', get: fake_response)
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
- def stub_get(url, fake_response, params = {})
100
- expect(RestClient).to receive(:get).
101
- with(url, params: params).
102
- and_return(fake_response)
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
- def response_with_page_information
106
- response = [{ id: 1, name: 'Arsenal'}, { id: 2, name: 'Chelsea' }].to_json
107
- allow(response).to receive(:headers) {
108
- {:links => '<http://api.carwow.co.uk/teams.json?page=6>;rel="last",<http://api.carwow.co.uk/teams.json?page=2>;rel="next"'}
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
- class Team < RestfulResource::Base
115
- self.url = "http://api.carwow.co.uk/teams"
116
- end
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
- class Player < RestfulResource::Base
119
- has_one :team
127
+ @makes = Make.all
128
+ end
120
129
 
121
- self.url = "http://api.carwow.co.uk/teams/:team_id/players"
122
- end
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
@@ -1,5 +1,6 @@
1
1
  require 'rspec'
2
2
  require_relative '../lib/restful_resource'
3
+ require_relative 'fixtures'
3
4
 
4
5
  RSpec.configure do |config|
5
6
  config.color = true
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.11
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-16 00:00:00.000000000 Z
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