1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
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
# },
# ...
# ]
#
# Hashie::Extensions::DeepLocate.deep_locate -> (key, value, object) { key == :title }, books
# # => [{:title=>"Ruby for beginners", :pages=>120}, ...]
def self.deep_locate(comparator, object)
comparator = _construct_key_comparator(comparator, object) unless comparator.respond_to?(:call)
_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)
search_key = search_key.to_s if activesupport_indifferent?(object)
search_key = search_key.to_s if object.respond_to?(:indifferent_access?) && object.indifferent_access?
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)
result.push object if object.any? { |value| _match_comparator?(value, comparator, object) }
(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
|