File: app/models/page.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  class: Page#3
includes
  DeprecatedTags   
  StandardTags   
  Taggable ( Radiant )
  Annotatable   
inherits from
  Base ( ActiveRecord )
has properties
attribute: request [RW] #40
attribute: response [RW] #40
attribute: pagination_parameters [RW] #40
method: layout_with_inheritance #46
method: description #55
method: description= / 1 #59
method: cache? #63
method: child_path / 1 #67
method: headers #72
method: part / 1 #77
method: has_part? / 1 #85
method: has_or_inherits_part? / 1 #89
method: inherits_part? / 1 #93
method: field / 1 #97
method: published? #105
method: scheduled? #109
method: status #113
method: status= / 1 #117
method: path #121
method: process / 2 #130
method: response_code #141
method: render #145
method: render_part / 1 #153
method: render_snippet / 1 #162
method: find_by_path (1/2) / 3 #166
method: update_status #191
method: to_xml / 2 #202
method: default_child #206
method: allowed_children_lookup #210
method: set_allowed_children_cache #214
class method: root #220
class method: find_by_path (2/E) / 2 #224
class method: find_by_url / 1 #228
class method: date_column_names #233
class method: display_name / 1 #237
class method: display_name= / 1 #252
class method: load_subclasses #256
class method: new_with_defaults / 1 #273
class method: is_descendant_class_name? / 1 #282
class method: descendant_class / 1 #286
class method: missing? #295
class method: default_page_parts / 1 #301
class method: default_page_fields / 1 #308
method: valid_class_name #318
method: attributes_protected_by_default #324
method: update_virtual #328
method: clean_path / 1 #337
method: parent? #342
method: lazy_initialize_parser_and_context #346
method: parse / 1 #354
method: parse_object / 1 #358
  class: MissingRootPageError#5
inherits from
  StandardError ( Builtin-Module )
has properties
method: initialize / 1 #6

Class Hierarchy

