class Fingerprintable::Fingerprinter

Attributes

attributes[R]
fallback_to_string[R]
ignore[R]
object[R]

Public Class Methods

diff(obj1, obj2) click to toggle source
# File lib/fingerprintable/fingerprinter.rb, line 80
def self.diff(obj1, obj2)
  f1 =  if obj1.respond_to?(:fingerprinter)
          obj1.fingerprinter
        else
          Fingerprinter.new(object: obj1)
        end

  f2 =  if obj2.respond_to?(:fingerprinter)
          obj2.fingerprinter
        else
          Fingerprinter.new(object: obj2)
        end

  f1.diff(f2)
end
new( attributes: [], cache: {}, fallback_to_string: false, ignore: [], object: ) click to toggle source
# File lib/fingerprintable/fingerprinter.rb, line 10
def initialize(
  attributes: [],
  cache: {},
  fallback_to_string: false,
  ignore: [],
  object:
)
  @fallback_to_string = fallback_to_string
  @ignore = ignore | ignore.map { |e| "@#{e}".to_sym }
  @object = object
  @attributes = populate_attributes(attributes: attributes, ignore: ignore)
  @cache = cache || {}
  @cache[object] = next_cache_id
  @fingerprinted = false
end

Public Instance Methods

cache() click to toggle source
# File lib/fingerprintable/fingerprinter.rb, line 26
def cache
  fingerprint unless @fingerprinted
  @cache
end
deep_convert_and_sort(obj) click to toggle source
# File lib/fingerprintable/fingerprinter.rb, line 44
def deep_convert_and_sort(obj)
  case obj
  when nil
    ''
  when String
    obj.to_s
  when FalseClass, Float, Integer, Symbol, TrueClass
    obj.to_s
  when Array
    obj.map { |v| deep_convert_and_sort(v) }.sort.to_s
  when Hash
    Hash[obj.map { |k, v| [deep_convert_and_sort(k), deep_convert_and_sort(v)] }].sort.to_s
  when Module
    obj.name
  else
    fingerprint_object(obj)
  end
end
diff(other_fingerprinter) click to toggle source
# File lib/fingerprintable/fingerprinter.rb, line 31
def diff(other_fingerprinter)
  raise "Passed object (#{other_fingerprinter.class.name}) does not match the fingerprinter object class: #{object.class.name}" if object.class != other_fingerprinter.object.class

  values = object_values_hash
  other_values = other_fingerprinter.object_values_hash

  (attributes | other_fingerprinter.attributes).reject { |e| values[e] == other_values[e] }
end
diff?(other_fingerprinter) click to toggle source
# File lib/fingerprintable/fingerprinter.rb, line 40
def diff?(other_fingerprinter)
  diff(other_fingerprinter).any?
end
fingerprint() click to toggle source
# File lib/fingerprintable/fingerprinter.rb, line 63
def fingerprint
  @fingerprint ||= begin
    @fingerprinted = true
    Digest::MD5.hexdigest(to_s)
  end
end
object_values_hash() click to toggle source
# File lib/fingerprintable/fingerprinter.rb, line 70
def object_values_hash
  @object_values_hash ||= Hash[attributes.map do |attr|
    [attr, object.send(attr)]
  end]
end
to_s() click to toggle source
# File lib/fingerprintable/fingerprinter.rb, line 76
def to_s
  @to_s ||= deep_convert_and_sort(object_values_hash).to_s
end

Private Instance Methods

fingerprint_object(use_object) click to toggle source
# File lib/fingerprintable/fingerprinter.rb, line 98
def fingerprint_object(use_object)
  return cache[use_object] if cache.include?(use_object)

  fingerprinter = if use_object.respond_to?(:fingerprint)
                    use_object.fingerprinter(cache: @cache)
                  elsif use_object.respond_to?(:instance_variables)
                    Fingerprinter.new(cache: cache, object: use_object)
                  end

  if fingerprinter.nil?
    raise "Do not know how to fingerprint #{use_object.class.name}.  Did you mean to add Fingerprintable to this object?" unless fallback_to_string
    raise "#{use_object.class.name} does not respond to to_s.  Did you mean to add Fingerprintable to this object?" unless use_object.respond_to?(:to_s)

    use_object.to_s
  end

  ret = fingerprinter.fingerprint
  cache.merge!(fingerprinter.cache)
  ret
end
next_cache_id() click to toggle source
# File lib/fingerprintable/fingerprinter.rb, line 119
def next_cache_id
  @cache.count
end
populate_attributes(attributes:, ignore:) click to toggle source
# File lib/fingerprintable/fingerprinter.rb, line 123
def populate_attributes(attributes:, ignore:)
  instance_variables = Hash[object.instance_variables.map { |e| [e, 1] }]
  ret = object.class.instance_methods.select do |method|
    instance_variables.key?("@#{method}".to_sym)
  end
  ret |= attributes
  ret -= ignore
  ret.map(&:to_sym).sort
end