frenetic 0.0.20.alpha.1 → 0.0.20.alpha.2
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.
- data/README.md +22 -3
- data/frenetic.gemspec +1 -0
- data/lib/frenetic.rb +23 -2
- data/lib/frenetic/concerns/briefly_memoizable.rb +30 -0
- data/lib/frenetic/configuration.rb +5 -0
- data/lib/frenetic/version.rb +1 -1
- data/spec/concerns/breifly_memoizable_spec.rb +59 -0
- data/spec/configuration_spec.rb +11 -0
- data/spec/fixtures/test_api_requests.rb +7 -2
- data/spec/support/timecop.rb +1 -0
- metadata +24 -3
data/README.md
CHANGED
@@ -91,10 +91,17 @@ the API contains, it can't navigate around it or parse any of it's responses.
|
|
91
91
|
This response will be requested by Frenetic whenever a call to
|
92
92
|
`YourAPI.description` is made.
|
93
93
|
|
94
|
-
**Note:** It is highly advised that
|
95
|
-
|
96
|
-
|
94
|
+
**Note:** It is highly advised that your API return Cache-Control headers in
|
95
|
+
this response. Frenetic needs to frequently refer to the API description to see
|
96
|
+
what is possible. This will result in lots of HTTP requests if you don't tell
|
97
|
+
it how long to wait before checking again.
|
97
98
|
|
99
|
+
If the API does return Cache-Control headers, Frenetic will always cache this
|
100
|
+
response regardless of which caching middleware you have configured or even if
|
101
|
+
you have caching disabled.
|
102
|
+
|
103
|
+
If you have no control over the API, refer to the
|
104
|
+
[Default Root Cache Age][root_cache] section.
|
98
105
|
|
99
106
|
|
100
107
|
|
@@ -250,6 +257,17 @@ Frenetic.new( url:url, adapter:Faraday::Adapter::Patron )
|
|
250
257
|
```
|
251
258
|
|
252
259
|
|
260
|
+
#### Default Root Cache Age
|
261
|
+
|
262
|
+
If you have no control over the API, you can explicitly tell Frenetic how long
|
263
|
+
to cache the API description for:
|
264
|
+
|
265
|
+
```ruby
|
266
|
+
Frenetic.new( url:url, default_root_cache_age:1.hour )
|
267
|
+
```
|
268
|
+
|
269
|
+
|
270
|
+
|
253
271
|
#### Faraday Middleware
|
254
272
|
|
255
273
|
Frenetic will yield its internal Faraday connection during initialization:
|
@@ -341,6 +359,7 @@ ideas on how to support other Hypermedia formats like [Collection+JSON][coll_jso
|
|
341
359
|
[spire.io]: http://api.spire.io/
|
342
360
|
[caching]: #response-caching
|
343
361
|
[faraday]: https://github.com/technoweenie/faraday
|
362
|
+
[root_cache]: #default-root-cache-age
|
344
363
|
[adapters]: https://github.com/lostisland/faraday/blob/c26a060acdd9eae356409c2ca79f1c22f8263de9/lib/faraday/adapter.rb#L7-L17
|
345
364
|
[rack_cache]: https://github.com/rtomayko/rack-cache
|
346
365
|
[coll_json]: http://amundsen.com/media-types/collection/
|
data/frenetic.gemspec
CHANGED
data/lib/frenetic.rb
CHANGED
@@ -4,6 +4,7 @@ require 'faraday_middleware'
|
|
4
4
|
|
5
5
|
require 'frenetic/version'
|
6
6
|
require 'frenetic/concerns/configurable'
|
7
|
+
require 'frenetic/concerns/briefly_memoizable'
|
7
8
|
require 'frenetic/middleware/hal_json'
|
8
9
|
require 'frenetic/resource'
|
9
10
|
require 'frenetic/resource_collection'
|
@@ -11,6 +12,7 @@ require 'frenetic/resource_collection'
|
|
11
12
|
class Frenetic
|
12
13
|
extend Forwardable
|
13
14
|
include Configurable
|
15
|
+
include BrieflyMemoizable
|
14
16
|
|
15
17
|
def_delegators :connection, :get, :put, :post, :delete
|
16
18
|
|
@@ -39,16 +41,35 @@ class Frenetic
|
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
42
|
-
#
|
43
|
-
#
|
44
|
+
# Since Frenetic needs to frequently refer to the API design, the result of
|
45
|
+
# this method is essentially cached, regardless of what caching middleware it
|
46
|
+
# is configured with.
|
47
|
+
#
|
48
|
+
# It fully honors the HTTP Cache-Control headers that are returned by the API.
|
49
|
+
#
|
50
|
+
# If no Cache-Control header is returned, then the results are not memoized.
|
44
51
|
def description
|
45
52
|
if response = get( config.url.to_s ) and response.success?
|
53
|
+
@description_age = cache_control_age( response.headers )
|
54
|
+
|
46
55
|
response.body
|
47
56
|
end
|
48
57
|
end
|
58
|
+
briefly_memoize :description
|
49
59
|
|
50
60
|
def schema
|
51
61
|
description['_embedded']['schema']
|
52
62
|
end
|
53
63
|
|
64
|
+
private
|
65
|
+
|
66
|
+
def cache_control_age( headers )
|
67
|
+
if cache_age = headers['Cache-Control']
|
68
|
+
age = cache_age.match(%r{max-age=(?<max_age>\d+)})[:max_age]
|
69
|
+
|
70
|
+
Time.now + age.to_i
|
71
|
+
else
|
72
|
+
config.default_root_cache_age
|
73
|
+
end
|
74
|
+
end
|
54
75
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
# Memoizes method calls, but only for a specific period of time.
|
4
|
+
# Useful for supporting HTTP Cache-Control without an external caching layer
|
5
|
+
# like Rack::Cache
|
6
|
+
module BrieflyMemoizable
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def briefly_memoize( symbol )
|
11
|
+
original_method = "_unmemoized_#{symbol}".to_sym
|
12
|
+
memoized_ivar = "@#{symbol}"
|
13
|
+
age_ivar = "@#{symbol}_age"
|
14
|
+
|
15
|
+
class_eval <<-EOS
|
16
|
+
if method_defined?(:#{original_method}) # if method_defined?(:_unmemoized_mime_type)
|
17
|
+
raise "Already memoized #{symbol}" # raise "Already memoized mime_type"
|
18
|
+
end # end
|
19
|
+
alias #{original_method} #{symbol} # alias _unmemoized_mime_type mime_type
|
20
|
+
|
21
|
+
def #{symbol}(*args) # def mime_type(*args)
|
22
|
+
#{memoized_ivar} = nil if #{age_ivar} && Time.now > #{age_ivar} # @mime_type = nil if @mime_type_age && Time.now > @mime_type_age
|
23
|
+
#
|
24
|
+
#{memoized_ivar} ||= #{original_method}(*args) # @mime_type ||= _unmemoized_mime_type(*args)
|
25
|
+
end # end
|
26
|
+
EOS
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -28,6 +28,7 @@ class Frenetic
|
|
28
28
|
adapter: adapter,
|
29
29
|
api_token: api_token,
|
30
30
|
cache: cache,
|
31
|
+
default_root_cache_age: default_root_cache_age,
|
31
32
|
headers: headers,
|
32
33
|
password: password,
|
33
34
|
url: url,
|
@@ -47,6 +48,10 @@ class Frenetic
|
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
51
|
+
def default_root_cache_age
|
52
|
+
@_cfg[:default_root_cache_age]
|
53
|
+
end
|
54
|
+
|
50
55
|
def headers
|
51
56
|
@@defaults[:headers].merge( @_cfg[:headers] || {} )
|
52
57
|
end
|
data/lib/frenetic/version.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BrieflyMemoizable do
|
4
|
+
let(:my_class) { Class.new }
|
5
|
+
|
6
|
+
before do
|
7
|
+
stub_const 'MyClass', my_class
|
8
|
+
|
9
|
+
MyClass.send :include, described_class
|
10
|
+
MyClass.class_eval do
|
11
|
+
def fetch
|
12
|
+
@fetch_age = Time.now + 3600
|
13
|
+
|
14
|
+
external_call
|
15
|
+
end
|
16
|
+
briefly_memoize :fetch
|
17
|
+
|
18
|
+
def external_call
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:instance) { MyClass.new }
|
25
|
+
|
26
|
+
describe '.briefly_memoize' do
|
27
|
+
context 'for an expensive method' do
|
28
|
+
before do
|
29
|
+
Timecop.freeze
|
30
|
+
|
31
|
+
instance.fetch
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'which is called again outside the memoize window' do
|
35
|
+
before do
|
36
|
+
Timecop.travel Time.now + 5400
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should be called' do
|
40
|
+
instance.should_receive(:external_call).once.and_call_original
|
41
|
+
|
42
|
+
instance.fetch
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'which is called again within the memoize window' do
|
47
|
+
before do
|
48
|
+
Timecop.travel Time.now + 1800
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should not be called' do
|
52
|
+
instance.should_receive(:external_call).never.and_call_original
|
53
|
+
|
54
|
+
instance.fetch
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/spec/configuration_spec.rb
CHANGED
@@ -15,6 +15,7 @@ describe Frenetic::Configuration do
|
|
15
15
|
it { should include :adapter }
|
16
16
|
it { should include :api_token }
|
17
17
|
it { should include :cache }
|
18
|
+
it { should include :default_root_cache_age }
|
18
19
|
it { should include :headers }
|
19
20
|
it { should include :password }
|
20
21
|
it { should include :url }
|
@@ -47,6 +48,16 @@ describe Frenetic::Configuration do
|
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
51
|
+
describe '#default_root_cache_age' do
|
52
|
+
let(:cfg) do
|
53
|
+
{ default_root_cache_age:3600 }
|
54
|
+
end
|
55
|
+
|
56
|
+
subject { instance.default_root_cache_age }
|
57
|
+
|
58
|
+
it { should == 3600 }
|
59
|
+
end
|
60
|
+
|
50
61
|
describe '#headers' do
|
51
62
|
let(:cfg) do
|
52
63
|
{ headers:{ accept:'MIME', x_foo:'BAR' } }
|
@@ -15,7 +15,12 @@ class HttpStubs
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def response( params = {} )
|
18
|
-
defaults.
|
18
|
+
defs = defaults.dup
|
19
|
+
headers = params.delete :headers
|
20
|
+
|
21
|
+
defs[:headers].merge! headers || {}
|
22
|
+
|
23
|
+
defs.merge( params ).tap do |p|
|
19
24
|
p[:body] = p[:body].to_json
|
20
25
|
end
|
21
26
|
end
|
@@ -36,7 +41,7 @@ class HttpStubs
|
|
36
41
|
|
37
42
|
def api_description
|
38
43
|
@rspec.stub_request( :any, 'example.com/api' )
|
39
|
-
.to_return response( body:schema )
|
44
|
+
.to_return response( body:schema, headers:{ 'Cache-Control' => 'max-age=3600, public' } )
|
40
45
|
end
|
41
46
|
|
42
47
|
def unknown_resource
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'timecop'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: frenetic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.20.alpha.
|
4
|
+
version: 0.0.20.alpha.2
|
5
5
|
prerelease: 7
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: faraday
|
@@ -139,6 +139,22 @@ dependencies:
|
|
139
139
|
- - ~>
|
140
140
|
- !ruby/object:Gem::Version
|
141
141
|
version: 1.11.0
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: timecop
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ~>
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 0.6.1
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ~>
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 0.6.1
|
142
158
|
description: An opinionated Ruby-based Hypermedia API client.
|
143
159
|
email:
|
144
160
|
- dlindahl@customink.com
|
@@ -156,6 +172,7 @@ files:
|
|
156
172
|
- Rakefile
|
157
173
|
- frenetic.gemspec
|
158
174
|
- lib/frenetic.rb
|
175
|
+
- lib/frenetic/concerns/briefly_memoizable.rb
|
159
176
|
- lib/frenetic/concerns/collection_rest_methods.rb
|
160
177
|
- lib/frenetic/concerns/configurable.rb
|
161
178
|
- lib/frenetic/concerns/hal_linked.rb
|
@@ -166,6 +183,7 @@ files:
|
|
166
183
|
- lib/frenetic/resource.rb
|
167
184
|
- lib/frenetic/resource_collection.rb
|
168
185
|
- lib/frenetic/version.rb
|
186
|
+
- spec/concerns/breifly_memoizable_spec.rb
|
169
187
|
- spec/concerns/configurable_spec.rb
|
170
188
|
- spec/concerns/hal_linked_spec.rb
|
171
189
|
- spec/concerns/member_rest_methods_spec.rb
|
@@ -178,6 +196,7 @@ files:
|
|
178
196
|
- spec/resource_spec.rb
|
179
197
|
- spec/spec_helper.rb
|
180
198
|
- spec/support/rspec.rb
|
199
|
+
- spec/support/timecop.rb
|
181
200
|
- spec/support/webmock.rb
|
182
201
|
homepage: http://dlindahl.github.com/frenetic/
|
183
202
|
licenses: []
|
@@ -193,7 +212,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
193
212
|
version: '0'
|
194
213
|
segments:
|
195
214
|
- 0
|
196
|
-
hash:
|
215
|
+
hash: -863323542650757064
|
197
216
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
198
217
|
none: false
|
199
218
|
requirements:
|
@@ -208,6 +227,7 @@ specification_version: 3
|
|
208
227
|
summary: Here lies a Ruby-based Hypermedia API client that expects HAL+JSON and makes
|
209
228
|
a lot of assumptions about your API.
|
210
229
|
test_files:
|
230
|
+
- spec/concerns/breifly_memoizable_spec.rb
|
211
231
|
- spec/concerns/configurable_spec.rb
|
212
232
|
- spec/concerns/hal_linked_spec.rb
|
213
233
|
- spec/concerns/member_rest_methods_spec.rb
|
@@ -220,4 +240,5 @@ test_files:
|
|
220
240
|
- spec/resource_spec.rb
|
221
241
|
- spec/spec_helper.rb
|
222
242
|
- spec/support/rspec.rb
|
243
|
+
- spec/support/timecop.rb
|
223
244
|
- spec/support/webmock.rb
|