Last active
September 3, 2016 20:47
-
-
Save figgleforth/a56de0b9a0b06dfdf6ebed815326abdc to your computer and use it in GitHub Desktop.
Ruby class extensions for accessing object values by keypath
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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