File: app/models/radiant/config.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: Radiant#1
has properties
module method: config_definitions #4
module method: config_definitions= / 1 #8
  class: Config#13
inherits from
  Base ( ActiveRecord )
has properties
attribute: definition (1/2) [R] #72
class method: [] / 1 #77
class method: []= / 2 #88
class method: to_hash #95
class method: initialize_cache #99
class method: cache_file_exists? #106
class method: stale_cache? #110
class method: ensure_cache_file #115
class method: cache_path #120
class method: cache_file #124
class method: site_settings #128
class method: default_settings #132
class method: user_settings #136
class method: namespace / 3 #150
class method: define / 2 #184
class method: definitions #210
class method: definition_for / 1 #214
class method: clear_definitions! #218
method: value= / 1 #236
method: value #261
method: definition (2/E) #272
method: boolean? #278
method: checked? #284
method: selector? #291
method: selected_value #297
method: update_cache #301
method: validate #307
  class: ConfigError#74
inherits from
  RuntimeError ( Builtin-Module )

Code

   1  module Radiant
   2 
   3    class << self
   4      def config_definitions
   5        @config_definitions ||= {}
   6      end
   7 
   8      def config_definitions=(definitions)
   9        @config_definitions = definitions
  10      end
  11    end
  12 
  13    class Config < ActiveRecord::Base
  14      #
  15      # The Radiant.config model class is stored in the database (and cached) but emulates a hash 
  16      # with simple bracket methods that allow you to get and set values like so:
  17      #
  18      #   Radiant.config['setting.name'] = 'value'
  19      #   Radiant.config['setting.name'] #=> "value"
  20      #
  21      # Config entries can be used freely as general-purpose global variables unless a definition
  22      # has been given for that key, in which case restrictions and defaults may apply. The restrictions  
  23      # can take the form of validations, requirements, permissions or permitted options. They are 
  24      # declared by calling Radiant::Config#define:
  25      # 
  26      #   # setting must be either 'foo', 'bar' or 'blank'
  27      #   define('admin.name', :select_from => ['foo', 'bar'])
  28      #
  29      #   # setting is (and must be) chosen from the names of currently available layouts
  30      #   define('shop.layout', :select_from => lambda { Layout.all.map{|l| [l.name,l.id]} }, :alow_blank => false)
  31      #
  32      #   # setting cannot be changed at runtime
  33      #   define('setting.important', :default => "something", :allow_change => false)
  34      #
  35      # Which almost always happens in a block like this:
  36      #
  37      #   Radiant.config do |config|
  38      #     config.namespace('user', :allow_change => true) do |user|
  39      #       user.define 'allow_password_reset?', :default => true
  40      #     end
  41      #   end
  42      #
  43      # and usually in a config/radiant_config.rb file either in radiant itself, in the application directory
  44      # or in an extension. Radiant currently defines the following settings and makes them editable by 
  45      # admin users on the site configuration page:
  46      #
  47      # admin.title               :: the title of the admin system
  48      # admin.subtitle            :: the subtitle of the admin system
  49      # defaults.page.parts       :: a comma separated list of default page parts
  50      # defaults.page.status      :: a string representation of the default page status
  51      # defaults.page.filter      :: the default filter to use on new page parts
  52      # defaults.page.fields      :: a comma separated list of the default page fields
  53      # defaults.snippet.filter   :: the default filter to use on new snippets
  54      # dev.host                  :: the hostname where draft pages are viewable
  55      # local.timezone            :: the timezone name (`rake -D time` for full list)
  56      #                              used to correct displayed times
  57      # page.edit.published_date? :: when true, shows the datetime selector
  58      #                              for published date on the page edit screen
  59      #
  60      # Helper methods are defined in ConfigurationHelper that will display config entry values
  61      # or edit fields:
  62      # 
  63      #   # to display label and value, where label comes from looking up the config key in the active locale
  64      #   show_setting('admin.name') 
  65      #
  66      #   # to display an appropriate checkbox, text field or select box with label as above:
  67      #   edit_setting('admin.name)
  68      #
  69      
  70      set_table_name "config"
  71      after_save :update_cache
  72      attr_reader :definition
  73      
  74      class ConfigError < RuntimeError; end
  75      
  76      class << self
  77        def [](key)
  78          if table_exists?
  79            unless Radiant::Config.cache_file_exists?
  80              Radiant::Config.ensure_cache_file
  81              Radiant::Config.initialize_cache
  82            end
  83            Radiant::Config.initialize_cache if Radiant::Config.stale_cache?
  84            Rails.cache.read('Radiant::Config')[key]
  85          end
  86        end
  87 
  88        def []=(key, value)
  89          if table_exists?
  90            setting = find_or_initialize_by_key(key)
  91            setting.value = value
  92          end
  93        end
  94        
  95        def to_hash
  96          Hash[ *find(:all).map { |pair| [pair.key, pair.value] }.flatten ]
  97        end
  98        
  99        def initialize_cache
 100          Radiant::Config.ensure_cache_file
 101          Rails.cache.write('Radiant::Config',Radiant::Config.to_hash)
 102          Rails.cache.write('Radiant.cache_mtime', File.mtime(cache_file))
 103          Rails.cache.silence!
 104        end
 105        
 106        def cache_file_exists?
 107          File.file?(cache_file)
 108        end
 109        
 110        def stale_cache?
 111          return true unless Radiant::Config.cache_file_exists?
 112          Rails.cache.read('Radiant.cache_mtime') != File.mtime(cache_file)
 113        end
 114        
 115        def ensure_cache_file
 116          FileUtils.mkpath(cache_path)
 117          FileUtils.touch(cache_file)
 118        end
 119        
 120        def cache_path
 121          "#{Rails.root}/tmp"
 122        end
 123        
 124        def cache_file
 125          cache_file = File.join(cache_path,'radiant_config_cache.txt')
 126        end
 127        
 128        def site_settings
 129          @site_settings ||= %w{ site.title site.host dev.host local.timezone }
 130        end
 131        
 132        def default_settings
 133          @default_settings ||= %w{ defaults.locale defaults.page.filter defaults.page.parts defaults.page.fields defaults.page.status defaults.snippet.filter }
 134        end
 135        
 136        def user_settings
 137          @user_settings ||= ['user.allow_password_reset?']
 138        end
 139        
 140        # A convenient drying method for specifying a prefix and options common to several settings.
 141        # 
 142        #   Radiant.config do |config| 
 143        #     config.namespace('secret', :allow_display => false) do |secret|
 144        #       secret.define('identity', :default => 'batman')      # defines 'secret.identity'
 145        #       secret.define('lair', :default => 'batcave')         # defines 'secret.lair'
 146        #       secret.define('longing', :default => 'vindication')  # defines 'secret.longing'
 147        #     end
 148        #   end
 149        #
 150        def namespace(prefix, options = {}, &block)
 151          prefix = [options[:prefix], prefix].join('.') if options[:prefix]
 152          with_options(options.merge(:prefix => prefix), &block)
 153        end
 154        
 155        # Declares a setting definition that will constrain and support the use of a particular config entry.
 156        #
 157        #   define('setting.key', options)
 158        #
 159        # Can take several options:
 160        # * :default is the value that will be placed in the database if none has been set already
 161        # * :type can be :string, :boolean or :integer. Note that all settings whose key ends in ? are considered boolean.
 162        # * :select_from should be a list or hash suitable for passing to options_for_select, or a block that will return such a list at runtime
 163        # * :validate_with should be a block that will receive a value and return true or false. Validations are also implied by type or select_from.
 164        # * :allow_blank should be false if the config item must not be blank or nil
 165        # * :allow_change should be false if the config item can only be set, not changed. Add a default to specify an unchanging config entry.
 166        # * :allow_display should be false if the config item should not be showable in radius tags
 167        #
 168        # From the main radiant config/initializers/radiant_config.rb:
 169        #
 170        #   Radiant.config do |config|
 171        #     config.define 'defaults.locale', :select_from => lambda { Radiant::AvailableLocales.locales }, :allow_blank => true
 172        #     config.define 'defaults.page.parts', :default => "Body,Extended"
 173        #     ...
 174        #   end
 175        #
 176        # It's also possible to reuse a definition by passing it to define:
 177        #
 178        #   choose_layout = Radiant::Config::Definition.new(:select_from => lambda {Layout.all.map{|l| [l.name, l.d]}})
 179        #   define "my.layout", choose_layout
 180        #   define "your.layout", choose_layout
 181        #
 182        # but at the moment that's only done in testing.
 183        #
 184        def define(key, options={})
 185          called_from = caller.grep(/\/initializers\//).first
 186          if options.is_a? Radiant::Config::Definition
 187            definition = options
 188          else
 189            key = [options[:prefix], key].join('.') if options[:prefix]
 190          end
 191 
 192          raise LoadError, %{
 193  Config definition error: '#{key}' is defined twice:
 194  1. #{called_from}
 195  2. #{definitions[key].definer}
 196          } unless definitions[key].nil? || definitions[key].empty?
 197 
 198          definition ||= Radiant::Config::Definition.new(options.merge(:definer => called_from))
 199          definitions[key] = definition
 200 
 201          if self[key].nil? && !definition.default.nil?
 202            begin
 203              self[key] = definition.default
 204            rescue ActiveRecord::RecordInvalid
 205              raise LoadError, "Default configuration invalid: value '#{definition.default}' is not allowed for '#{key}'"
 206            end
 207          end
 208        end
 209        
 210        def definitions
 211          Radiant.config_definitions
 212        end
 213        
 214        def definition_for(key)
 215          definitions[key] ||= Radiant::Config::Definition.new(:empty => true)
 216        end
 217        
 218        def clear_definitions!
 219          Radiant.config_definitions = {}
 220        end
 221        
 222      end
 223      
 224      # The usual way to use a config item:
 225      #
 226      #    Radiant.config['key'] = value
 227      #
 228      # is equivalent to this:
 229      #
 230      #   Radiant::Config.find_or_create_by_key('key').value = value
 231      #
 232      # Calling value= also applies any validations and restrictions that are found in the associated definition.
 233      # so this will raise a ConfigError if you try to change a protected config entry or a RecordInvalid if you 
 234      # set a value that is not among those permitted.
 235      #
 236      def value=(param)
 237        newvalue = param.to_s
 238        if newvalue != self[:value]
 239          raise ConfigError, "#{self.key} cannot be changed" unless settable? || self[:value].blank?
 240          if boolean?
 241            self[:value] = (newvalue == "1" || newvalue == "true") ? "true" : "false"
 242          else
 243            self[:value] = newvalue
 244          end
 245          self.save!
 246        end
 247        self[:value]
 248      end
 249 
 250      # Requesting a config item:
 251      #
 252      #    key = Radiant.config['key']
 253      #
 254      # is equivalent to this:
 255      #
 256      #   key = Radiant::Config.find_or_create_by_key('key').value
 257      #
 258      # If the config item is boolean the response will be true or false. For items with :type => :integer it will be an integer, 
 259      # for everything else a string.
 260      # 
 261      def value
 262        if boolean?
 263          checked?
 264        else
 265          self[:value]
 266        end
 267      end
 268 
 269      # Returns the definition associated with this config item. If none has been declared this will be an empty definition
 270      # that does not restrict use.
 271      #
 272      def definition
 273        @definition ||= self.class.definition_for(self.key)
 274      end
 275 
 276      # Returns true if the item key ends with '?' or the definition specifies :type => :boolean.
 277      #
 278      def boolean?
 279        definition.boolean? || self.key.ends_with?("?")
 280      end
 281      
 282      # Returns true if the item is boolean and true.
 283      #
 284      def checked?
 285        return nil if self[:value].nil?
 286        boolean? && self[:value] == "true"
 287      end
 288      
 289      # Returns true if the item defintion includes a :select_from parameter that limits the range of permissible options.
 290      #
 291      def selector?
 292        definition.selector?
 293      end
 294      
 295      # Returns a name corresponding to the current setting value, if the setting definition includes a select_from parameter.
 296      #
 297      def selected_value
 298        definition.selected(value)
 299      end
 300      
 301      def update_cache
 302        Radiant::Config.initialize_cache
 303      end
 304 
 305      delegate :default, :type, :allow_blank?, :hidden?, :visible?, :settable?, :selection, :notes, :units, :to => :definition
 306 
 307      def validate
 308        definition.validate(self)
 309      end
 310 
 311    end
 312  end