virginia 0.4.0 → 0.5.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 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