Skip to content

Instantly share code, notes, and snippets.

@figgleforth
Last active September 3, 2016 20:47
Show Gist options
  • Save figgleforth/a56de0b9a0b06dfdf6ebed815326abdc to your computer and use it in GitHub Desktop.
Save figgleforth/a56de0b9a0b06dfdf6ebed815326abdc to your computer and use it in GitHub Desktop.
Ruby class extensions for accessing object values by keypath
module Keypathable
## Operators
####
# Non mutating operator
def >(keypath)
if self.is_a?(Array) || self.is_a?(ActiveRecord::Associations::CollectionProxy)
return self.values_for_keypath(keypath)
else
return self.value_for_keypath(keypath)
end
end
# Mutating operator
def <(keypath)
if self.is_a?(Array) || self.is_a?(ActiveRecord::Associations::CollectionProxy)
return self.values_for_keypath!(keypath)
else
warn "WARNING: Cannot mutate #{self.class.name}"
return self
end
end
## Implementation
####
# Returns a copy of the Array mapped to the object returned by calling value_for_keypath on each member
def values_for_keypath(keypath)
unless self.respond_to?(:map)
warn "WARNING: #{self.class.name} doesn't respond to `map`"
return self
end
return self.map{|item| item.value_for_keypath(keypath) }
end
# Returns a mutates self mapped o the object returned by calling value_for_keypath on each member
def values_for_keypath!(keypath)
unless self.respond_to?(:map!)
warn "WARNING: #{self.class.name} doesn't respond to `map!`"
return self
end
return self.map!{|item| item.value_for_keypath(keypath) }
end
# Returns a value (if it exists) at the keypath relative to self
def value_for_keypath(keypath)
return self if keypath.blank?
keys = keypath.split(".")
value = self
if !value.respond_to?(keys.first)
return nil
end
if keys.count > 1
# This is not truly recursive, but it works fine
keys.each do |k|
value = value.value_for_key(k)
break if value.nil?
end
else
return value_for_key(keys.first)
end
return value
end
protected
def value_for_key(key)
if self.is_a?(String)
return nil
elsif self.is_a?(Hash)
return self.dig(key)
elsif self.is_a?(Object)
return self.send(key) if (self.respond_to? key)
return nil
end
end
end
class Object
include Keypathable
end
class Array
include Keypathable
end
class ActiveRecord::Associations::CollectionProxy
include Keypathable
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment