forked from sunspot/sunspot
/
util.rb
252 lines (234 loc) · 6.69 KB
/
util.rb
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
module Sunspot
#
# The Sunspot::Util module provides utility methods used elsewhere in the
# library.
#
module Util #:nodoc:
class <<self
#
# Get all of the superclasses for a given class, including the class
# itself.
#
# ==== Parameters
#
# clazz<Class>:: class for which to get superclasses
#
# ==== Returns
#
# Array:: Collection containing class and its superclasses
#
def superclasses_for(clazz)
superclasses = [clazz]
superclasses << (clazz = clazz.superclass) while clazz.superclass != Object
superclasses
end
#
# Convert a string to snake case
#
# ==== Parameters
#
# string<String>:: String to convert to snake case
#
# ==== Returns
#
# String:: String in snake case
#
def snake_case(string)
string.scan(/(^|[A-Z])([^A-Z]+)/).map! { |word| word.join.downcase }.join('_')
end
#
# Convert a string to camel case
#
# ==== Parameters
#
# string<String>:: String to convert to camel case
#
# ==== Returns
#
# String:: String in camel case
#
def camel_case(string)
string.split('_').map! { |word| word.capitalize }.join
end
#
# Get a constant from a fully qualified name
#
# ==== Parameters
#
# string<String>:: The fully qualified name of a constant
#
# ==== Returns
#
# Object:: Value of constant named
#
def full_const_get(string)
string.split('::').inject(Object) do |context, const_name|
context.const_defined?(const_name) ? context.const_get(const_name) : context.const_missing(const_name)
end
end
#
# Evaluate the given proc in the context of the given object if the
# block's arity is non-positive, or by passing the given object as an
# argument if it is negative.
#
# ==== Parameters
#
# object<Object>:: Object to pass to the proc
#
def instance_eval_or_call(object, &block)
if block.arity > 0
block.call(object)
else
ContextBoundDelegate.instance_eval_with_context(object, &block)
end
end
def extract_options_from(args)
if args.last.is_a?(Hash)
args.pop
else
{}
end
end
#
# Ruby's treatment of Strings as Enumerables is heavily annoying. As far
# as I know the behavior of Kernel.Array() is otherwise fine.
#
def Array(object)
if object.is_a?(String)
[object]
else
super
end
end
#
# Perform a deep merge of hashes, returning the result as a new hash.
# See #deep_merge_into for rules used to merge the hashes
#
# ==== Parameters
#
# left<Hash>:: Hash to merge
# right<Hash>:: The other hash to merge
#
# ==== Returns
#
# Hash:: New hash containing the given hashes deep-merged.
#
def deep_merge(left, right)
deep_merge_into({}, left, right)
end
#
# Perform a deep merge of the right hash into the left hash
#
# ==== Parameters
#
# left:: Hash to receive merge
# right:: Hash to merge into left
#
# ==== Returns
#
# Hash:: left
#
def deep_merge!(left, right)
deep_merge_into(left, left, right)
end
private
#
# Deep merge two hashes into a third hash, using rules that produce nice
# merged parameter hashes. The rules are as follows, for a given key:
#
# * If only one hash has a value, or if both hashes have the same value,
# just use the value.
# * If either of the values is not a hash, create arrays out of both
# values and concatenate them.
# * Otherwise, deep merge the two values (which are both hashes)
#
# ==== Parameters
#
# destination<Hash>:: Hash into which to perform the merge
# left<Hash>:: One hash to merge
# right<Hash>:: The other hash to merge
#
# ==== Returns
#
# Hash:: destination
#
def deep_merge_into(destination, left, right)
left.each_pair do |name, left_value|
right_value = right[name]
destination[name] =
if right_value.nil? || left_value == right_value
left_value
elsif !left_value.respond_to?(:each_pair) || !right_value.respond_to?(:each_pair)
Array(left_value) + Array(right_value)
else
merged_value = {}
deep_merge_into(merged_value, left_value, right_value)
end
end
left_keys = Set.new(left.keys)
destination.merge!(right.reject { |k, v| left_keys.include?(k) })
destination
end
end
class Coordinates #:nodoc:
def initialize(coords)
@coords = coords
end
def lat
if @coords.respond_to?(:[])
@coords[0]
elsif @coords.respond_to?(:lat)
@coords.lat
else
@coords.latitude
end.to_f
end
def lng
if @coords.respond_to?(:[])
@coords[1]
elsif @coords.respond_to?(:lng)
@coords.lng
elsif @coords.respond_to?(:lon)
@coords.lon
elsif @coords.respond_to?(:long)
@coords.long
elsif @coords.respond_to?(:longitude)
@coords.longitude
end.to_f
end
end
class ContextBoundDelegate
class <<self
def instance_eval_with_context(receiver, &block)
calling_context = eval('self', block.binding)
if parent_calling_context = calling_context.instance_eval{@__calling_context__}
calling_context = parent_calling_context
end
new(receiver, calling_context).instance_eval(&block)
end
private :new
end
BASIC_METHODS = Set[:==, :equal?, :"!", :"!=", :instance_eval,
:object_id, :__send__, :__id__]
instance_methods.each do |method|
unless BASIC_METHODS.include?(method.to_sym)
undef_method(method)
end
end
def initialize(receiver, calling_context)
@__receiver__, @__calling_context__ = receiver, calling_context
end
def method_missing(method, *args, &block)
begin
@__receiver__.send(method.to_sym, *args, &block)
rescue ::NoMethodError => e
begin
@__calling_context__.send(method.to_sym, *args, &block)
rescue ::NoMethodError
raise(e)
end
end
end
end
end
end