module Fragmentary::Fragment

Attributes

key_name[W]
record_type[W]

Public Class Methods

base_class() click to toggle source
# File lib/fragmentary/fragment.rb, line 5
def self.base_class
  @base_class
end
included(base) click to toggle source
# File lib/fragmentary/fragment.rb, line 9
def self.included(base)

  @base_class = base

  base.class_eval do
    include ActionView::Helpers::CacheHelper

    belongs_to :parent, :class_name => name
    belongs_to :root, :class_name => name
    has_many :children, :class_name => name, :foreign_key => :parent_id, :dependent => :destroy
    belongs_to :user

    # Don't touch the parent when we create the child - the child was created by
    # renderng the parent, which occured because the parent was touched, thus
    # triggering the current request. Touching it again would result in a
    # redundant duplicate request.
    after_commit :touch_parent, :on => [:update, :destroy]

    attr_accessible :parent_id, :root_id, :record_id, :user_id, :user_type, :key

    attr_accessor :indexed_children

    # Set cache timestamp format to :usec instead of :nsec because the latter is greater precision than Postgres supports,
    # resulting in mismatches between timestamps on a newly created fragment and one retrieved from the database.
    # Probably not needed for Rails 5, which uses :usec by default.
    self.cache_timestamp_format = :usec

  end

  base.instance_eval do
    class << self; attr_writer :record_type, :key_name; end
  end

  base.extend ClassMethods

  ActionView::Base.send :include, FragmentsHelper
end

Public Instance Methods

cache_exist?() click to toggle source
# File lib/fragmentary/fragment.rb, line 492
def cache_exist?
  # expand_cache_key calls cache_key and prepends "views/"
  cache_store.exist?(ActiveSupport::Cache.expand_cache_key(self, 'views'))
end
cache_store() click to toggle source
# File lib/fragmentary/fragment.rb, line 451
def cache_store
  self.class.cache_store
end
child(options) click to toggle source

Note that this method can be called in two different contexts. One is as part of rendering the parent fragment, which means that the parent was obtained using either Fragment.root or a previous invocation of this method. In this case, the children will have already been loaded and indexed. The second is when the child is being rendered on its own, e.g. inserted by ajax into a parent that is already on the page. In this case the children won't have already been loaded or indexed.

# File lib/fragmentary/fragment.rb, line 401
def child(options)
  if child = options[:child]
    raise ArgumentError, "You passed a child fragment to a parent it's not a child of." unless child.parent_id == self.id
    child
  else
    existing = options.delete(:existing)
    # root_id and parent_id are passed from parent to child. For all except root fragments, root_id is stored explicitly.
    derived_options = {:root_id => root_id || id}
    # record_id is passed from parent to child unless it is required to be provided explicitly.
    derived_options.merge!(:record_id => record_id) unless options[:type].constantize.needs_record_id?
    klass, search_attributes, options = Fragment.base_class.attributes(options.reverse_merge(derived_options))

    # Try to find the child within the children loaded previously
    select_attributes = search_attributes.merge(:type => klass.name)
    if child_search_key and keyed_children = indexed_children.try(:[], select_attributes[child_search_key])
      # If the key was found we don't need to include it in the attributes used for the final selection
      select_attributes.delete(child_search_key)
    end

    # If there isn't a key or there isn't set of previously indexed_children (e.g. the child is being rendered
    # on its own), we just revert to the regular children association.
    fragment = (keyed_children || children).to_a.find{|child| select_attributes.all?{|key, value| child.send(key) == value}}

    # If we didn't find an existing child, create a new record unless only an existing record was requested
    unless fragment or existing
      fragment = klass.new(search_attributes.merge(options))
      children << fragment  # Saves the fragment and sets the parent_id attribute
    end

    # Load the grandchildren, so they'll each be available later. Index them if a search key is available.
    if fragment
      fragment_children = fragment.children
      fragment.set_indexed_children if fragment.child_search_key
    end
    fragment
  end
end
child_search_key() click to toggle source

Instance Methods


# File lib/fragmentary/fragment.rb, line 382
def child_search_key
  self.class.child_search_key
end
delete_cache() click to toggle source
# File lib/fragmentary/fragment.rb, line 469
def delete_cache
  cache_store.delete(ActiveSupport::Cache.expand_cache_key(self, 'views'))
