withassociates/slices

View on GitHub
app/models/page.rb

Summary

Maintainability
B
5 hrs
Test Coverage
class Page
  include Mongoid::Document
  include Mongoid::Timestamps
  include MongoSearch::Searchable

  include Slices::Tree
  include Slices::PageAsJSON
  include Slices::HasSlices
  include Slices::HasAttachments::PageInstanceMethods

  DESCRIPTION_DEPRECATION_WARNING = "Page#description is now meta_description. If you are upgrading, run 'rake slices:migrate:meta_description' to update."
  LAST_CHANGED_AT_CACHE_KEY = 'page-last-changed'

  field :name, localize: Slices::Config.i18n?
  field :role  # only relevant for virtual pages
  field :active, type: Boolean, default: false
  field :layout, type: String, default: 'default'
  field :meta_description, localize: Slices::Config.i18n?
  field :title, localize: Slices::Config.i18n?
  field :has_content, type: Boolean, default: false

  belongs_to :author, class_name: 'Admin'
  has_slices :slices
  has_and_belongs_to_many :assets

  text_search_in :introduction, :extended, :name

  scope :entries, ->{ all }
  scope :virtual, ->{ where(:role.ne => nil).asc(:name) }

  validates_presence_of :name

  before_save :update_has_content
  before_destroy :destroy_children
  after_save :update_last_changed_at
  after_save :cache_virtual_page

  class NotFound < RuntimeError; end

  CACHED_VIRTUAL_PAGES = {
    'not_found' => '404',
    'error' => '500'
  }

  def self.role_for_status(status)
    if CACHED_VIRTUAL_PAGES.has_value?(status)
      CACHED_VIRTUAL_PAGES.detect { |k, v| v == status }[0]
    else
      nil
    end
  end

  def self.available_layouts
    Layout.all.map do |human_name, machine_name|
      { human_name: human_name, machine_name: machine_name }
    end
  end

  # Virtual pages are not associated with a specific URL. Instead, they
  # can be rendered at any path, depending on the circumstances (e.g.
  # when a page isn't found, or when an error occurs). Consequently they
  # aren't created with a :parent attribute.
  def self.make(attributes = {})
    attributes = attributes.symbolize_keys
    parent = parent_from_attributes(attributes)
    attributes[:path] ||= path_from_attributes(attributes, parent)

    new(attributes).tap do |page|
      yield(page) if block_given?
      page.parent = parent unless attributes.include?(:role)
      if parent.present?
        page.position = parent.children.count
        page.layout = parent.layout if page.entry?
      end
      page.save
    end
  end

  def self.find_by_id(id)
    ActiveSupport::Deprecation::warn 'Page.find_by_id is depreciated, please use Page.find instead.'
    find(id)
  rescue Mongoid::Errors::DocumentNotFound
    nil
  end

  def self.find_by_id!(id)
    ActiveSupport::Deprecation::warn 'Page.find_by_id is depreciated, please use Page.find instead.'
    find_by_id(id) || (raise NotFound)
  end

  def self.find_virtual(role)
    find_by(role: role)
  end

  def update_last_changed_at
    Rails.cache.write(LAST_CHANGED_AT_CACHE_KEY, Time.now.to_i)
  end

  def self.last_changed_at
    Rails.cache.read(LAST_CHANGED_AT_CACHE_KEY) || 0
  end

  def cacheable_virtual_page?
    CACHED_VIRTUAL_PAGES.has_key?(role)
  end

  def cache_virtual_page
    if cacheable_virtual_page? && (! Rails.env.test?)
      fork do
        script = File.join(Slices.gem_path, 'script', 'request-local-page')
        rails = File.join(Rails.root, 'script', 'rails')
        path = "/#{CACHED_VIRTUAL_PAGES[role]}.html"
        FileUtils.rm_f(File.join(Rails.root, 'public', path))
        exec(rails, 'runner', '-e', Rails.env, script, path)
      end
    end
  end

  def virtual?
    role.present?
  end

  def entry?
    false
  end

  def set_page?
    kind_of?(SetPage)
  end

  def sets
    slices.select { |slice| slice.kind_of?(SetSlice) }
  end

  def set_slice(kind)
    slice_class = Object.const_get("#{kind.to_s}SetSlice".camelize)
    slices.detect { |slice| slice.kind_of?(slice_class) }
  end

  def template
    "pages/show"
  end

  def update_attributes(attributes)
    attributes = attributes.symbolize_keys

    unless home?
      if attributes.has_key?(:name) || attributes.has_key?(:permalink)
        new_path = self.class.path_from_attributes(attributes, parent)
        attributes[:path] = new_path if new_path != path
      end
      if attributes.has_key?(:path) && attributes[:path].blank?
        attributes.delete(:path)
      end
    end

    super
    if valid?
      update_path_for_children if attributes.has_key?(:path)
    end
  end


  # Added in merge or page & content
  def set_keywords
    super
    slices.each do |slice|
      self._keywords += MongoSearch::KeywordsExtractor.extract(slice.search_text)
    end
    self._keywords.uniq!
  end

  def available_layouts
    self.class.available_layouts
  end

  # End of added

  def description
    ActiveSupport::Deprecation::warn DESCRIPTION_DEPRECATION_WARNING
    meta_description
  end

  def description=(value)
    ActiveSupport::Deprecation::warn DESCRIPTION_DEPRECATION_WARNING
    self.meta_description = value
  end

  def self.parent_from_attributes(attributes)
    if attributes.has_key?(:parent_path)
      Page.find_by_path(attributes.delete(:parent_path))
    elsif attributes.has_key?(:parent_id)
      Page.find(attributes.delete(:parent_id))
    else
      attributes.delete(:parent)
    end
  end
  private_class_method :parent_from_attributes

  def self.page_exists?(path)
    Page.find_by_path(path)
  rescue NotFound
    false
  end
  private_class_method :page_exists?

  private
    def update_has_content
      self.has_content = slices.any?
      true # must be true otherwise save will fail
    end

    def destroy_children
      children.each { |child| child.destroy }
    end

end