rike422/loose-leaf

View on GitHub
lib/loose_leaf/document_attribute.rb

Summary

Maintainability
A
0 mins
Test Coverage
module LooseLeaf
  class DocumentAttribute
    attr_reader :document, :attribute

    def initialize(document, attribute)
      @document = document
      @attribute = attribute

      @operations = []
    end

    # Based on https://github.com/Operational-Transformation/ot.js/blob/15d4e7/lib/server.js#L16
    def apply_operation(operation, client_version)
      Rails.logger.debug "Applying client version #{client_version}, server version: #{version}"

      unless client_version > version
        operation = transform_old_operation(operation, client_version)
      end

      store_operation(operation)

      self.value = new_text(operation)

      [version, operation]
    end

    def version
      operations.length
    end

    def value
      cached_value || document.attributes[attribute.to_s]
    end

    def value=(value)
      return unless document.persisted?

      Rails.cache.write(value_key, value)
    end

    def clear_cache
      Rails.cache.delete(value_key)
    end

    def clear_operations_cache
      Rails.cache.delete(operations_key)
    end

    private

    def value_key
      "loose_leaf-#{document.class}-#{document.id}-#{attribute}"
    end

    def cached_value
      return unless document.persisted?
      Rails.cache.read(value_key)
    end

    def operations_key
      "loose_leaf-#{document.class}-#{document.id}-#{attribute}-operations"
    end

    def operations
      Rails.cache.read(operations_key) || []
    end

    def store_operation(operation)
      Rails.cache.write(operations_key, operations + [operation])
    end

    def new_text(operation)
      operation.apply(value || '')
    end

    def transform_old_operation(operation, client_version)
      server_version = version
      concurrent_operations = operations.slice(
        client_version - 1,
        server_version - client_version + 1
      )

      concurrent_operations.each do |other_operation|
        operation = OT::TextOperation.transform(operation, other_operation).first
      end

      operation
    end
  end
end