Code

   1  require 'acts_as_tree'
   2 
   3  class Page < ActiveRecord::Base
   4 
   5    class MissingRootPageError < StandardError
   6      def initialize(message = 'Database missing root page'); super end
   7    end
   8 
   9    # Callbacks
  10    before_save :update_virtual, :update_status, :set_allowed_children_cache
  11 
  12    # Associations
  13    acts_as_tree :order => 'virtual DESC, title ASC'
  14    has_many :parts, :class_name => 'PagePart', :order => 'id', :dependent => :destroy
  15    accepts_nested_attributes_for :parts, :allow_destroy => true
  16    has_many :fields, :class_name => 'PageField', :order => 'id', :dependent => :destroy
  17    accepts_nested_attributes_for :fields, :allow_destroy => true
  18    belongs_to :layout
  19    belongs_to :created_by, :class_name => 'User'
  20    belongs_to :updated_by, :class_name => 'User'
  21 
  22    # Validations
  23    validates_presence_of :title, :slug, :breadcrumb, :status_id
  24 
  25    validates_length_of :title, :maximum => 255
  26    validates_length_of :slug, :maximum => 100
  27    validates_length_of :breadcrumb, :maximum => 160
  28 
  29    validates_format_of :slug, :with => %r{^([-_.A-Za-z0-9]*|/)$}
  30    validates_uniqueness_of :slug, :scope => :parent_id
  31 
  32    validate :valid_class_name
  33 
  34    include Radiant::Taggable
  35    include StandardTags
  36    include DeprecatedTags
  37    include Annotatable
  38 
  39    annotate :description
  40    attr_accessor :request, :response, :pagination_parameters
  41    class_inheritable_accessor :default_child
  42    self.default_child = self
  43 
  44    set_inheritance_column :class_name
  45 
  46    def layout_with_inheritance
  47      unless layout_without_inheritance
  48        parent.layout if parent?
  49      else
  50        layout_without_inheritance
  51      end
  52    end
  53    alias_method_chain :layout, :inheritance
  54 
  55    def description
  56      self["description"]
  57    end
  58 
  59    def description=(value)
  60      self["description"] = value
  61    end
  62 
  63    def cache?
  64      true
  65    end
  66 
  67    def child_path(child)
  68      clean_path(path + '/' + child.slug)
  69    end
  70    alias_method :child_url, :child_path
  71 
  72    def headers
  73      # Return a blank hash that child classes can override or merge
  74      { }
  75    end
  76 
  77    def part(name)
  78      if new_record? or parts.to_a.any?(&:new_record?)
  79        parts.to_a.find {|p| p.name == name.to_s }
  80      else
  81        parts.find_by_name name.to_s
  82      end
  83    end
  84 
  85    def has_part?(name)
  86      !part(name).nil?
  87    end
  88 
  89    def has_or_inherits_part?(name)
  90      has_part?(name) || inherits_part?(name)
  91    end
  92 
  93    def inherits_part?(name)
  94      !has_part?(name) && self.ancestors.any? { |page| page.has_part?(name) }
  95    end
  96 
  97    def field(name)
  98      if new_record? or fields.any?(&:new_record?)
  99        fields.detect { |f| f.name.downcase == name.to_s.downcase }
 100      else
 101        fields.find_by_name name.to_s
 102      end
 103    end
 104 
 105    def published?
 106      status == Status[:published]
 107    end
 108    
 109    def scheduled?
 110      status == Status[:scheduled]
 111    end
 112    
 113    def status
 114     Status.find(self.status_id)
 115    end
 116    
 117    def status=(value)
 118      self.status_id = value.id
 119    end
 120 
 121    def path
 122      if parent?
 123        parent.child_path(self)
 124      else
 125        clean_path(slug)
 126      end
 127    end
 128    alias_method :url, :path
 129 
 130    def process(request, response)
 131      @request, @response = request, response
 132      if layout
 133        content_type = layout.content_type.to_s.strip
 134        @response.headers['Content-Type'] = content_type unless content_type.empty?
 135      end
 136      headers.each { |k,v| @response.headers[k] = v }
 137      @response.body = render
 138      @response.status = response_code
 139    end
 140 
 141    def response_code
 142      200
 143    end
 144 
 145    def render
 146      if layout
 147        parse_object(layout)
 148      else
 149        render_part(:body)
 150      end
 151    end
 152 
 153    def render_part(part_name)
 154      part = part(part_name)
 155      if part
 156        parse_object(part)
 157      else
 158        ''
 159      end
 160    end
 161 
 162    def render_snippet(snippet)
 163      parse_object(snippet)
 164    end
 165 
 166    def find_by_path(path, live = true, clean = true)
 167      return nil if virtual?
 168      path = clean_path(path) if clean
 169      my_path = self.path
 170      if (my_path == path) && (not live or published?)
 171        self
 172      elsif (path =~ /^#{Regexp.quote(my_path)}([^\/]*)/)
 173        slug_child = children.find_by_slug($1)
 174        if slug_child
 175          found = slug_child.find_by_url(path, live, clean) # TODO: set to find_by_path after deprecation
 176          return found if found
 177        end
 178        children.each do |child|
 179          found = child.find_by_url(path, live, clean) # TODO: set to find_by_path after deprecation
 180          return found if found
 181        end
 182        file_not_found_types = ([FileNotFoundPage] + FileNotFoundPage.descendants)
 183        file_not_found_names = file_not_found_types.collect { |x| x.name }
 184        condition = (['class_name = ?'] * file_not_found_names.length).join(' or ')
 185        condition = "status_id = #{Status[:published].id} and (#{condition})" if live
 186        children.find(:first, :conditions => [condition] + file_not_found_names)
 187      end
 188    end
 189    alias_method :find_by_url, :find_by_path
 190 
 191    def update_status
 192      self.published_at = Time.zone.now if published? && self.published_at == nil
 193      
 194      if self.published_at != nil && (published? || scheduled?)
 195        self[:status_id] = Status[:scheduled].id if self.published_at  > Time.zone.now
 196        self[:status_id] = Status[:published].id if self.published_at <= Time.zone.now
 197      end
 198 
 199      true    
 200    end
 201 
 202    def to_xml(options={}, &block)
 203      super(options.reverse_merge(:include => :parts), &block)
 204    end
 205 
 206    def default_child
 207      self.class.default_child
 208    end
 209 
 210    def allowed_children_lookup
 211      [default_child, *Page.descendants.sort_by(&:name)].uniq
 212    end
 213 
 214    def set_allowed_children_cache
 215      self.allowed_children_cache = allowed_children_lookup.collect(&:name).join(',')
 216    end
 217 
 218    class << self
 219 
 220      def root
 221        find_by_parent_id(nil)
 222      end
 223 
 224      def find_by_path(path, live = true)
 225        raise MissingRootPageError unless root
 226        root.find_by_path(path, live)
 227      end
 228      def find_by_url(*args)
 229        ActiveSupport::Deprecation.warn("`find_by_url' has been deprecated; use `find_by_path' instead.", caller)
 230        find_by_path(*args)
 231      end
 232 
 233      def date_column_names
 234        self.columns.collect{|c| c.name if c.sql_type =~ /(date|time)/}.compact
 235      end
 236 
 237      def display_name(string = nil)
 238        if string
 239          @display_name = string
 240        else
 241          @display_name ||= begin
 242            n = name.to_s
 243            n.sub!(/^(.+?)Page$/, '\1')
 244            n.gsub!(/([A-Z])/, ' \1')
 245            n.strip
 246          end
 247        end
 248        @display_name = @display_name + " - not installed" if missing? && @display_name !~ /not installed/
 249        @display_name
 250      end
 251      
 252      def display_name=(string)
 253        display_name(string)
 254      end
 255 
 256      def load_subclasses
 257        ([RADIANT_ROOT] + Radiant::Extension.descendants.map(&:root)).each do |path|
 258          Dir["#{path}/app/models/*_page.rb"].each do |page|
 259            $1.camelize.constantize if page =~ %r{/([^/]+)\.rb}
 260          end
 261        end
 262        if ActiveRecord::Base.connection.tables.include?('pages') && Page.column_names.include?('class_name') # Assume that we have bootstrapped
 263          Page.connection.select_values("SELECT DISTINCT class_name FROM pages WHERE class_name <> '' AND class_name IS NOT NULL").each do |p|
 264            begin
 265              p.constantize
 266            rescue NameError, LoadError
 267              eval(%Q{class #{p} < Page; acts_as_tree; def self.missing?; true end end}, TOPLEVEL_BINDING)
 268            end
 269          end
 270        end
 271      end
 272 
 273      def new_with_defaults(config = Radiant::Config)
 274        page = new
 275        page.parts.concat default_page_parts(config)
 276        page.fields.concat default_page_fields(config)
 277        default_status = config['defaults.page.status']
 278        page.status = Status[default_status] if default_status
 279        page
 280      end
 281 
 282      def is_descendant_class_name?(class_name)
 283        (Page.descendants.map(&:to_s) + [nil, "", "Page"]).include?(class_name)
 284      end
 285 
 286      def descendant_class(class_name)
 287        raise ArgumentError.new("argument must be a valid descendant of Page") unless is_descendant_class_name?(class_name)
 288        if ["", nil, "Page"].include?(class_name)
 289          Page
 290        else
 291          class_name.constantize
 292        end
 293      end
 294 
 295      def missing?
 296        false
 297      end
 298 
 299      private
 300 
 301        def default_page_parts(config = Radiant::Config)
 302          default_parts = config['defaults.page.parts'].to_s.strip.split(/\s*,\s*/)
 303          default_parts.map do |name|
 304            PagePart.new(:name => name, :filter_id => config['defaults.page.filter'])
 305          end
 306        end
 307 
 308        def default_page_fields(config = Radiant::Config)
 309          default_fields = config['defaults.page.fields'].to_s.strip.split(/\s*,\s*/)
 310          default_fields.map do |name|
 311            PageField.new(:name => name)
 312          end
 313        end
 314    end
 315 
 316    private
 317 
 318      def valid_class_name
 319        unless Page.is_descendant_class_name?(class_name)
 320          errors.add :class_name, "must be set to a valid descendant of Page"
 321        end
 322      end
 323 
 324      def attributes_protected_by_default
 325        super - [self.class.inheritance_column]
 326      end
 327 
 328      def update_virtual
 329        unless self.class == Page.descendant_class(class_name)
 330          self.virtual = Page.descendant_class(class_name).new.virtual?
 331        else
 332          self.virtual = virtual?
 333        end
 334        true
 335      end
 336 
 337      def clean_path(path)
 338        "/#{ path.to_s.strip }/".gsub(%r{//+}, '/')
 339      end
 340      alias_method :clean_url, :clean_path
 341 
 342      def parent?
 343        !parent.nil?
 344      end
 345 
 346      def lazy_initialize_parser_and_context
 347        unless @parser and @context
 348          @context = PageContext.new(self)
 349          @parser = Radius::Parser.new(@context, :tag_prefix => 'r')
 350        end
 351        @parser
 352      end
 353 
 354      def parse(text)
 355        lazy_initialize_parser_and_context.parse(text)
 356      end
 357 
 358      def parse_object(object)
 359        text = object.content || ''
 360        text = parse(text)
 361        text = object.filter.filter(text) if object.respond_to? :filter_id
 362        text
 363      end
 364 
 365  end