virginia 0.4.0 → 0.5.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: 07e0d2abf4237b76b1f56162e819f4fd997d811b
4
- data.tar.gz: ebd38ebbd3ffb59194c3d5c89ba60be8f66b5241
3
+ metadata.gz: ecea8b6d003b01938bc430ec0dff8dd07d43f84e
4
+ data.tar.gz: ffa4a6fe704ae8de52f36aad3a01f168ebaacb22
5
5
  SHA512:
6
- metadata.gz: 2edaea0ae04deae86d67912d3169c3d502b6dc9c1cb85b436065c39eb6d06d9ae105d18b6d5eecd1ee0b9c2236d5a262cec16e171dcf0a33b80259396611e0b9
7
- data.tar.gz: b2f943bda56a7a176448eb6c9e64e66ef2d027bcc97a8baf80736629a96e49eb37bd17cc1d62e66274d5951c38b8479103e426cac1506838e6186b2fc34c0b89
6
+ metadata.gz: e76fcf59abbe5d17776ada161021491489ad9c81667f1f5baba592463a8873eb023c69fbc0927316fb46ed051ea07ccb15dc04977c0dd8e1833504259b1b648e
7
+ data.tar.gz: 7f793657d6ee01f31246fff65fdaf4a79cdf2a3bec287df951df0a10f7b3528dc227a9db252a600a098374cfa4b5cde4d469b8b190a01c3da9329c7762712224
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --format documentation
@@ -1,3 +1,14 @@
1
+ # develop
2
+
3
+ # Version 0.4.0
4
+ * BREAKING CHANGES! See the updated example in the README and the changes described below:
5
+ * Support for storing document content-types associated with each cached document. You must now specify the content type (or use the default of text/plain) when caching a document. This way Virginia knows how to serve it when it is requested.
6
+ * Wrap cached documents and store them as Virginia::DocumentCache::Document. The wrapped document is what will be returned from the cache.
7
+ * DocumentCache#fetch no longer accepts a `lifetime` argument. Instead, return an array of arguments suitable for passing to `#store` from the block, or return a complete DocumentCache::Document
8
+ * Switch DocumentCache to an Actor for better performance
9
+ * Load & start DocumentCache's actor by default
10
+ * Allow registering a document ID with a callback to populate it on demand
11
+
1
12
  # Version 0.4.0
2
13
  * Add document cache
3
14
 
data/Guardfile CHANGED
@@ -1,4 +1,4 @@
1
- guard 'rspec', :version => 2, :cli => '--format documentation' do
1
+ guard 'rspec', cmd: 'bundle exec rspec' do
2
2
  watch(%r{^spec/.+_spec\.rb$})
3
3
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
4
  watch('spec/spec_helper.rb') { "spec/" }
data/README.md CHANGED
@@ -57,16 +57,15 @@ Assuming you are using Sinatra like in the above examples, put this in your `lib
57
57
 
58
58
  ```Ruby
59
59
  require 'sinatra'
60
- require 'virginia/document_cache'
61
60
 
62
61
  get '/documents/:id' do
63
62
  begin
64
- grammar = Virginia::DocumentCache.fetch params[:id]
63
+ document = Virginia::DocumentCache.fetch params[:id]
64
+ headers['Content-Type'] = document.content_type
65
+ document.body
65
66
  rescue Virginia::DocumentCache::NotFound
66
67
  raise Sinatra::NotFound
67
68
  end
68
-
69
- grammar.to_s
70
69
  end
