Module: Fragmentary::Fragment

Defined in:
lib/fragmentary/fragment.rb

Defined Under Namespace

Modules: ClassMethods, NeedsKey, NeedsRecordId, NeedsUserId, NeedsUserType

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.base_classObject



5
6
7
# File 'lib/fragmentary/fragment.rb', line 5

def self.base_class
  @base_class
end

.included(base) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 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_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

Instance Method Details

#cache_exist?Boolean

Returns:

  • (Boolean)


543
544
545
546
# File 'lib/fragmentary/fragment.rb', line 543

def cache_exist?
  # expand_cache_key calls cache_key and prepends "views/"
  cache_store.exist?(fragment_key)
end

#cache_storeObject



492
493
494
# File 'lib/fragmentary/fragment.rb', line 492

def cache_store
  self.class.cache_store
end

#child(options) ⇒ Object

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.



442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'lib/fragmentary/fragment.rb', line 442

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_keyObject

Instance Methods




423
424
425
# File 'lib/fragmentary/fragment.rb', line 423

def child_search_key
  self.class.child_search_key
end

#contentObject

Typically used along with #cache_exist? when testing from the console. Note that both methods will only return correct results for fragments associated with the application_root_url (either root or children) corresponding to the particular console session in use. i.e. you can’t see into the production cache from a prerelease console session and vice versa.



553
554
555
# File 'lib/fragmentary/fragment.rb', line 553

def content
  cache_store.read(fragment_key)
end

#delete_cacheObject



513
514
515
# File 'lib/fragmentary/fragment.rb', line 513

def delete_cache
  cache_store.delete(fragment_key)
end

#delete_cache_treeObject

Recursively delete the cache entry for this fragment and all of its children Does NOT destroy the fragment or its children



519
520
521
522
# File 'lib/fragmentary/fragment.rb', line 519

def delete_cache_tree
  children.each(&:delete_cache_tree)
  delete_cache if cache_exist?
end

#delete_matched_cacheObject



509
510
511
# File 'lib/fragmentary/fragment.rb', line 509

def delete_matched_cache
  cache_store.delete_matched(Regexp.new("#{self.class.model_name.cache_key}/#{id}"))
end

#destroy(options = {}) ⇒ Object

delete the associated cache content before destroying the fragment



503
504
505
506
507
# File 'lib/fragmentary/fragment.rb', line 503

def destroy(options = {})
  options.delete(:delete_matches) ? delete_matched_cache : delete_cache
  @no_request = options.delete(:no_request)  # stored for use in #touch_parent via the after_commit callback
  super()
end

#existing_child(options) ⇒ Object



433
434
435
# File 'lib/fragmentary/fragment.rb', line 433

def existing_child(options)
  child(options.merge(:existing => true))
end

#fragment_keyObject

This emulates the result of passing the fragment object to AbstractController::Caching::Fragments#combined_fragment_cache_key when the cache helper method invokes controller.read_fragment from the view. The result can be passed to ActiveSupport::Cache methods #read, #write, #fetch, #delete, and #exist?



560
561
562
# File 'lib/fragmentary/fragment.rb', line 560

def fragment_key
  ['views', self]
end

#record_typeObject

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.



482
483
484
# File 'lib/fragmentary/fragment.rb', line 482

def record_type
  @record_type ||= self.class.needs_record_id? ? self.class.record_type : self.parent.record_type
end

#requestObject

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



593
594
595
# File 'lib/fragmentary/fragment.rb', line 593

def request
  requestable? ? @request ||= Request.new(request_method, request_path, request_parameters, request_options) : nil
end

#request_methodObject

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.



576
577
578
# File 'lib/fragmentary/fragment.rb', line 576

def request_method
  self.class.request_method
end

#request_optionsObject



584
585
586
# File 'lib/fragmentary/fragment.rb', line 584

def request_options
  self.class.request_options
end

#request_parametersObject



580
581
582
# File 'lib/fragmentary/fragment.rb', line 580

def request_parameters
  self.class.request_parameters # -> nil
end

#request_queuesObject

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.



488
489
490
# File 'lib/fragmentary/fragment.rb', line 488

def request_queues
  self.class.request_queues
end

#requestable?Boolean

Returns:

  • (Boolean)


588
589
590
# File 'lib/fragmentary/fragment.rb', line 588

def requestable?
  @requestable ||= respond_to? :request_path
end

#set_indexed_childrenObject



427
428
429
430
431
# File 'lib/fragmentary/fragment.rb', line 427

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) ⇒ Object



496
497
498
499
500
# File 'lib/fragmentary/fragment.rb', line 496

def touch(*args, no_request: false)
  @no_request = no_request  # stored for use in #touch_parent via the after_commit callback
  request_queues.each{|key, hsh| hsh.each{|key2, queue| queue << request}} if request && !no_request
  super(*args)
end

#touch_or_destroyObject

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



533
534
535
536
537
538
539
540
541
# File 'lib/fragmentary/fragment.rb', line 533

def touch_or_destroy
  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(:no_request => true)  # will also destroy all children because of :dependent => :destroy
  end
end

#touch_tree(no_request: false) ⇒ Object

Recursively touch the fragment and all of its children



525
526
527
528
529
# File 'lib/fragmentary/fragment.rb', line 525

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