module Fragmentary::Fragment
Attributes
Public Class Methods
# File lib/fragmentary/fragment.rb, line 5 def self.base_class @base_class end
# 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
# 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
# File lib/fragmentary/fragment.rb, line 451 def cache_store self.class.cache_store end
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
Instance Methods
# File lib/fragmentary/fragment.rb, line 382 def child_search_key self.class.child_search_key end
# File lib/fragmentary/fragment.rb, line 469 def delete_cache cache_store.delete(ActiveSupport::Cache.expand_cache_key(self, 'views')) end
# 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
# File lib/fragmentary/fragment.rb, line 460 def destroy(options = {}) options.delete(:delete_matches) ? delete_matched_cache : delete_cache super() end
# File lib/fragmentary/fragment.rb, line 392 def existing_child(options) child(options.merge(:existing => true)) end
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
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-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
# File lib/fragmentary/fragment.rb, line 517 def request_options self.class.request_options end
# File lib/fragmentary/fragment.rb, line 513 def request_parameters self.class.request_parameters # -> nil end
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
# File lib/fragmentary/fragment.rb, line 521 def requestable? @requestable ||= respond_to? :request_path end
# 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
# 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 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
# 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
# File lib/fragmentary/fragment.rb, line 531 def touch_parent parent.try :touch unless previous_changes["memo"] end