comfy/comfortable-mexican-sofa

View on GitHub
app/models/concerns/comfy/cms/with_fragments.rb

Summary

Maintainability
A
25 mins
Test Coverage
# frozen_string_literal: true

module Comfy::Cms::WithFragments

  extend ActiveSupport::Concern

  included do
    attr_accessor :fragments_attributes_changed

    belongs_to :layout,
      class_name: "Comfy::Cms::Layout"

    has_many :fragments,
      class_name: "Comfy::Cms::Fragment",
      as:         :record,
      autosave:   true,
      dependent:  :destroy

    before_save :clear_content_cache

    validates :layout,
      presence: true
  end

  # Array of fragment hashes in the following format:
  #   [
  #     {identifier: "frag_a", format: "text", content: "fragment a content"},
  #     {identifier: "frag_b", format: "file", files: [{file_a}, {file_b}]}
  #   ]
  # It also handles when frag hashes come in as a hash:
  #   {
  #     "0" => {identifer: "foo", content: "bar"},
  #     "1" => {identifier: "bar", content: "foo"}
  #   }
  def fragments_attributes=(frag_hashes = [])
    frag_hashes = frag_hashes.values if frag_hashes.is_a?(Hash)

    frag_hashes.each do |frag_attrs|
      unless frag_attrs.is_a?(HashWithIndifferentAccess)
        frag_attrs.symbolize_keys!
      end

      identifier = frag_attrs.delete(:identifier)

      fragment =
        fragments.detect { |f| f.identifier == identifier } ||
        fragments.build(identifier: identifier)

      fragment.attributes = frag_attrs

      # tracking dirty
      self.fragments_attributes_changed ||= fragment.changed?
    end
  end

  # Snapshop of page fragments data used primarily for saving revisions
  def fragments_attributes(was = false)
    fragments.collect do |frag|
      attrs = {}
      %i[identifier tag content datetime boolean].each do |column|
        attrs[column] = frag.send(was ? "#{column}_was" : column)
      end
      # TODO: save files against revision (not on db though)
      # attrs[:files] = frag.attachments.collect do |a|
      #   {io: a.download, filename: a.filename.to_s, content_type: a.content_type}
      # end
      attrs
    end
  end

  # Method to collect prevous state of blocks for revisions
  def fragments_attributes_was
    fragments_attributes(:previous_values)
  end

  # Grabbing nodes that we need to render form elements in the admin area
  # Rejecting duplicates as we'd need to render only one form field. Don't declare
  # duplicate tags on the layout. That's wierd (but still works).
  def fragment_nodes
    nodes
      .select { |n| n.is_a?(ComfortableMexicanSofa::Content::Tag::Fragment) }
      .uniq(&:identifier)
  end

  # Rendered content of the page. We grab whatever layout is associated with the
  # page and feed its content tokens to the renderer while passing this page as
  # context.
  def render(n = nodes)
    renderer.render(n)
  end

  # If content_cache column is populated we don't need to call render for this
  # page.
  def content_cache
    if (cache = read_attribute(:content_cache)).nil?
      cache = render
      update_column(:content_cache, cache) unless new_record?
    end
    cache
  end

  # Nuking content cache so it can be regenerated.
  def clear_content_cache!
    update_column(:content_cache, nil)
  end

  # Blanking cache on page saves so it can be regenerated on access
  def clear_content_cache
    write_attribute(:content_cache, nil)
  end

protected

  def renderer
    ComfortableMexicanSofa::Content::Renderer.new(self)
  end

  def nodes
    return [] unless layout.present?

    tokens = layout.content_tokens
    renderer.nodes(tokens)
  end

end