hashie/lib/hashie/extensions/deep_locate.rb

114 lines
3.7 KiB
Ruby

module Hashie
module Extensions
module DeepLocate
# The module level implementation of #deep_locate, incase you do not want
# to include/extend the base datastructure. For further examples please
# see #deep_locate.
#
# @example
# books = [
# {
# title: "Ruby for beginners",
# pages: 120
# },
# ...
# ]
#
# DeepLocate.deep_locate -> (key, value, object) { key == :title }, books
# # => [{:title=>"Ruby for beginners", :pages=>120}, ...]
def self.deep_locate(comparator, object)
unless comparator.respond_to?(:call)
comparator = _construct_key_comparator(comparator, object)
end
_deep_locate(comparator, object)
end
# Performs a depth-first search on deeply nested data structures for a
# given comparator callable and returns each Enumerable, for which the
# callable returns true for at least one the its elements.
#
# @example
# books = [
# {
# title: "Ruby for beginners",
# pages: 120
# },
# {
# title: "CSS for intermediates",
# pages: 80
# },
# {
# title: "Collection of ruby books",
# books: [
# {
# title: "Ruby for the rest of us",
# pages: 576
# }
# ]
# }
# ]
#
# books.extend(Hashie::Extensions::DeepLocate)
#
# # for ruby 1.9 leave *no* space between the lambda rocket and the braces
# # http://ruby-journal.com/becareful-with-space-in-lambda-hash-rocket-syntax-between-ruby-1-dot-9-and-2-dot-0/
#
# books.deep_locate -> (key, value, object) { key == :title && value.include?("Ruby") }
# # => [{:title=>"Ruby for beginners", :pages=>120},
# # {:title=>"Ruby for the rest of us", :pages=>576}]
#
# books.deep_locate -> (key, value, object) { key == :pages && value <= 120 }
# # => [{:title=>"Ruby for beginners", :pages=>120},
# # {:title=>"CSS for intermediates", :pages=>80}]
def deep_locate(comparator)
Hashie::Extensions::DeepLocate.deep_locate(comparator, self)
end
def self._construct_key_comparator(search_key, object)
if object.respond_to?(:indifferent_access?) && object.indifferent_access? ||
activesupport_indifferent?(object)
search_key = search_key.to_s
end
lambda do |non_callable_object|
->(key, _, _) { key == non_callable_object }
end.call(search_key)
end
private_class_method :_construct_key_comparator
def self._deep_locate(comparator, object, result = [])
if object.is_a?(::Enumerable)
if object.any? { |value| _match_comparator?(value, comparator, object) }
result.push object
end
(object.respond_to?(:values) ? object.values : object.entries).each do |value|
_deep_locate(comparator, value, result)
end
end
result
end
private_class_method :_deep_locate
def self._match_comparator?(value, comparator, object)
if object.is_a?(::Hash)
key, value = value
else
key = nil
end
comparator.call(key, value, object)
end
private_class_method :_match_comparator?
def self.activesupport_indifferent?(object)
defined?(::ActiveSupport::HashWithIndifferentAccess) &&
object.is_a?(::ActiveSupport::HashWithIndifferentAccess)
end
private_class_method :activesupport_indifferent?
end
end
end