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)


533
534
535
536
# File 'lib/fragmentary/fragment.rb', line 533

def cache_exist?
  # expand_cache_key calls cache_key and prepends "views/"
  cache_store.exist?(ActiveSupport::Cache.expand_cache_key(self, 'views'))
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

#delete_cacheObject



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

def delete_cache
  cache_store.delete(ActiveSupport::Cache.expand_cache_key(self, 'views'))
end

#delete_matched_cacheObject



506
507
508
# File 'lib/fragmentary/fragment.rb', line 506

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

#destroy(options = {}) ⇒ Object



501
502
503
504
# File 'lib/fragmentary/fragment.rb', line 501

def destroy(options = {})
  options.delete(:delete_matches) ? delete_matched_cache : delete_cache
  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

#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



567
568
569
# File 'lib/fragmentary/fragment.rb', line 567

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.



550
551
552
# File 'lib/fragmentary/fragment.rb', line 550

def request_method
  self.class.request_method
end

#request_optionsObject



558
559
560
# File 'lib/fragmentary/fragment.rb', line 558

def request_options
  self.class.request_options
end

#request_parametersObject



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

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)


562
563
564
# File 'lib/fragmentary/fragment.rb', line 562

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
# File 'lib/fragmentary/fragment.rb', line 496

def touch(*args, no_request: false)
  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.



522
523
524
525
526
527
528
529
530
531
# File 'lib/fragmentary/fragment.rb', line 522

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



514
515
516
517
518
# File 'lib/fragmentary/fragment.rb', line 514

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