71
70
  ```
72
71
 
@@ -1,11 +1,10 @@
1
1
  require "adhearsion"
2
- require "active_support/dependencies/autoload"
3
- require "virginia/version"
4
- require "virginia/plugin"
2
+ %w(
3
+ version
4
+ plugin
5
+ service
6
+ document_cache
7
+ ).each { |file| require "virginia/#{file}" }
5
8
 
6
9
  module Virginia
7
- extend ActiveSupport::Autoload
8
- autoload :Plugin
9
- autoload :Service
10
- autoload :LoggingHandler
11
10
  end
@@ -1,29 +1,31 @@
1
1
  # encoding: utf-8
2
2
  require 'singleton'
3
+ require 'virginia/document_cache/document'
3
4
 
4
5
  module Virginia
5
6
  class DocumentCache
6
- include Singleton
7
+ include Celluloid
7
8
 
8
9
  NotFound = Class.new StandardError
9
10
 
10
- attr_reader :mutex
11
+ DEFAULT_CONTENT_TYPE = 'text/plain'
12
+ DEFAULT_LIFETIME = 10
13
+
14
+ execute_block_on_receiver :register, :send
11
15
 
12
16
  class << self
13
17
  def method_missing(m, *args, &block)
14
- instance.mutex.synchronize do
15
- instance.send m, *args, &block
16
- end
18
+ Celluloid::Actor[:virginia_document_cache].send m, *args, &block
17
19
  end
18
20
  end
19
21
 
20
22
  def initialize
21
- @mutex = Mutex.new
22
23
  @documents = {}
24
+ @document_creators = {}
23
25
 
24
- supervisor = Housekeeping.supervise_as :document_cache_housekeeping
25
- Adhearsion::Events.register_callback :shutdown do
26
- supervisor.terminate
26
+ every(60) do
27
+ logger.debug "Reaping expired cached document"
28
+ reap_expired!
27
29
  end
28
30
  end
29
31
 
@@ -31,56 +33,82 @@ module Virginia
31
33
  # @param [Object] document The document to be stored in the cache
32
34
  # @param [Fixnum, Nil] lifetime The amount of time in seconds the document should be kept. If nil, document will be kept indefinitely.
33
35
  # @param [String, Nil] id The ID to use to store the document. If nil, one will be generated.
34
- # @return [String] ID of the stored document
35
- def store(document, lifetime = 10, id = nil)
36
+ # @return [String] Cache ID of the stored document
37
+ def store(document, content_type = DEFAULT_CONTENT_TYPE, lifetime = DEFAULT_LIFETIME, id = nil)
38
+ return if document.nil? || document.to_s.empty?
39
+
36
40
  id ||= generate_id
37
- @documents[id] = {
38
- document: document,
39
- expires: lifetime ? Time.now + lifetime : nil
40
- }
41
- id
41
+ doc = Virginia::DocumentCache::Document.new id, document, content_type, lifetime
42
+ store_document doc
43
+ end
44
+
45
+ # Registers a new Virginia::DocumentCache::Document with the cache
46
+ # @param [Virginia::DocumentCache::Document] document The document to be stored in the cache
47
+ # @return [String] Cache ID of the stored document
48
+ def store_document(document)
49
+ @documents[document.id] = document
50
+ document.id
42
51
  end
43
52
 
44
53
  # Deletes a document from the cache
45
54
  # @param [Object] id ID of the document to be removed from the cache
46
55
  # @return [Object, Nil] document Returns the document if found in the cache, nil otherwise
47
56
  def delete(id)
48
- data = @documents.delete id
49
- data[:document]
57
+ @documents.delete id
50
58
  end
51
59
 
52
60
  # Retrieves a document from the cache
53
61
  # @param [String] id ID of the document to be retrieved from the cache
54
- # @param [Fixnum, Nil] lifetime The amount of time in seconds the document should be kept. If nil, document will be kept indefinitely.
55
62
  # @yield If given, will be used to generate the document, store it, and then return.
56
- # @return [Object] document Returns the document if found in the cache
57
- # @raises [NotFound] If the document is not found in the cache
58
- def fetch(id, lifetime = 10)
63
+ # @return [Virginia::DocumentCache::Document] Returns the document if found in the cache
64
+ # @raises [Virginia::DocumentCache::NotFound] If the document is not found in the cache
65
+ def fetch(id)
66
+ # Check for a registered creator first
67
+ check_and_run_creator id unless @documents.has_key? id
68
+
69
+ # If we still don't have a document, check for a supplied block
59
70
  unless @documents.has_key? id
71
+
60
72
  if block_given?
61
- store yield, lifetime, id
73
+ result = yield
74
+ if result.is_a? Document
75
+ store_document result
76
+ else
77
+ args = *yield
78
+ doc = Virginia::DocumentCache::Document.new id, args[0], args[1] || DEFAULT_CONTENT_TYPE, args[2] || DEFAULT_LIFETIME
79
+ store_document doc
80
+ end
62
81
  else
63
- raise NotFound
82
+ abort NotFound.new
64
83
  end
65
84
  end
66
-
67
- @documents[id][:document]
85
+
86
+ @documents[id]
68
87
  end
69
88
 
70
- def reap_expired!
71
- @documents.each_pair do |id, data|
72
- @documents.delete(id) if data[:expires] < Time.now
73
- end
89
+ # Registers a creation callback to populate a given document when requested
90
+ # @param [String] id ID of the document for which the creator should be registered
91
+ # @param [String] content_type Content-Type of the document
92
+ # @param [Fixnum, Nil] lifetime The amount of time in seconds the document should be kept. If nil, document will be kept indefinitely.
93
+ # @return [Nil, Object] nil if the ID was not found among the creators; otherwise the creator is returned
94
+ def register(id, content_type, lifetime = DEFAULT_LIFETIME, &callback)
95
+ @document_creators[id] = {
96
+ content_type: content_type,
97
+ lifetime: lifetime,
98
+ callback: callback,
99
+ }
74
100
  end
75
101
 
76
- class Housekeeping
77
- include Celluloid
102
+ # Removes a document creator registration by ID
103
+ # @param [String] id ID of the document for which the creator should be deleted
104
+ # @return [Nil, Object] nil if the ID was not found among the creators; otherwise the creator is returned
105
+ def unregister(id)
106
+ @document_creators.delete id
107
+ end
78
108
 
79
- def initialize
80
- every(1.minute) do
81
- logger.debug "Reaping expired cached document"
82
- DocumentCache.reap_expired!
83
- end
109
+ def reap_expired!
110
+ @documents.each_pair do |id, doc|
111
+ @documents.delete(id) if doc.expires_at && doc.expires_at < Time.now
84
112
  end
85
113
  end
86
114
 
@@ -89,5 +117,15 @@ module Virginia
89
117
  def generate_id
90
118
  SecureRandom.hex(16)
91
119
  end
120
+
121
+ def check_and_run_creator(id)
122
+ if creator = @document_creators[id]
123
+ begin
124
+ store_document Document.new id, creator[:callback].call, creator[:content_type], creator[:lifetime]
125
+ rescue => e
126
+ abort e
127
+ end
128
+ end
129
+ end
92
130
  end
93
131
  end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ module Virginia
4
+ class DocumentCache
5
+ class Document < Struct.new(:id, :body, :content_type, :lifetime, :created_at, :expires_at)
6
+ def initialize(id, body, content_type = 'text/plain', lifetime = nil)
7
+ self.id, self.body, self.content_type, self.lifetime = id, body, content_type, lifetime
8
+ self.created_at = Time.now
9
+ if self.lifetime
10
+ self.expires_at = self.created_at + self.lifetime
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -6,6 +6,12 @@ module Virginia
6
6
  run :virginia do
7
7
  logger.info "Virginia has been loaded"
8
8
  Service.start
9
+
10
+ # Document Cache
11
+ supervisor = Virginia::DocumentCache.supervise_as(:virginia_document_cache)
12
+ Adhearsion::Events.register_callback :shutdown do
13
+ supervisor.terminate
14
+ end
9
15
  end
10
16
 
11
17
  config :virginia do
@@ -1,3 +1,3 @@
1
1
  module Virginia
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -1,13 +1,18 @@
1
1
  require 'adhearsion'
2
2
  require 'virginia'
3
+ require 'timecop'
3
4
 
4
5
  ENV['AHN_ENV'] = 'test'
5
6
 
6
7
  RSpec.configure do |config|
7
- config.color_enabled = true
8
+ config.color = true
8
9
  config.tty = true
9
10
 
10
11
  config.filter_run :focus => true
11
12
  config.run_all_when_everything_filtered = true
13
+
14
+ config.after :each do
15
+ Timecop.return
16
+ end
12
17
  end
13
18
 
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Virginia::DocumentCache::Document do
5
+ let(:subject) { Virginia::DocumentCache::Document }
6
+
7
+ before :each do
8
+ Timecop.freeze
9
+ @document = subject.new 'fake_id', 'fake_content'
10
+ end
11
+
12
+ it 'should accurately represent my content' do
13
+ expect(@document.body).to eq 'fake_content'
14
+ end
15
+
16
+ it 'should automatically set the created_at time' do
17
+ expect(@document.created_at).to eq Time.now
18
+ end
19
+
20
+ it 'should default to an empty expiration time' do
21
+ expect(@document.expires_at).to be_nil
22
+ end
23
+
24
+ it 'should automatically determine the expiration time' do
25
+ # TODO: Use Timecop to make this test more precise
26
+ doc = subject.new '1', 'foo', 'text/plain', 30
27
+ expect(doc.expires_at).to eq Time.now + 30
28
+ end
29
+ end
@@ -0,0 +1,144 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'virginia/document_cache'
4
+
5
+ describe Virginia::DocumentCache do
6
+ let(:subject) { Virginia::DocumentCache.new }
7
+
8
+ it 'should store the document and return the ID' do
9
+ expect(subject.store('foo')).to be_a String
10
+ end
11
+
12
+ it 'should not store a nil document' do
13
+ expect(subject.store(nil)).to be_nil
14
+ end
15
+
16
+ it 'should not store an empty string document' do
17
+ expect(subject.store("")).to be_nil
18
+ end
19
+
20
+ it 'should allow me to retrieve the document by ID' do
21
+ doc = 'foobar'
22
+ id = subject.store doc
23
+ expect(subject.fetch(id).body).to eq doc
24
+ end
25
+
26
+ it 'should allow me to store and retrieve a document with a specified ID' do
27
+ subject.store 'foobar', 'text/plain', nil, 'abc123'
28
+ expect(subject.fetch('abc123').body).to eq 'foobar'
29
+ end
30
+
31
+ it 'should store the document content type' do
32
+ id = subject.store '{foo: "bar"}', 'application/json'
33
+ expect(subject.fetch(id).content_type).to eq 'application/json'
34
+ end
35
+
36
+ it 'should allow me to specify a lifetime' do
37
+ Timecop.freeze
38
+ id = subject.store 'foobar', 'text/plain', 30
39
+ doc = subject.fetch id
40
+ expect(doc.expires_at).to eq Time.now + 30
41
+ end
42
+
43
+ it 'should allow documents that do not expire' do
44
+ Timecop.freeze
45
+ id = subject.store 'foobar', 'text/plain', nil
46
+ doc = subject.fetch id
47
+ expect(doc.expires_at).to be nil
48
+ Timecop.travel Time.now + (20 * 365 * 24 * 60 * 60) # 20 years
49
+ subject.reap_expired!
50
+ doc = subject.fetch id
51
+ expect(doc.body).to eq 'foobar'
52
+ end
53
+
54
+ it 'should remove (only) expired documents from the cache' do
55
+ Timecop.freeze
56
+ id1 = subject.store 'foobar', 'text/plain', 30
57
+ id2 = subject.store 'bazqux', 'text/plain', 90
58
+ Timecop.travel Time.now + 60
59
+ subject.reap_expired!
60
+
61
+ expect { subject.fetch(id1) }.to raise_error Virginia::DocumentCache::NotFound
62
+ expect(subject.fetch(id2).body).to eq 'bazqux'
63
+ end
64
+
65
+ context 'auto-creation on #fetch' do
66
+ let(:doc_id) { 'boo' }
67
+ let(:body) { 'abcd' }
68
+ let(:ctype) { 'text/plain' }
69
+ let(:lifetime) { 93 }
70
+
71
+ before :each do
72
+ Timecop.freeze
73
+ expect { subject.fetch(doc_id) }.to raise_error Virginia::DocumentCache::NotFound
74
+ end
75
+
76
+ after :each do
77
+ doc = subject.fetch doc_id
78
+ expect(doc.body).to eq body
79
+ expect(doc.content_type).to eq ctype
80
+ expect(doc.expires_at).to eq Time.now + lifetime
81
+ end
82
+
83
+ it 'should allow me to supply a block to #store and create a document if one is not already cached' do
84
+ subject.fetch(doc_id) do
85
+ [body, 'text/plain', lifetime]
86
+ end
87
+ end
88
+
89
+ it 'should allow me to supply a block to #store that returns a document and cache it, if one is not already cached' do
90
+ subject.fetch(doc_id) do
91
+ Virginia::DocumentCache::Document.new doc_id, body, ctype, lifetime
92
+ end
93
+ end
94
+ end
95
+
96
+ context 'registering document content at an id' do
97
+ let(:doc_id) { 'abcd1234' }
98
+ let(:body) { 'once upon a time there was a muppet' }
99
+ let(:ctype) { 'application/x-powa' }
100
+ let(:lifetime) { 193 }
101
+
102
+ before :each do
103
+ expect { subject.fetch(doc_id) }.to raise_error Virginia::DocumentCache::NotFound
104
+ Timecop.freeze
105
+ end
106
+
107
+ context 'auto-creating documents' do
108
+ after :each do
109
+ doc = subject.fetch doc_id
110
+ expect(doc.body).to eq body
111
+ expect(doc.content_type).to eq ctype
112
+ expect(doc.expires_at).to eq Time.now + lifetime
113
+ end
114
+
115
+ it 'should auto-populate the document if not found in the cache' do
116
+ subject.register(doc_id, ctype, lifetime) do
117
+ body
118
+ end
119
+ end
120
+
121
+ it 'should not invoke the block if the document is already in the cache' do
122
+ subject.store body, ctype, lifetime, doc_id
123
+ subject.register(doc_id, ctype, lifetime) do
124
+ raise "Should not get here!"
125
+ end
126
+ end
127
+ end
128
+
129
+ it 'should allow removing a document creator' do
130
+ Timecop.freeze
131
+ subject.register(doc_id, ctype, lifetime) do
132
+ body
133
+ end
134
+ doc = subject.fetch(doc_id)
135
+
136
+ # Move past expiration and clean up the cached document
137
+ Timecop.travel Time.now + (lifetime + 1)
138
+ subject.reap_expired!
139
+
140
+ subject.unregister(doc_id)
141
+ expect { subject.fetch(doc_id) }.to raise_error Virginia::DocumentCache::NotFound
142
+ end
143
+ end
144
+ end
@@ -18,7 +18,7 @@ describe Virginia::Service do
18
18
  end
19
19
 
20
20
  it "should instantiate the handler" do
21
- rack_logger = mock 'Rack::CommonLogger'
21
+ rack_logger = double 'Rack::CommonLogger'
22
22
  ::Rack::CommonLogger.should_receive(:new).once.with(TestApp, Adhearsion.logger).and_return rack_logger
23
23
  ::Reel::Rack::Server.should_receive(:supervise_as).once.with(:reel_rack_server, rack_logger, options)
24
24
  Virginia::Service.start
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
17
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
18
  s.require_paths = ["lib"]
19
19
 
20
- s.add_runtime_dependency %q<adhearsion>, ["~> 2.4"]
20
+ s.add_runtime_dependency %q<adhearsion>, ["~> 2.6"]
21
21
  s.add_runtime_dependency %q<activesupport>, [">= 3.0"]
22
22
  s.add_runtime_dependency %q<reel>, ["~> 0.5.0"]
23
23
  s.add_runtime_dependency %q<reel-rack>
@@ -25,5 +25,6 @@ Gem::Specification.new do |s|
25
25
  s.add_development_dependency %q<bundler>, ["~> 1.0"]
26
26
  s.add_development_dependency %q<rspec>, ["~> 2.5"]
27
27
  s.add_development_dependency %q<rake>, [">= 0"]
28
+ s.add_development_dependency %q<timecop>
28
29
  s.add_development_dependency %q<guard-rspec>
29
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: virginia
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Pradovera
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-27 00:00:00.000000000 Z
11
+ date: 2015-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: adhearsion
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '2.4'
19
+ version: '2.6'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '2.4'
26
+ version: '2.6'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: timecop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: guard-rspec
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -131,6 +145,7 @@ extensions: []
131
145
  extra_rdoc_files: []
132
146
  files:
133
147
  - ".gitignore"
148
+ - ".rspec"
134
149
  - ".travis.yml"
135
150
  - CHANGELOG.md
136
151
  - Gemfile
@@ -140,11 +155,14 @@ files:
140
155
  - Rakefile
141
156
  - lib/virginia.rb
142
157
  - lib/virginia/document_cache.rb
158
+ - lib/virginia/document_cache/document.rb
143
159
  - lib/virginia/plugin.rb
144
160
  - lib/virginia/service.rb
145
161
  - lib/virginia/version.rb
146
162
  - spec/fixtures/config.ru
147
163
  - spec/spec_helper.rb
164
+ - spec/virginia/document_cache/document_spec.rb
165
+ - spec/virginia/document_cache_spec.rb
148
166
  - spec/virginia/service_spec.rb
149
167
  - tmp/dsl.rb
150
168
  - tmp/example.rb
@@ -168,11 +186,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
168
186
  version: '0'
169
187
  requirements: []
170
188
  rubyforge_project: virginia
171
- rubygems_version: 2.4.6
189
+ rubygems_version: 2.4.5
172
190
  signing_key:
173
191
  specification_version: 4
174
192
  summary: A Reel interface to Adhearsion
175
193
  test_files:
176
194
  - spec/fixtures/config.ru
177
195
  - spec/spec_helper.rb
196
+ - spec/virginia/document_cache/document_spec.rb
197
+ - spec/virginia/document_cache_spec.rb
178
198
  - spec/virginia/service_spec.rb