lib/cms/route_extensions.rb
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