end
delete_matched_cache() click to toggle source
# File lib/fragmentary/fragment.rb, line 465
def delete_matched_cache
  cache_store.delete_matched(Regexp.new("#{self.class.model_name.cache_key}/#{id}"))
end
destroy(options = {}) click to toggle source
Calls superclass method
# File lib/fragmentary/fragment.rb, line 460
def destroy(options = {})
  options.delete(:delete_matches) ? delete_matched_cache : delete_cache
  super()
end
existing_child(options) click to toggle source
# File lib/fragmentary/fragment.rb, line 392
def existing_child(options)
  child(options.merge(:existing => true))
end
record_type() click to toggle source

If this fragment's class needs a record_id, it will also have a record_type. If not, we copy the record_id from the parent, if it has one.

# File lib/fragmentary/fragment.rb, line 441
def record_type
  @record_type ||= self.class.needs_record_id? ? self.class.record_type : self.parent.record_type
end
request() click to toggle source

Returns a Request object that can be used to send a server request for the fragment content

# File lib/fragmentary/fragment.rb, line 526
def request
  requestable? ? @request ||= Request.new(request_method, request_path, request_parameters, request_options) : nil
end
request_method() click to toggle source

Request-related methods… Note: subclasses that define request_path need to also define self.request_path and should define the instance method in terms of the class method. Likewise, those that define request_parameters also need to defined self.request_parameters and define the instance method in terms of the class method. Subclasses generally don't need to define request_method or request_options, but may need to define self.request_options. The instance method version request_options is defined in terms of the class method below.

Also… subclasses that define request_path also need to define self.request, but not the instance method request since that is defined below in terms of its constituent request arguments. The reason is that the class method self.request generally takes a parameter (e.g. a record_id or a key), and this is used in different ways depending on the class, whereas the instance method takes the same form regardless of the class.

# File lib/fragmentary/fragment.rb, line 509
def request_method
  self.class.request_method
end
request_options() click to toggle source
# File lib/fragmentary/fragment.rb, line 517
def request_options
  self.class.request_options
end
request_parameters() click to toggle source
# File lib/fragmentary/fragment.rb, line 513
def request_parameters
  self.class.request_parameters # -> nil
end
request_queues() click to toggle source

Though each fragment is typically associated with a particular user_type, touching a root fragment will send page requests for the path associated with the fragment to queues for all relevant user_types for this fragment class.

# File lib/fragmentary/fragment.rb, line 447
def request_queues
  self.class.request_queues
end
requestable?() click to toggle source
# File lib/fragmentary/fragment.rb, line 521
def requestable?
  @requestable ||= respond_to? :request_path
end
set_indexed_children() click to toggle source
# File lib/fragmentary/fragment.rb, line 386
def set_indexed_children
  return unless child_search_key
  obj = Hash.new {|h, indx| h[indx] = []}
  @indexed_children = children.each_with_object(obj) {|child, collection| collection[child.send(child_search_key)] << child }
end
touch(*args, no_request: false) click to toggle source
Calls superclass method
# File lib/fragmentary/fragment.rb, line 455
def touch(*args, no_request: false)
  request_queues.each{|key, queue| queue << request} if request && !no_request
  super(*args)
end
touch_or_destroy() click to toggle source

Touch this fragment and all descendants that have entries in the cache. Destroy any that don't have cache entries.

# File lib/fragmentary/fragment.rb, line 481
def touch_or_destroy
  puts "  touch_or_destroy #{self.class.name} #{id}"
  if cache_exist?
    children.each(&:touch_or_destroy)
    # if there are children, this will be touched automatically once they are.
    touch(:no_request => true) unless children.any?
  else
    destroy  # will also destroy all children because of :dependent => :destroy
  end
end
touch_tree(no_request: false) click to toggle source
# File lib/fragmentary/fragment.rb, line 473
def touch_tree(no_request: false)
  children.each{|child| child.touch_tree(:no_request => no_request)}
  # If there are children, we'll have already touched this fragment in the process of touching them.
  touch(:no_request => no_request) unless children.any?
end

Private Instance Methods

touch_parent() click to toggle source
# File lib/fragmentary/fragment.rb, line 531
def touch_parent
  parent.try :touch unless previous_changes["memo"]
end