summaryrefslogtreecommitdiff
path: root/vendor/gems/couchrest_session_store/lib/couchrest/model/rotation.rb
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gems/couchrest_session_store/lib/couchrest/model/rotation.rb')
-rw-r--r--vendor/gems/couchrest_session_store/lib/couchrest/model/rotation.rb263
1 files changed, 0 insertions, 263 deletions
diff --git a/vendor/gems/couchrest_session_store/lib/couchrest/model/rotation.rb b/vendor/gems/couchrest_session_store/lib/couchrest/model/rotation.rb
deleted file mode 100644
index 9e1a5c3..0000000
--- a/vendor/gems/couchrest_session_store/lib/couchrest/model/rotation.rb
+++ /dev/null
@@ -1,263 +0,0 @@
-module CouchRest
- module Model
- module Rotation
- extend ActiveSupport::Concern
- include CouchRest::Model::DatabaseMethod
-
- included do
- use_database_method :rotated_database_name
- end
-
- def create(*args)
- super(*args)
- rescue CouchRest::NotFound => exc
- raise storage_missing(exc)
- end
-
- def update(*args)
- super(*args)
- rescue CouchRest::NotFound => exc
- raise storage_missing(exc)
- end
-
- def destroy(*args)
- super(*args)
- rescue CouchRest::NotFound => exc
- raise storage_missing(exc)
- end
-
- private
-
- # returns a special 'storage missing' exception when the db has
- # not been created. very useful, since this happens a lot and a
- # generic 404 is not that helpful.
- def storage_missing(exc)
- if exc.http_body =~ /no_db_file/
- CouchRest::StorageMissing.new(exc.response, database)
- else
- exc
- end
- end
-
- public
-
- module ClassMethods
- #
- # Set up database rotation.
- #
- # base_name -- the name of the db before the rotation number is
- # appended.
- #
- # options -- one of:
- #
- # * :every -- frequency of rotation
- # * :expiration_field - what field to use to determine if a
- # document is expired.
- # * :timestamp_field - alternately, what field to use for the
- # document timestamp.
- # * :timeout -- used to expire documents with only a timestamp
- # field (in minutes)
- #
- def rotate_database(base_name, options={})
- @rotation_base_name = base_name
- @rotation_every = (options.delete(:every) || 30.days).to_i
- @expiration_field = options.delete(:expiration_field)
- @timestamp_field = options.delete(:timestamp_field)
- @timeout = options.delete(:timeout)
- if options.any?
- raise ArgumentError.new('Could not understand options %s' % options.keys)
- end
- end
-
- #
- # Check to see if dbs should be rotated. The :window
- # argument specifies how far in advance we should
- # create the new database (default 1.day).
- #
- # This method relies on the assumption that it is called
- # at least once within each @rotation_every period.
- #
- def rotate_database_now(options={})
- window = options[:window] || 1.day
-
- now = Time.now.utc
- current_name = rotated_database_name(now)
- current_count = now.to_i/@rotation_every
-
- next_time = window.from_now.utc
- next_name = rotated_database_name(next_time)
- next_count = current_count+1
-
- prev_name = current_name.sub(/(\d+)$/) {|i| i.to_i-1}
- replication_started = false
- old_name = prev_name.sub(/(\d+)$/) {|i| i.to_i-1} # even older than prev_name
- trailing_edge_time = window.ago.utc
-
- if !database_exists?(current_name)
- # we should have created the current db earlier, but if somehow
- # it is missing we must make sure it exists.
- create_new_rotated_database(:from => prev_name, :to => current_name)
- replication_started = true
- end
-
- if next_time.to_i/@rotation_every >= next_count && !database_exists?(next_name)
- # time to create the next db in advance of actually needing it.
- create_new_rotated_database(:from => current_name, :to => next_name)
- end
-
- if trailing_edge_time.to_i/@rotation_every == current_count
- # delete old dbs, but only after window time has past since the last rotation
- if !replication_started && database_exists?(prev_name)
- # delete previous, but only if we didn't just start replicating from it
- self.server.database(db_name_with_prefix(prev_name)).delete!
- end
- if database_exists?(old_name)
- # there are some edge cases, when rotate_database_now is run
- # infrequently, that an older db might be left around.
- self.server.database(db_name_with_prefix(old_name)).delete!
- end
- end
- end
-
- def rotated_database_name(time=nil)
- unless @rotation_base_name && @rotation_every
- raise ArgumentError.new('missing @rotation_base_name or @rotation_every')
- end
- time ||= Time.now.utc
- units = time.to_i / @rotation_every.to_i
- "#{@rotation_base_name}_#{units}"
- end
-
- #
- # create a new empty database.
- #
- def create_database!(name=nil)
- db = if name
- self.server.database!(db_name_with_prefix(name))
- else
- self.database!
- end
- create_rotation_filter(db)
- if self.respond_to?(:design_doc)
- design_doc.sync!(db)
- # or maybe this?:
- #self.design_docs.each do |design|
- # design.migrate(to_db)
- #end
- end
- return db
- end
-
- protected
-
- #
- # Creates database named by options[:to]. Optionally, set up
- # continuous replication from the options[:from] db, if it exists. The
- # assumption is that the from db will be destroyed later, cleaning up
- # the replication once it is no longer needed.
- #
- # This method will also copy design documents if present in the from
- # db, in the CouchRest::Model, or in a database named after
- # @rotation_base_name.
- #
- def create_new_rotated_database(options={})
- from = options[:from]
- to = options[:to]
- to_db = self.create_database!(to)
- if database_exists?(@rotation_base_name)
- base_db = self.server.database(db_name_with_prefix(@rotation_base_name))
- copy_design_docs(base_db, to_db)
- end
- if from && from != to && database_exists?(from)
- from_db = self.server.database(db_name_with_prefix(from))
- replicate_old_to_new(from_db, to_db)
- end
- end
-
- def copy_design_docs(from, to)
- params = {:startkey => '_design/', :endkey => '_design0', :include_docs => true}
- from.documents(params) do |doc_hash|
- design = doc_hash['doc']
- begin
- to.get(design['_id'])
- rescue CouchRest::NotFound
- design.delete('_rev')
- to.save_doc(design)
- end
- end
- end
-
- def create_rotation_filter(db)
- name = 'rotation_filter'
- filter_string = if @expiration_field
- NOT_EXPIRED_FILTER % {:expires => @expiration_field}
- elsif @timestamp_field && @timeout
- NOT_TIMED_OUT_FILTER % {:timestamp => @timestamp_field, :timeout => (60 * @timeout)}
- else
- NOT_DELETED_FILTER
- end
- filters = {"not_expired" => filter_string}
- db.save_doc("_id" => "_design/#{name}", "filters" => filters)
- rescue CouchRest::Conflict
- end
-
- #
- # Replicates documents from_db to to_db, skipping documents that have
- # expired or been deleted.
- #
- # NOTE: It would be better if we could do this:
- #
- # from_db.replicate_to(to_db, true, false,
- # :filter => 'rotation_filter/not_expired')
- #
- # But replicate_to() does not support a filter argument, so we call
- # the private method replication() directly.
- #
- def replicate_old_to_new(from_db, to_db)
- create_rotation_filter(from_db)
- from_db.send(:replicate, to_db, true, :source => from_db.name, :filter => 'rotation_filter/not_expired')
- end
-
- #
- # Three different filters, depending on how the model is set up.
- #
- # NOT_EXPIRED_FILTER is used when there is a single field that
- # contains an absolute time for when the document has expired. The
- #
- # NOT_TIMED_OUT_FILTER is used when there is a field that records the
- # timestamp of the last time the document was used. The expiration in
- # this case is calculated from the timestamp plus @timeout.
- #
- # NOT_DELETED_FILTER is used when the other two cannot be.
- #
- NOT_EXPIRED_FILTER = "" +
-%[function(doc, req) {
- if (doc._deleted) {
- return false;
- } else if (typeof(doc.%{expires}) != "undefined") {
- return Date.now() < (new Date(doc.%{expires})).getTime();
- } else {
- return true;
- }
-}]
-
- NOT_TIMED_OUT_FILTER = "" +
-%[function(doc, req) {
- if (doc._deleted) {
- return false;
- } else if (typeof(doc.%{timestamp}) != "undefined") {
- return Date.now() < (new Date(doc.%{timestamp})).getTime() + %{timeout};
- } else {
- return true;
- }
-}]
-
- NOT_DELETED_FILTER = "" +
-%[function(doc, req) {
- return !doc._deleted;
-}]
-
- end
- end
- end
-end