SpontaneousCMS/spontaneous

View on GitHub
lib/spontaneous/rack/page_controller.rb

Summary

Maintainability
B
4 hrs
Test Coverage
# encoding: UTF-8

require 'sinatra/base'

module Spontaneous::Rack
  class PageController < Sinatra::Base
    class << self
      # We wrap Sinatra's route methods in order to do two things:
      #   1. To provide a path of '*' when none is given and
      #   2. To register the presence of a handler for each method in order to
      #      correctly respond to the #dynamic? test
      def get(*args, &bk)     __dynamic!(:get,     super(*__route_args(args), &bk)) end
      def put(*args, &bk)     __dynamic!(:put,     super(*__route_args(args), &bk)) end
      def post(*args, &bk)    __dynamic!(:post,    super(*__route_args(args), &bk)) end
      def delete(*args, &bk)  __dynamic!(:delete,  super(*__route_args(args), &bk)) end
      def head(*args, &bk)    __dynamic!(:head,    super(*__route_args(args), &bk)) end
      def options(*args, &bk) __dynamic!(:options, super(*__route_args(args), &bk)) end
      def patch(*args, &bk)   __dynamic!(:patch,   super(*__route_args(args), &bk)) end
      def link(*args, &bk)    __dynamic!(:link,    super(*__route_args(args), &bk)) end
      def unlink(*args, &bk)  __dynamic!(:unlink,  super(*__route_args(args), &bk)) end

      def dynamic?(method = :get)
        __dynamic[method]
      end


      private

      def __route_args(args)
        opts = args.extract_options!
        path = (String === args.first) ? args.first : S::Constants::SLASH
        [path, opts]
      end

      def __dynamic!(method, action)
        __dynamic[method] = true
      end

      def __dynamic
        @__dynamic ||= {}
      end
    end

    attr_reader :content, :site

    def initialize(site, content, output)
      @site, @content, @output, @locals = site, content, output, {}
      @page = content.page
      super(nil)
    end

    # render [instance (Content), output (Symbol), status (Fixnum), locals (Hash)]
    # render [singleton name (Symbol), status (Fixnum), locals (Hash)] => [instance, output, status, locals]
    # render [singleton name (Symbol)]       => [singleton name, output, 200, {}]
    # render [instance (Content)] => [instance, output, 200, {}]
    # render [locals (Hash)] => [page, output, 200, locals]
    # render [status (Fixnum)] => [page, output, status, {}]
    # render(:home, :xml, 200, {logged_in: true}) # => :home => singleton name, :xml => output
    # render(:home, 200, {logged_in: true}) # => :home => singleton name
    # render(:home, {logged_in: true}) # => :home => singleton name
    # render(:xml, 200, {logged_in: true}) # NOT ALLOWED: if you want to specify the output then you must also specify the page
    # render(403, {logged_in: false})
    #
    # Every controller *must* include a call to render
    def render(*args)
      @locals = args.extract_options!
      show(*args)
      render_body
    end

    # Show provides a way to configure future calls to render without the overhead of
    # calling #render itself. Good if you want to set up a default state & then override
    # it later.
    # Only #render accepts a locals hash that is passed into the render call
    def show(*args)
      page, output, status = self.page, self.output, self.status
      case args.length
      when 3
        page, output, status = args
      when 2
        page, last = args
        case last
        when Fixnum
          status = last
        else
          output = last
        end
      when 1
        case (arg = args.first)
        when Fixnum
          status = arg
        else
          page = arg
        end
      end
      page(page)
      status(status)
      output(output)
    end


    REDIRECTS = {
      :permanent => 301,
      :temporary => 302
    }

    def redirect(location, redirect_code=:temporary)
      if String === location
        destination = @site[location]
        location = destination.path if destination and destination.respond_to?(:path)
      else
        location = location.path if location.respond_to?(:path)
      end
      redirect_code = REDIRECTS[redirect_code] if Symbol === redirect_code
      redirect_code ||= REDIRECTS[:temporary]
      # let Sinatra's helper method set up the proper Location headers for us
      catch(:halt) { super(location) }
      status(redirect_code)
      # then re-throw the :halt
      halt
    end

    def output(*args)
      return @output if args.empty?
      @output = args.first
    end

    def not_found
      super(not_found_body)
    end

    def page(page = nil)
      return @page if page.nil?
      @page = fetch_page(page)
    end

    private

    def renderer(output)
      env[Spontaneous::Rack::RENDERER].call(output)
    end

    def fetch_page(locator)
      case locator
      when Class # (singleton) type classes
        locator.first
      when Symbol, String
        fetch_singleton(locator) || fetch_path(locator)
      else # probably an instance
        locator
      end
    end

    def fetch_path(path)
      @site[path]
    end

    def fetch_singleton(page)
      return @site.send(page) if @site.singleton?(page)
      nil
    end

    def render_body
      body(render_page)
    end

    def render_page
      not_found if     page.nil?
      not_found unless page.provides_output?(self.output)
      output = page.output(self.output)
      render_output(output)
    end

    def render_output(output)
      if output.public?
        content_type(output.mime_type)
        do_render(output, @locals)
      else
        not_found
      end
    end

    def do_render(output, locals)
      locals = locals.merge({
        params: params, # use sinatras indifferent params
        request: request,
        session: request.session,
        env: request.env
      })
      output.render_using(renderer(output), locals)
    end

    def not_found_body
      "not found"
    end
  end
end