class Coverband::Collectors::ViewTracker

This class tracks view file usage via ActiveSupport::Notifications

This is a port of Flatfoot, a project I open sourced years ago, but am now rolling into Coverband github.com/livingsocial/flatfoot

Attributes

ignore_patterns[R]
logged_views[RW]
logger[R]
roots[R]
store[R]
target[RW]
views_to_record[RW]

Public Class Methods

new(options = {}) click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 18
def initialize(options = {})
  raise NotImplementedError, "View Tracker requires Rails 4 or greater" unless self.class.supported_version?
  raise "Coverband: view tracker initialized before configuration!" if !Coverband.configured? && ENV["COVERBAND_TEST"] == "test"

  @project_directory = File.expand_path(Coverband.configuration.root)
  @ignore_patterns = Coverband.configuration.ignore
  @store = options.fetch(:store) { Coverband.configuration.store }
  @logger = options.fetch(:logger) { Coverband.configuration.logger }
  @target = options.fetch(:target) { Dir.glob("#{@project_directory}/app/views/**/*.html.{erb,haml,slim}") }

  @roots = options.fetch(:roots) { Coverband.configuration.all_root_patterns }
  @roots = @roots.split(",") if @roots.is_a?(String)
  @one_time_timestamp = false

  @logged_views = []
  @views_to_record = []
end
supported_version?() click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 133
def self.supported_version?
  defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split(".").first.to_i >= 4
end

Public Instance Methods

all_views() click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 73
def all_views
  all_views = []
  target.each do |view|
    roots.each do |root|
      view = view.gsub(root, "")
    end
    all_views << view
  end
  all_views.uniq
end
as_json() click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 91
def as_json
  {
    unused_views: unused_views,
    used_views: used_views
  }.to_json
end
clear_file!(filename) click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 111
def clear_file!(filename)
  return unless filename

  filename = "#{@project_directory}/#{filename}"
  redis_store.hdel(tracker_key, filename)
  logged_views.delete(filename)
end
report_views_tracked() click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 119
def report_views_tracked
  redis_store.set(tracker_time_key, Time.now.to_i) unless @one_time_timestamp || tracker_time_key_exists?
  @one_time_timestamp = true
  reported_time = Time.now.to_i
  views_to_record.each do |file|
    redis_store.hset(tracker_key, file, reported_time)
  end
  self.views_to_record = []
rescue => e
  # we don't want to raise errors if Coverband can't reach redis.
  # This is a nice to have not a bring the system down
  logger&.error "Coverband: view_tracker failed to store, error #{e.class.name}"
end
reset_recordings() click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 106
def reset_recordings
  redis_store.del(tracker_key)
  redis_store.del(tracker_time_key)
end
track_views(_name, _start, _finish, _id, payload) click to toggle source

This method is called on every render call, so we try to reduce method calls and ensure high performance

# File lib/coverband/collectors/view_tracker.rb, line 40
def track_views(_name, _start, _finish, _id, payload)
  if (file = payload[:identifier])
    if newly_seen_file?(file)
      logged_views << file
      views_to_record << file if track_file?(file)
    end
  end

  ###
  # Annoyingly while you get full path for templates
  # notifications only pass part of the path for layouts dropping any format info
  # such as .html.erb or .js.erb
  # http://edgeguides.rubyonrails.org/active_support_instrumentation.html#render_partial-action_view
  ###
  return unless (layout_file = payload[:layout])
  return unless newly_seen_file?(layout_file)

  logged_views << layout_file
  views_to_record << layout_file if track_file?(layout_file, layout: true)
end
tracking_since() click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 98
def tracking_since
  if (tracking_time = redis_store.get(tracker_time_key))
    Time.at(tracking_time.to_i).iso8601
  else
    "N/A"
  end
end
unused_views() click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 84
def unused_views
  recently_used_views = used_views.keys
  unused_views = all_views.reject { |view| recently_used_views.include?(view) }
  # since layouts don't include format we count them used if they match with ANY formats
  unused_views.reject { |view| view.match(/\/layouts\//) && recently_used_views.any? { |used_view| view.include?(used_view) } }
end
used_views() click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 61
def used_views
  views = redis_store.hgetall(tracker_key)
  normalized_views = {}
  views.each_pair do |view, time|
    roots.each do |root|
      view = view.gsub(root, "")
    end
    normalized_views[view] = time
  end
  normalized_views
end

Protected Instance Methods

newly_seen_file?(file) click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 139
def newly_seen_file?(file)
  return false if logged_views.include?(file)

  true
end
track_file?(file, options = {}) click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 145
def track_file?(file, options = {})
  @ignore_patterns.none? do |pattern|
    file.include?(pattern)
  end && (file.start_with?(@project_directory) || options[:layout])
end

Private Instance Methods

redis_store() click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 153
def redis_store
  store.raw_store
end
tracker_key() click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 165
def tracker_key
  "render_tracker_2"
end
tracker_time_key() click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 169
def tracker_time_key
  "render_tracker_time"
end
tracker_time_key_exists?() click to toggle source
# File lib/coverband/collectors/view_tracker.rb, line 157
def tracker_time_key_exists?
  if defined?(redis_store.exists?)
    redis_store.exists?(tracker_time_key)
  else
    redis_store.exists(tracker_time_key)
  end
end