lib/active_scaffold/config/core.rb
module ActiveScaffold::Config
# to fix the ckeditor bridge problem inherit from full class name
class Core < ActiveScaffold::Config::Base
include ActiveScaffold::OrmChecks
# global level configuration
# --------------------------
# provides read/write access to the global Actions DataStructure
cattr_reader :actions, instance_accessor: false
def self.actions=(val)
@@actions = ActiveScaffold::DataStructures::Actions.new(*val)
end
self.actions = %i[create list search update delete show nested subform]
# configures where the ActiveScaffold plugin itself is located. there is no instance version of this.
cattr_accessor :plugin_directory
@@plugin_directory = File.expand_path(__FILE__).match(%{(^.*)/lib/active_scaffold/config/core.rb})[1]
# lets you specify a global ActiveScaffold frontend.
cattr_accessor :frontend, instance_accessor: false
@@frontend = :default
# lets you specify a global ActiveScaffold theme for your frontend.
cattr_accessor :theme, instance_accessor: false
@@theme = :default
# enable caching of action link urls
cattr_accessor :cache_action_link_urls, instance_accessor: false
@@cache_action_link_urls = true
# enable caching of association options
cattr_accessor :cache_association_options, instance_accessor: false
@@cache_association_options = true
# enable setting ETag and LastModified on responses and using fresh_when/stale? to respond with 304 and avoid rendering views
cattr_accessor :conditional_get_support, instance_accessor: false
@@conditional_get_support = false
# enable saving user settings in session (per_page, limit, page, sort, search params)
cattr_accessor :store_user_settings, instance_accessor: false
@@store_user_settings = true
# lets you disable the DHTML history
cattr_writer :dhtml_history, instance_accessor: false
def self.dhtml_history?
@@dhtml_history ? true : false
end
@@dhtml_history = true
# action links are used by actions to tie together. you can use them, too! this is a collection of ActiveScaffold::DataStructures::ActionLink objects.
cattr_reader :action_links, instance_reader: false
@@action_links = ActiveScaffold::DataStructures::ActionLinks.new
# access to the permissions configuration.
# configuration options include:
# * current_user_method - what method on the controller returns the current user. default: :current_user
# * default_permission - what the default permission is. default: true
def self.security
ActiveScaffold::ActiveRecordPermissions
end
# access to default column configuration.
def self.column
ActiveScaffold::DataStructures::Column
end
# columns that should be ignored for every model. these should be metadata columns like change dates, versions, etc.
# values in this array may be symbols or strings.
def self.ignore_columns
@@ignore_columns
end
def self.ignore_columns=(val)
@@ignore_columns = ActiveScaffold::DataStructures::Set.new(*val)
end
@@ignore_columns = ActiveScaffold::DataStructures::Set.new
# lets you specify whether add a create link for each sti child
cattr_accessor :sti_create_links, instance_accessor: false
@@sti_create_links = true
# prefix messages with current timestamp, set the format to display (you can use I18n keys) or true and :short will be used
cattr_accessor :timestamped_messages, instance_accessor: false
@@timestamped_messages = false
# a hash of string (or array of strings) and highlighter string to highlight words in messages. It will use highlight rails helper
cattr_accessor :highlight_messages, instance_accessor: false
@@highlight_messages = nil
# method names or procs to be called after all configure blocks
cattr_reader :after_config_callbacks, instance_accessor: false
@@after_config_callbacks = [:_configure_sti]
def self.freeze
super
security.freeze
column.freeze
end
# instance-level configuration
# ----------------------------
# provides read/write access to the local Actions DataStructure
attr_reader :actions
def actions=(args)
@actions = ActiveScaffold::DataStructures::Actions.new(*args)
end
# provides read/write access to the local Columns DataStructure
attr_reader :columns
def columns=(val)
@columns._inheritable = val.collect(&:to_sym)
# Add virtual columns
@columns.add(*val)
end
# lets you override the global ActiveScaffold frontend for a specific controller
attr_accessor :frontend
# lets you override the global ActiveScaffold theme for a specific controller
attr_accessor :theme
# enable caching of action link urls
attr_accessor :cache_action_link_urls
# enable caching of association options
attr_accessor :cache_association_options
# enable setting ETag and LastModified on responses and using fresh_when/stale? to respond with 304 and avoid rendering views
attr_accessor :conditional_get_support
# enable saving user settings in session (per_page, limit, page, sort, search params)
attr_accessor :store_user_settings
# lets you specify whether add a create link for each sti child for a specific controller
attr_accessor :sti_create_links
def add_sti_create_links?
sti_create_links && !sti_children.nil?
end
# action links are used by actions to tie together. they appear as links for each record, or general links for the ActiveScaffold.
attr_reader :action_links
# a generally-applicable name for this ActiveScaffold ... will be used for generating page/section headers
attr_writer :label
def label(options = {})
as_(@label, options) || model.model_name.human(options.merge(options[:count].to_i == 1 ? {} : {:default => model.name.pluralize}))
end
# STI children models, use an array of model names
attr_accessor :sti_children
# prefix messages with current timestamp, set the format to display (you can use I18n keys) or true and :short will be used
attr_accessor :timestamped_messages
# a hash of string (or array of strings) and highlighter string to highlight words in messages. It will use highlight rails helper
attr_accessor :highlight_messages
##
## internal usage only below this point
## ------------------------------------
def initialize(model_id)
# model_id is the only absolutely required configuration value. it is also not publicly accessible.
@model_id = model_id
setup_user_setting_key
# inherit the actions list directly from the global level
@actions = self.class.actions.clone
# create a new default columns datastructure, since it doesn't make sense before now
attribute_names = _columns.collect { |c| c.name.to_sym }.sort_by(&:to_s)
association_column_names = _reflect_on_all_associations.collect { |a| a.name.to_sym }
if defined?(ActiveMongoid) && model < ActiveMongoid::Associations
association_column_names.concat model.am_relations.keys.map(&:to_sym)
end
@columns = ActiveScaffold::DataStructures::Columns.new(model, attribute_names + association_column_names.sort_by(&:to_s))
# and then, let's remove some columns from the inheritable set.
content_columns = Set.new(_content_columns.map(&:name))
@columns.exclude(*self.class.ignore_columns)
@columns.exclude(*@columns.find_all { |c| c.column && content_columns.exclude?(c.column.name) }.collect(&:name))
@columns.exclude(*model.reflect_on_all_associations.collect { |a| a.foreign_type.to_sym if a.options[:polymorphic] }.compact)
# inherit the global frontend
@frontend = self.class.frontend
@theme = self.class.theme
@cache_action_link_urls = self.class.cache_action_link_urls
@cache_association_options = self.class.cache_association_options
@conditional_get_support = self.class.conditional_get_support
@store_user_settings = self.class.store_user_settings
@sti_create_links = self.class.sti_create_links
# inherit from the global set of action links
@action_links = self.class.action_links.clone
@timestamped_messages = self.class.timestamped_messages
@highlight_messages = self.class.highlight_messages
end
# To be called before freezing
def _cache_lazy_values
action_links.each(&:name_to_cache) if cache_action_link_urls
columns.select(&:sortable?).each(&:sort)
columns.select(&:searchable?).each(&:search_sql)
actions.each do |action_name|
action = send(action_name)
Array(action.class.columns_collections).each { |method| action.send(method) }
end
end
# To be called after your finished configuration
def _configure_sti
return if sti_children.nil?
column = model.inheritance_column
if sti_create_links
columns[column].form_ui ||= :hidden
else
columns[column].form_ui ||= :select
columns[column].options ||= {}
columns[column].options[:options] = sti_children.collect do |model_name|
[model_name.to_s.camelize.constantize.model_name.human, model_name.to_s.camelize]
end
end
end
def _setup_action(action)
define_singleton_method action do
self[action]
end
end
# configuration routing.
# we want to route calls named like an activated action to that action's global or local Config class.
# ---------------------------
def method_missing(name, *args)
self[name] || super
end
def respond_to_missing?(name, include_all = false)
self.class.config_class?(name) && @actions.include?(name.to_sym) || super
end
def [](action_name)
klass = self.class.config_class(action_name)
return unless klass
underscored_name = action_name.to_s.underscore.to_sym
unless @actions.include? underscored_name
raise "#{action_name.to_s.camelcase} is not enabled. Please enable it or remove any references in your configuration (e.g. config.#{underscored_name}.columns = [...])."
end
@action_configs ||= {}
@action_configs[underscored_name] ||= klass.new(self)
end
def []=(action_name, action_config)
@action_configs ||= {}
@action_configs[action_name] = action_config
end
private :[]=
def self.method_missing(name, *args)
config_class(name) || super
end
def self.config_class(name)
"ActiveScaffold::Config::#{name.to_s.camelcase}".constantize if config_class?(name)
end
def self.config_class?(name)
ActiveScaffold::Config.const_defined? name.to_s.camelcase
end
def self.respond_to_missing?(name, include_all = false)
config_class?(name) && @@actions.include?(name.to_s.underscore) || super
end
# some utility methods
# --------------------
attr_reader :model_id
def model
@model ||= @model_id.to_s.camelize.constantize
end
alias active_record_class model
def primary_key
mongoid? ? '_id' : model.primary_key
end
# warning - this won't work as a per-request dynamic attribute in rails 2.0. You'll need to interact with Controller#generic_view_paths
def inherited_view_paths
@inherited_view_paths ||= []
end
def build_action_columns(action, columns)
action_columns =
if columns.is_a?(ActiveScaffold::DataStructures::ActionColumns)
columns.dup
else
ActiveScaffold::DataStructures::ActionColumns.new(*columns)
end
action_columns.action = action.is_a?(Symbol) ? send(action) : action
action_columns
end
# must be a class method so the layout doesn't depend on a controller that uses active_scaffold
# note that this is unaffected by per-controller frontend configuration.
def self.asset_path(filename, frontend = self.frontend)
"active_scaffold/#{frontend}/#{filename}"
end
# must be a class method so the layout doesn't depend on a controller that uses active_scaffold
# note that this is unaffected by per-controller frontend configuration.
def self.javascripts(frontend = self.frontend)
javascript_dir = File.join(Rails.public_path, 'javascripts', asset_path('', frontend))
Dir.entries(javascript_dir).reject { |e| !e.match(/\.js$/) || (!dhtml_history? && e.match('dhtml_history')) }
end
def self.available_frontends
frontends_dir = Rails.root.join('vendor', 'plugins', ActiveScaffold::Config::Core.plugin_directory, 'frontends')
Dir.entries(frontends_dir).reject { |e| e.match(/^\./) } # Get rid of files that start with .
end
class UserSettings < UserSettings
include ActiveScaffold::Configurable
user_attr :cache_action_link_urls, :cache_association_options, :conditional_get_support,
:timestamped_messages, :highlight_messages
def method_missing(name, *args)
value = @conf.actions.include?(name) ? @conf.send(name) : super
value.is_a?(Base) ? action_user_settings(value) : value
end
def respond_to_missing?(name, include_all = false)
super # avoid rubocop warning
end
def action_user_settings(action_config)
if action_config.user.nil? && action_config.respond_to?(:new_user_settings)
action_config.new_user_settings @storage, @params
end
action_config.user || action_config
end
def columns
@columns ||= UserColumns.new(@conf.columns)
end
def action_links
@action_links ||= CowProxy.wrap(@conf.action_links)
end
def model
@conf.model # for performance, called many times, so we avoid method_missing
end
def actions
@conf.actions # for performance, called many times, so we avoid method_missing
end
end
class UserColumns
include Enumerable
def initialize(columns)
@global_columns = columns
@columns = {}
end
def [](name)
return nil unless @global_columns[name]
@columns[name.to_sym] ||= CowProxy.wrap @global_columns[name]
end
def each
return enum_for(:each) unless block_given?
@global_columns.each do |col|
yield self[col.name]
end
end
def method_missing(name, *args, &block)
if @global_columns.respond_to?(name, true)
@global_columns.send(name, *args, &block)
else
super
end
end
def respond_to_missing?(name, include_all = false)
@global_columns.respond_to?(name, include_all) || super
end
end
end
end