RadiusNetworks / doc_repo

MIT License
0 stars 0 forks source link

Re-write/Design refactor #8

Closed cupakromer closed 7 years ago

cupakromer commented 7 years ago

Resolve #6

This is a major version change. Consult the README.md for the full list of breaking changes.

Configuration

With this new version the following minimum configuration is still required:

DocRepo.configure do |c|
  c.org = "YourOrg"
  c.repo = "your_repo"
end

This expands the prior configuration adding the ability to control more of the response formats, where within the repo the docs are located, and add caching:

DocRepo.configure do |c|
  # Repo settings
  c.org = "YourOrg"
  c.repo = "your_repo"
  c.branch = "api-v2"
  c.doc_root = "/api-docs/rest"

  # Content settings
  c.doc_formats = %w[
    .md
    .mark
    .txt
  ]
  c.fallback_ext = ".mark"

  # Cache settings
  c.cache_store = Rails.cache
  c.cache_options = {
    namespace: :docs,
    expires_in: 36.hours,
  }
end

Basic Usage

The main usage is still through the DocRepo module and follows a block style. Things have been renamed in an attempt to improve the API intent and not hide as much of the underlying abstraction. The following is an example of a Rails controller using this new API:

class DocsController < ApplicationController
  def show
    DocRepo.request(params[:slug]) do |on|
      on.complete do |doc|
        @doc = doc
      end

      on.redirect do |target|
        redirect_to target.location, status: target.code
      end
    end
  end
end

At a minimum complete and redirect handlers should be specified. Additionally, specific handlers for missing documents (via not_found) and general remote origin related errors (via error) may be specified:

class DocsController < ApplicationController
  def show
    DocRepo.request(params[:slug]) do |on|
      on.complete do |doc|
        @doc = doc
      end

      on.redirect do |target|
        redirect_to target.location, status: target.code
      end

      on.not_found do |error|
        logger.warn "Not Found (URI=#{error.uri})"
        render file: "public/404.html", status: :not_found, layout: false
      end

      on.error do |error|
        logger.error "#{error} (URI=#{error.uri})"
        Bugsnag.notify error
        @error = error
        render :error, status: error.code
      end
    end
  end
end

When the error handler is not specified the default behavior will be to raise the error which would have been provided to the block. Similarly, when the not_found handler is not specified it will fallback to the specified error or raising when that is not set.

Caching and Conditional GET Support

By default a null cache is configured. This will result in all requests being sent to the remote origin server. Custom cache stores can configured through cache_store. In order to work the custom cache must implement the following APIs (existing Rails cache stores implement these):

Any custom cache will be used as an internal HTTP cache. This HTTP cache will prevent remote origin requests when possible. Any configured cache_options are provided directly to the cache_store for all fetch and write calls.

To reduce the number of requests to the remote origin server HTTP responses will be served from cache for as long as the cache is valid per the cache store. Additionally, this supports a basic understanding of HTTP cache through the Expires(RFC 7234) header. When a local HTTP cache has expired, but is still valid in cache_store, a conditional GET request will be made to the origin server. Any ETag(RFC 7232) or Last-Modified(RFC 7232) headers originally provided by the origin server will be sent in the request through If-None-Match(RFC 7232) and If-Modified-Since(RFC 7232) headers respectively.

Based on the response either the existing cache will be refreshed (i.e. in response to a 304 Not Modified) or replaced (i.e. in response to a 200 OK). This will cause the local HTTP cache to be re-written to the cache_store.

Rails Views, Cache, and Conditional GET Support

By default all DocRepo::Doc instances will generate HTML when provided to render for the following types :html, :plain, and :body:

DocRepo.request(params[:slug]) do |on|
  on.complete do |doc|
    # These two lines are equivalent
    render html: doc.to_html.html_safe
    render html: doc

    # As are these
    render plain: doc.to_html.html_safe
    render plain: doc
  end
end

For those documents which are written in markdown, if you wish to provide a way to display the raw markdown you will need to explicitly provide it through DocRepo::Doc#content:

DocRepo.request(params[:slug]) do |on|
  on.complete do |doc|
    respond_to do |format|
      format.html { render html: doc }

      format.text { render plain: doc.content }
    end
  end
end

Inside of a view you will need to call to_html, content or to_text as appropriate:

<%== doc.to_html %>
<%= doc.to_html.html_safe %>

The above mentioned caching behavior does not hook into the Rails view cache nor request/response conditional GET interfaces. However, DocRepo::Doc instances provided to the complete handler do implement the necessary interfaces.

You can explicitly define how to handle conditional GET through stale? or fresh_when:

DocRepo.request(params[:slug]) do |on|
  on.complete do |doc|
    @doc = doc
    fresh_when strong_etag: doc.cache_key_with_version, last_modified: doc.last_modified
  end
end

Alternatively, you can provide the document instance. When Rails is available the necessary interfaces are defined so that this just works:

DocRepo.request(params[:slug]) do |on|
  on.complete do |doc|
    fresh_when @doc = doc
  end
end

This also applies to view caches as well:

<% cache @doc do %>
  <%== @doc.to_html %>
<% end %>

Rails 5.1 and Earlier Cache Keys

The gem will attempt to check the Rails version when it is loaded and the Rails module is defined. When it detects a version prior to 5.2 it will load a patch which retains the legacy behavior of DocRepo::Doc#cache_key containing version information. On theses versions of Rails DocRepo::Doc#cache_key_with_version will simply be an alias for cache_key.

Rails 5.2 Recyclable View Caches

Support for this feature is built-in. The default implementation for DocRepo::Doc#cache_key does not include the version. Additionally, DocRepo::Doc#cache_key_with_version is already available to provide a versioned implementation. This means Rails view caches can be recycled while conditional GET calls through fresh_when and stale? continue to behave as expected.

While we do not suggest it, if you wish to explicitly retain the legacy cache_key behavior then you will need to load it through an initializer:

# config/initializers/doc_repo.rb
require 'doc_repo/rails/legacy_versioned_cache'