artirix/browsercms

View on GitHub
lib/cms/route_extensions.rb

Summary

Maintainability
B
4 hrs
Test Coverage
module Cms::RouteExtensions
  DEFAULT_RESOURCE_ACTIONS = [:index, :show, :new, :create, :edit, :update, :destroy]

  DEFAULT_CONTENT_BLOCKS_OPTIONS = { bulk_update: true }

  # Adds all necessary routes to manage a new content type. Works very similar to the Rails _resources_ method, adding basic CRUD routes, as well as additional ones
  #   for CMS specific routes (like versioning)
  #
  # @param [Symbol] content_block_name - The plural name of a new Content Type. Should match the name of the model_class, like :dogs or :donation_statuses
  def content_blocks(content_block_name, options = {}, &block)
    options     = DEFAULT_CONTENT_BLOCKS_OPTIONS.merge options
    model_class = guess_model_class(content_block_name)

    # options to
    bulk_update = options.delete :bulk_update

    only = options.delete(:only) || DEFAULT_RESOURCE_ACTIONS.dup

    if model_class.try :readonly?
      only.delete :new
      only.delete :create
      only.delete :edit
      only.delete :update
      only.delete :destroy

      bulk_update = false
    end

    options[:only] = only unless only == DEFAULT_RESOURCE_ACTIONS

    # pass the options only if any present
    resources_args = [content_block_name]
    resources_args << options if options.present?

    resources(*resources_args) do
      if model_class.publishable? || model_class.versioned?
        member do
          put :publish if model_class.publishable?
          if model_class.versioned?
            get :versions
            get 'version/:version', to: "#{content_block_name}#version", as: 'version'
            put 'revert_to/:version', to: "#{content_block_name}#revert_to", as: 'revert'
          end
        end
      end

      if bulk_update
        collection do
          put :update, to: "#{content_block_name}#bulk_update"
        end
      end

      yield if block_given?
    end
  end

  # Adds the routes required for BrowserCMS to function to a routes.rb file. Should be the last route in the file, as
  # all following routes will be ignored.
  #
  # Usage:
  #   YourAppName::Application.routes.draw do
  #      match '/some/path/in/your/app' :to=>"controller#action''
  #      mount_browsercms
  #   end
  #
  def mount_browsercms

    mount Cms::Engine => "/cms", :as => "cms"

    add_page_routes_defined_in_database

    # Add User management features
    devise_for :cms_user,
               class_name:  Cms.user_class_name,
               path:        '',
               skip:        :password,
               path_names:  { sign_in: 'login' },
               controllers: { sessions: 'cms/sites/sessions' }

    devise_scope :cms_user do
      get '/forgot-password' => "cms/sites/passwords#new", :as => 'forgot_password'
      post '/forgot-password' => "cms/sites/passwords#create", as: 'cms_user_password'
      get '/passwords/:id/edit' => "cms/sites/passwords#edit", as: 'edit_password'
      put '/forgot-password' => "cms/sites/passwords#update", as: 'update_password'
    end

    # Handle 'stock' attachments
    get "/attachments/:id/:filename", :to => "cms/attachments#download"
    get "/", :to => "cms/content#show"

    # Only need :POST to support  portlets that are acting like controllers.
    # Ideally we could get rid of this need.
    match "*path", :to => "cms/content#show", via: [:get, :post]
  end

  # Preserving for backwards compatibility with bcms-3.3.x and earlier.
  # @deprecated
  alias :routes_for_browser_cms :mount_browsercms

  private

  def guess_model_class(content_block_name)
    content_name = content_block_name.to_s.classify
    prefix       = determine_model_prefix
    begin
      namespaced_model_name = "#{Cms::Module.current_namespace}::#{content_name}"
      model_class           = namespaced_model_name.constantize
    rescue NameError
      model_class = "#{prefix}#{content_name}".constantize
    end
    model_class
  end

  def determine_model_prefix
    if @scope && @scope[:module]
      "#{@scope[:module].camelize}::"
    else
      ""
    end
  end


  # Define additional routes (in the main_app) that addressable content types need.
  def add_routes_for_addressable_content_blocks
    classes = Cms::Concerns::Addressable.classes_that_require_custom_routes
    classes.each do |klass|
      base_route_name = klass.name.demodulize.underscore.gsub("/", "_")
      add_show_via_slug_route(base_route_name, klass)
      add_inline_content_route(base_route_name, klass)
    end
  end

  # I.e. /cms/forms/:id/inline
  def add_inline_content_route(base_route_name, klass)
    denamespaced_controller = klass.name.demodulize.pluralize.underscore
    module_name             = klass.name.deconstantize.underscore
    inline_route_name       = "#{base_route_name}_inline"
    unless route_exists?(inline_route_name)
      klass.content_type.engine_class.routes.prepend do
        if klass.content_type.main_app_model?
          namespace module_name do
            inline_route(denamespaced_controller, inline_route_name, klass)
          end
        else
          inline_route(denamespaced_controller, inline_route_name, klass)
        end
      end
    end
  end

  def inline_route(denamespaced_controller, inline_route_name, klass)
    begin
      get "#{klass.path}/:id/inline", to: "#{denamespaced_controller}#inline", as: inline_route_name
    rescue ArgumentError
      # Because when you prepend a route, you can't easily determine if it has already been defined.
      # This avoids the error when Cms::PageRoute.reload_routes is called.
      Rails.logger.debug "Skipping readding existing route (probably during a route reload): get \"#{klass.path}/:id/inline\", to: \"#{denamespaced_controller}#inline\", as: #{inline_route_name}"
    end
  end

  # I.e. /forms/:slug
  def add_show_via_slug_route(base_route_name, klass)
    slug_path             = "#{klass.path}/:slug"
    namespaced_controller = klass.name.underscore.pluralize
    slug_path_name        = "#{base_route_name}_slug"
    # Add route to main application (By doing this here, we ensure all ContentBlock constants have already been load)
    # Engines don't process their routes until after the main routes are created.
    unless route_exists?(slug_path_name)
      Rails.application.routes.prepend do
        get slug_path, to: "#{namespaced_controller}#show_via_slug", as: slug_path_name
      end
    end
  end

  # Determine if a named route already exists, since Rails 4 will object if a duplicate named route is defined now.
  # Otherwise in development, when routes are reloaded the CMS would throw errors.
  def route_exists?(route_name)
    Rails.application.routes.named_routes[route_name]
  end

  def add_page_routes_defined_in_database
    if Cms::PageRoute.can_be_loaded?
      Cms::PageRoute.order("#{Cms::PageRoute.table_name}.name").each do |r|
        match r.pattern, :to => r.to, :as => r.route_name, :_page_route_id => r.page_route_id, :via => r.via, :constraints => r.constraints
      end
    end
  end
end