#:enddoc: module RailsConnector class Revision < CmsBaseModel CACHE_PREFIX = 'revision' self.key_prefix = "rev" property :generation property :base_revision_id property :title property :content_cache_id property :editable # Selects the revision with the given id as current revision def self.current=(revision_or_proc) @current = revision_or_proc end def self.current if @current.respond_to? :call @current = @current.call else @current || default end end def self.find_by_label(label_name) workspace = Workspace.find(label_name) find(workspace.revision_id) end def self.default find_by_label("published") end def self.begin_caching @editable_cache = Configuration.cache_editable_workspaces ? persistent_cache : Cache.new @read_only_cache = persistent_cache end def self.end_caching @editable_cache = @read_only_cache = nil end def self.caching? @read_only_cache && @editable_cache end def self.cache_for(revision) revision.editable ? @editable_cache : @read_only_cache end def find_obj_data_by(index, keys) if self.class.caching? find_obj_data_from_cache_or_database_by(index, keys) else find_obj_data_from_database_by(index, keys) end end def chain @chain ||= Chain.build_for(self, content_cache) end def invalidate_chain @chain = nil end # returns the base revision or nil for an initial revision def base_revision @base_revision ||= base_revision_id ? Revision.find(base_revision_id) : nil end # returns the content cache to be used with this revision or nil if not available def content_cache if content_cache_id if @content_cache && content_cache_id == @content_cache.id @content_cache else @content_cache = ContentCache.find_by_id(content_cache_id) end end end def inspect "<#{self.class} id=\"#{id}\" title=\"#{title}\">" end def obj_classes rtc['obj_classes'] end def attributes rtc['attributes'] end private def self.persistent_cache Cache.new(:fallback_backend => Rails.cache, :cache_prefix => CACHE_PREFIX) end def find_obj_data_from_database_by(index, keys) return [] if keys.blank? instrumenter = ActiveSupport::Notifications.instrumenter instrumenter.instrument( "cms_load.rails_connector", :name => "Obj Load", :index => index, :keys => keys ) do keys.map do |key| results = chain.query(index, key) results.values.map { |row| extract_obj_data(row) } end end end def find_obj_data_from_cache_or_database_by(index, keys) keys_from_database = [] # load results from cache results_from_cache = keys.map do |key| cache.read(cache_key_for(index, key)).tap do |objs| keys_from_database << key unless objs end end # load cache misses from database and store them in cache results_from_database = find_obj_data_from_database_by(index, keys_from_database) keys_from_database.each_with_index do |key, key_number| store_obj_data_list_in_cache(index, key, results_from_database[key_number]) end # combine the results results_from_cache.map do |objs_from_cache| objs_from_cache || results_from_database.shift end end UNIQUE_INDICES = [:id, :path, :permalink].freeze OBJ_PROPERTY_VALUE_TO_RANGE_VALUE_CONVERSIONS = { :path => PathConversion.method(:path_from_list) }.freeze def store_obj_data_list_in_cache(index, key, obj_data_list) obj_data_list.each do |obj_data| values = obj_data["values"] UNIQUE_INDICES.each do |unique_index| index_value = values["_#{unique_index}"] if (converter = OBJ_PROPERTY_VALUE_TO_RANGE_VALUE_CONVERSIONS[unique_index]) index_value = converter.call(index_value) end store_item_in_cache(unique_index, index_value, [obj_data]) end end unless UNIQUE_INDICES.include?(index) store_item_in_cache(index, key, obj_data_list) end end def store_item_in_cache(index, key, item) cache.write(cache_key_for(index, key), item) if self.class.caching? end def cache_key_for(index, key) "#{id}/obj/#{index}/#{key}" end def extract_obj_data(data) { "values" => data["values"].merge( "_id" => data["obj_id"], "_obj_type" => data["obj_type"], "_obj_class" => data["obj_class_name"] ), "rtc_ref" => data["rtc_ref"], "ext_ref" => data["ext_ref"], } end def rtc @rtc ||= DictStorage.get(read_attribute("rtc_ref")) end def cache self.class.cache_for(self) end end end