114 lines
3.7 KiB
Ruby
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
|