rspec-core 3.1.7 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.yardopts +1 -0
- data/Changelog.md +84 -0
- data/README.md +10 -1
- data/lib/rspec/core.rb +28 -8
- data/lib/rspec/core/backport_random.rb +12 -9
- data/lib/rspec/core/configuration.rb +350 -112
- data/lib/rspec/core/configuration_options.rb +14 -7
- data/lib/rspec/core/dsl.rb +7 -4
- data/lib/rspec/core/example.rb +86 -50
- data/lib/rspec/core/example_group.rb +247 -86
- data/lib/rspec/core/filter_manager.rb +38 -93
- data/lib/rspec/core/flat_map.rb +4 -4
- data/lib/rspec/core/formatters.rb +10 -6
- data/lib/rspec/core/formatters/base_formatter.rb +7 -4
- data/lib/rspec/core/formatters/base_text_formatter.rb +12 -12
- data/lib/rspec/core/formatters/console_codes.rb +8 -7
- data/lib/rspec/core/formatters/deprecation_formatter.rb +5 -3
- data/lib/rspec/core/formatters/documentation_formatter.rb +10 -4
- data/lib/rspec/core/formatters/helpers.rb +6 -4
- data/lib/rspec/core/formatters/html_formatter.rb +13 -8
- data/lib/rspec/core/formatters/html_printer.rb +26 -10
- data/lib/rspec/core/formatters/profile_formatter.rb +10 -7
- data/lib/rspec/core/formatters/protocol.rb +27 -18
- data/lib/rspec/core/formatters/snippet_extractor.rb +14 -7
- data/lib/rspec/core/hooks.rb +252 -211
- data/lib/rspec/core/memoized_helpers.rb +16 -16
- data/lib/rspec/core/metadata.rb +67 -28
- data/lib/rspec/core/metadata_filter.rb +151 -24
- data/lib/rspec/core/minitest_assertions_adapter.rb +5 -2
- data/lib/rspec/core/mocking_adapters/flexmock.rb +1 -1
- data/lib/rspec/core/mocking_adapters/mocha.rb +8 -8
- data/lib/rspec/core/notifications.rb +155 -94
- data/lib/rspec/core/option_parser.rb +16 -10
- data/lib/rspec/core/pending.rb +11 -9
- data/lib/rspec/core/project_initializer.rb +1 -1
- data/lib/rspec/core/project_initializer/spec/spec_helper.rb +10 -8
- data/lib/rspec/core/rake_task.rb +37 -52
- data/lib/rspec/core/reporter.rb +30 -7
- data/lib/rspec/core/ruby_project.rb +12 -4
- data/lib/rspec/core/runner.rb +5 -8
- data/lib/rspec/core/sandbox.rb +37 -0
- data/lib/rspec/core/shared_example_group.rb +41 -15
- data/lib/rspec/core/test_unit_assertions_adapter.rb +3 -3
- data/lib/rspec/core/version.rb +1 -1
- data/lib/rspec/core/warnings.rb +2 -2
- data/lib/rspec/core/world.rb +12 -28
- metadata +44 -31
- metadata.gz.sig +0 -0
@@ -20,7 +20,7 @@ module RSpec
|
|
20
20
|
#
|
21
21
|
# @example
|
22
22
|
#
|
23
|
-
# #
|
23
|
+
# # Explicit declaration of subject.
|
24
24
|
# describe Person do
|
25
25
|
# subject { Person.new(:birthdate => 19.years.ago) }
|
26
26
|
# it "should be eligible to vote" do
|
@@ -29,7 +29,7 @@ module RSpec
|
|
29
29
|
# end
|
30
30
|
# end
|
31
31
|
#
|
32
|
-
# #
|
32
|
+
# # Implicit subject => { Person.new }.
|
33
33
|
# describe Person do
|
34
34
|
# it "should be eligible to vote" do
|
35
35
|
# subject.should be_eligible_to_vote
|
@@ -37,17 +37,17 @@ module RSpec
|
|
37
37
|
# end
|
38
38
|
# end
|
39
39
|
#
|
40
|
-
# #
|
40
|
+
# # One-liner syntax - expectation is set on the subject.
|
41
41
|
# describe Person do
|
42
42
|
# it { is_expected.to be_eligible_to_vote }
|
43
43
|
# # or
|
44
44
|
# it { should be_eligible_to_vote }
|
45
45
|
# end
|
46
46
|
#
|
47
|
-
# @note Because `subject` is designed to create state that is reset
|
48
|
-
# each example, and `before(:context)` is designed to setup
|
49
|
-
# shared across _all_ examples in an example group,
|
50
|
-
# intended to be used in a `before(:context)` hook.
|
47
|
+
# @note Because `subject` is designed to create state that is reset
|
48
|
+
# between each example, and `before(:context)` is designed to setup
|
49
|
+
# state that is shared across _all_ examples in an example group,
|
50
|
+
# `subject` is _not_ intended to be used in a `before(:context)` hook.
|
51
51
|
#
|
52
52
|
# @see #should
|
53
53
|
# @see #should_not
|
@@ -211,8 +211,8 @@ EOS
|
|
211
211
|
# though we have yet to see this in practice. You've been warned.
|
212
212
|
#
|
213
213
|
# @note Because `let` is designed to create state that is reset between
|
214
|
-
# each example, and `before(:context)` is designed to setup state that
|
215
|
-
# shared across _all_ examples in an example group, `let` is _not_
|
214
|
+
# each example, and `before(:context)` is designed to setup state that
|
215
|
+
# is shared across _all_ examples in an example group, `let` is _not_
|
216
216
|
# intended to be used in a `before(:context)` hook.
|
217
217
|
#
|
218
218
|
# @example
|
@@ -221,10 +221,10 @@ EOS
|
|
221
221
|
# let(:thing) { Thing.new }
|
222
222
|
#
|
223
223
|
# it "does something" do
|
224
|
-
# #
|
224
|
+
# # First invocation, executes block, memoizes and returns result.
|
225
225
|
# thing.do_something
|
226
226
|
#
|
227
|
-
# #
|
227
|
+
# # Second invocation, returns the memoized value.
|
228
228
|
# thing.should be_something
|
229
229
|
# end
|
230
230
|
# end
|
@@ -302,8 +302,8 @@ EOS
|
|
302
302
|
end
|
303
303
|
|
304
304
|
# Declares a `subject` for an example group which can then be wrapped
|
305
|
-
# with `expect` using `is_expected` to make it the target of an
|
306
|
-
# in a concise, one-line example.
|
305
|
+
# with `expect` using `is_expected` to make it the target of an
|
306
|
+
# expectation in a concise, one-line example.
|
307
307
|
#
|
308
308
|
# Given a `name`, defines a method with that name which returns the
|
309
309
|
# `subject`. This lets you declare the subject once and access it
|
@@ -348,9 +348,9 @@ EOS
|
|
348
348
|
end
|
349
349
|
end
|
350
350
|
|
351
|
-
# Just like `subject`, except the block is invoked by an implicit
|
352
|
-
# hook. This serves a dual purpose of setting up state and
|
353
|
-
# memoized reference to that state.
|
351
|
+
# Just like `subject`, except the block is invoked by an implicit
|
352
|
+
# `before` hook. This serves a dual purpose of setting up state and
|
353
|
+
# providing a memoized reference to that state.
|
354
354
|
#
|
355
355
|
# @example
|
356
356
|
#
|
data/lib/rspec/core/metadata.rb
CHANGED
@@ -25,30 +25,51 @@ module RSpec
|
|
25
25
|
# @see Configuration#filter_run_including
|
26
26
|
# @see Configuration#filter_run_excluding
|
27
27
|
module Metadata
|
28
|
+
# Matches strings either at the beginning of the input or prefixed with a
|
29
|
+
# whitespace, containing the current path, either postfixed with the
|
30
|
+
# separator, or at the end of the string. Match groups are the character
|
31
|
+
# before and the character after the string if any.
|
32
|
+
#
|
33
|
+
# http://rubular.com/r/fT0gmX6VJX
|
34
|
+
# http://rubular.com/r/duOrD4i3wb
|
35
|
+
# http://rubular.com/r/sbAMHFrOx1
|
36
|
+
def self.relative_path_regex
|
37
|
+
@relative_path_regex ||= /(\A|\s)#{File.expand_path('.')}(#{File::SEPARATOR}|\s|\Z)/
|
38
|
+
end
|
39
|
+
|
28
40
|
# @api private
|
29
41
|
#
|
30
42
|
# @param line [String] current code line
|
31
43
|
# @return [String] relative path to line
|
32
44
|
def self.relative_path(line)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
#
|
37
|
-
# http://rubular.com/r/fT0gmX6VJX
|
38
|
-
# http://rubular.com/r/duOrD4i3wb
|
39
|
-
# http://rubular.com/r/sbAMHFrOx1
|
40
|
-
#
|
41
|
-
|
42
|
-
regex = /(\A|\s)#{File.expand_path('.')}(#{File::SEPARATOR}|\s|\Z)/
|
43
|
-
|
44
|
-
line = line.sub(regex, "\\1.\\2")
|
45
|
-
line = line.sub(/\A([^:]+:\d+)$/, '\\1')
|
46
|
-
return nil if line == '-e:1'
|
45
|
+
line = line.sub(relative_path_regex, "\\1.\\2".freeze)
|
46
|
+
line = line.sub(/\A([^:]+:\d+)$/, '\\1'.freeze)
|
47
|
+
return nil if line == '-e:1'.freeze
|
47
48
|
line
|
48
49
|
rescue SecurityError
|
49
50
|
nil
|
50
51
|
end
|
51
52
|
|
53
|
+
# @private
|
54
|
+
# Iteratively walks up from the given metadata through all
|
55
|
+
# example group ancestors, yielding each metadata hash along the way.
|
56
|
+
def self.ascending(metadata)
|
57
|
+
yield metadata
|
58
|
+
return unless (group_metadata = metadata.fetch(:example_group) { metadata[:parent_example_group] })
|
59
|
+
|
60
|
+
loop do
|
61
|
+
yield group_metadata
|
62
|
+
break unless (group_metadata = group_metadata[:parent_example_group])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# @private
|
67
|
+
# Returns an enumerator that iteratively walks up the given metadata through all
|
68
|
+
# example group ancestors, yielding each metadata hash along the way.
|
69
|
+
def self.ascend(metadata)
|
70
|
+
enum_for(:ascending, metadata)
|
71
|
+
end
|
72
|
+
|
52
73
|
# @private
|
53
74
|
# Used internally to build a hash from an args array.
|
54
75
|
# Symbols are converted into hash keys with a value of `true`.
|
@@ -67,6 +88,17 @@ module RSpec
|
|
67
88
|
hash
|
68
89
|
end
|
69
90
|
|
91
|
+
# @private
|
92
|
+
def self.deep_hash_dup(object)
|
93
|
+
return object.dup if Array === object
|
94
|
+
return object unless Hash === object
|
95
|
+
|
96
|
+
object.inject(object.dup) do |duplicate, (key, value)|
|
97
|
+
duplicate[key] = deep_hash_dup(value)
|
98
|
+
duplicate
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
70
102
|
# @private
|
71
103
|
def self.backtrace_from(block)
|
72
104
|
return caller unless block.respond_to?(:source_location)
|
@@ -114,10 +146,11 @@ module RSpec
|
|
114
146
|
file_path_and_line_number_from(caller)
|
115
147
|
end
|
116
148
|
|
117
|
-
|
118
|
-
metadata[:file_path]
|
119
|
-
metadata[:line_number]
|
120
|
-
metadata[:location]
|
149
|
+
relative_file_path = Metadata.relative_path(file_path)
|
150
|
+
metadata[:file_path] = relative_file_path
|
151
|
+
metadata[:line_number] = line_number.to_i
|
152
|
+
metadata[:location] = "#{relative_file_path}:#{line_number}"
|
153
|
+
metadata[:absolute_file_path] = File.expand_path(relative_file_path)
|
121
154
|
end
|
122
155
|
|
123
156
|
def file_path_and_line_number_from(backtrace)
|
@@ -128,16 +161,16 @@ module RSpec
|
|
128
161
|
|
129
162
|
def description_separator(parent_part, child_part)
|
130
163
|
if parent_part.is_a?(Module) && child_part =~ /^(#|::|\.)/
|
131
|
-
''
|
164
|
+
''.freeze
|
132
165
|
else
|
133
|
-
' '
|
166
|
+
' '.freeze
|
134
167
|
end
|
135
168
|
end
|
136
169
|
|
137
170
|
def build_description_from(parent_description=nil, my_description=nil)
|
138
171
|
return parent_description.to_s unless my_description
|
139
172
|
separator = description_separator(parent_description, my_description)
|
140
|
-
parent_description.to_s + separator
|
173
|
+
(parent_description.to_s + separator) << my_description.to_s
|
141
174
|
end
|
142
175
|
|
143
176
|
def ensure_valid_user_keys
|
@@ -171,9 +204,11 @@ module RSpec
|
|
171
204
|
group_metadata.update(example_metadata)
|
172
205
|
|
173
206
|
example_metadata[:example_group] = group_metadata
|
207
|
+
example_metadata[:shared_group_inclusion_backtrace] = SharedExampleGroupInclusionStackFrame.current_backtrace
|
174
208
|
example_metadata.delete(:parent_example_group)
|
175
209
|
|
176
|
-
|
210
|
+
description_args = description.nil? ? [] : [description]
|
211
|
+
hash = new(example_metadata, user_metadata, description_args, block)
|
177
212
|
hash.populate
|
178
213
|
hash.metadata
|
179
214
|
end
|
@@ -215,16 +250,18 @@ module RSpec
|
|
215
250
|
Proc.new do |hash, key|
|
216
251
|
case key
|
217
252
|
when :example_group
|
218
|
-
# We commonly get here when rspec-core is applying a previously
|
219
|
-
# filter rule, such as when a gem configures:
|
253
|
+
# We commonly get here when rspec-core is applying a previously
|
254
|
+
# configured filter rule, such as when a gem configures:
|
220
255
|
#
|
221
256
|
# RSpec.configure do |c|
|
222
257
|
# c.include MyGemHelpers, :example_group => { :file_path => /spec\/my_gem_specs/ }
|
223
258
|
# end
|
224
259
|
#
|
225
|
-
# It's confusing for a user to get a deprecation at this point in
|
226
|
-
# we issue a deprecation from the config APIs
|
227
|
-
#
|
260
|
+
# It's confusing for a user to get a deprecation at this point in
|
261
|
+
# the code, so instead we issue a deprecation from the config APIs
|
262
|
+
# that take a metadata hash, and MetadataFilter sets this thread
|
263
|
+
# local to silence the warning here since it would be so
|
264
|
+
# confusing.
|
228
265
|
unless RSpec.thread_local_metadata[:silence_metadata_example_group_deprecations]
|
229
266
|
RSpec.deprecate("The `:example_group` key in an example group's metadata hash",
|
230
267
|
:replacement => "the example group's hash directly for the " \
|
@@ -275,6 +312,7 @@ module RSpec
|
|
275
312
|
:parent_example_group,
|
276
313
|
:execution_result,
|
277
314
|
:file_path,
|
315
|
+
:absolute_file_path,
|
278
316
|
:full_description,
|
279
317
|
:line_number,
|
280
318
|
:location,
|
@@ -405,7 +443,8 @@ module RSpec
|
|
405
443
|
# `metadata[:example_group][:described_class]` when you use
|
406
444
|
# anonymous controller specs) such that changes are written
|
407
445
|
# back to the top-level metadata hash.
|
408
|
-
# * Exposes the parent group metadata as
|
446
|
+
# * Exposes the parent group metadata as
|
447
|
+
# `[:example_group][:example_group]`.
|
409
448
|
class LegacyExampleGroupHash
|
410
449
|
include HashImitatable
|
411
450
|
|
@@ -8,13 +8,8 @@ module RSpec
|
|
8
8
|
module MetadataFilter
|
9
9
|
class << self
|
10
10
|
# @private
|
11
|
-
def
|
12
|
-
filters.
|
13
|
-
end
|
14
|
-
|
15
|
-
# @private
|
16
|
-
def all_apply?(filters, metadata)
|
17
|
-
filters.all? { |k, v| filter_applies?(k, v, metadata) }
|
11
|
+
def apply?(predicate, filters, metadata)
|
12
|
+
filters.__send__(predicate) { |k, v| filter_applies?(k, v, metadata) }
|
18
13
|
end
|
19
14
|
|
20
15
|
# @private
|
@@ -48,9 +43,8 @@ module RSpec
|
|
48
43
|
end
|
49
44
|
|
50
45
|
def location_filter_applies?(locations, metadata)
|
51
|
-
|
52
|
-
|
53
|
-
line_number ? line_number_filter_applies?(line_number, metadata) : true
|
46
|
+
line_numbers = example_group_declaration_lines(locations, metadata)
|
47
|
+
line_numbers.empty? || line_number_filter_applies?(line_numbers, metadata)
|
54
48
|
end
|
55
49
|
|
56
50
|
def line_number_filter_applies?(line_numbers, metadata)
|
@@ -59,14 +53,13 @@ module RSpec
|
|
59
53
|
end
|
60
54
|
|
61
55
|
def relevant_line_numbers(metadata)
|
62
|
-
|
63
|
-
[metadata[:line_number]].compact + (relevant_line_numbers(parent_of metadata))
|
56
|
+
Metadata.ascend(metadata).map { |meta| meta[:line_number] }
|
64
57
|
end
|
65
58
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
|
59
|
+
def example_group_declaration_lines(locations, metadata)
|
60
|
+
FlatMap.flat_map(Metadata.ascend(metadata)) do |meta|
|
61
|
+
locations[meta[:absolute_file_path]]
|
62
|
+
end.uniq
|
70
63
|
end
|
71
64
|
|
72
65
|
def filters_apply?(key, value, metadata)
|
@@ -75,14 +68,6 @@ module RSpec
|
|
75
68
|
value.all? { |k, v| filter_applies?(k, v, subhash) }
|
76
69
|
end
|
77
70
|
|
78
|
-
def parent_of(metadata)
|
79
|
-
if metadata.key?(:example_group)
|
80
|
-
metadata[:example_group]
|
81
|
-
else
|
82
|
-
metadata[:parent_example_group]
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
71
|
def silence_metadata_example_group_deprecations
|
87
72
|
RSpec.thread_local_metadata[:silence_metadata_example_group_deprecations] = true
|
88
73
|
yield
|
@@ -91,5 +76,147 @@ module RSpec
|
|
91
76
|
end
|
92
77
|
end
|
93
78
|
end
|
79
|
+
|
80
|
+
# Tracks a collection of filterable items (e.g. modules, hooks, etc)
|
81
|
+
# and provides an optimized API to get the applicable items for the
|
82
|
+
# metadata of an example or example group.
|
83
|
+
#
|
84
|
+
# There are two implementations, optimized for different uses.
|
85
|
+
# @private
|
86
|
+
module FilterableItemRepository
|
87
|
+
# This implementation is simple, and is optimized for frequent
|
88
|
+
# updates but rare queries. `append` and `prepend` do no extra
|
89
|
+
# processing, and no internal memoization is done, since this
|
90
|
+
# is not optimized for queries.
|
91
|
+
#
|
92
|
+
# This is ideal for use by a example or example group, which may
|
93
|
+
# be updated multiple times with globally configured hooks, etc,
|
94
|
+
# but will not be queried frequently by other examples or examle
|
95
|
+
# groups.
|
96
|
+
# @private
|
97
|
+
class UpdateOptimized
|
98
|
+
attr_reader :items_and_filters
|
99
|
+
|
100
|
+
def initialize(applies_predicate)
|
101
|
+
@applies_predicate = applies_predicate
|
102
|
+
@items_and_filters = []
|
103
|
+
end
|
104
|
+
|
105
|
+
def append(item, metadata)
|
106
|
+
@items_and_filters << [item, metadata]
|
107
|
+
end
|
108
|
+
|
109
|
+
def prepend(item, metadata)
|
110
|
+
@items_and_filters.unshift [item, metadata]
|
111
|
+
end
|
112
|
+
|
113
|
+
def items_for(request_meta)
|
114
|
+
@items_and_filters.each_with_object([]) do |(item, item_meta), to_return|
|
115
|
+
to_return << item if item_meta.empty? ||
|
116
|
+
MetadataFilter.apply?(@applies_predicate, item_meta, request_meta)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
unless [].respond_to?(:each_with_object) # For 1.8.7
|
121
|
+
undef items_for
|
122
|
+
def items_for(request_meta)
|
123
|
+
@items_and_filters.inject([]) do |to_return, (item, item_meta)|
|
124
|
+
to_return << item if item_meta.empty? ||
|
125
|
+
MetadataFilter.apply?(@applies_predicate, item_meta, request_meta)
|
126
|
+
to_return
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# This implementation is much more complex, and is optimized for
|
133
|
+
# rare (or hopefully no) updates once the queries start. Updates
|
134
|
+
# incur a cost as it has to clear the memoization and keep track
|
135
|
+
# of applicable keys. Queries will be O(N) the first time an item
|
136
|
+
# is provided with a given set of applicable metadata; subsequent
|
137
|
+
# queries with items with the same set of applicable metadata will
|
138
|
+
# be O(1) due to internal memoization.
|
139
|
+
#
|
140
|
+
# This is ideal for use by config, where filterable items (e.g. hooks)
|
141
|
+
# are typically added at the start of the process (e.g. in `spec_helper`)
|
142
|
+
# and then repeatedly queried as example groups and examples are defined.
|
143
|
+
# @private
|
144
|
+
class QueryOptimized < UpdateOptimized
|
145
|
+
alias find_items_for items_for
|
146
|
+
private :find_items_for
|
147
|
+
|
148
|
+
def initialize(applies_predicate)
|
149
|
+
super
|
150
|
+
@applicable_keys = Set.new
|
151
|
+
@proc_keys = Set.new
|
152
|
+
@memoized_lookups = Hash.new do |hash, applicable_metadata|
|
153
|
+
hash[applicable_metadata] = find_items_for(applicable_metadata)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def append(item, metadata)
|
158
|
+
super
|
159
|
+
handle_mutation(metadata)
|
160
|
+
end
|
161
|
+
|
162
|
+
def prepend(item, metadata)
|
163
|
+
super
|
164
|
+
handle_mutation(metadata)
|
165
|
+
end
|
166
|
+
|
167
|
+
def items_for(metadata)
|
168
|
+
# The filtering of `metadata` to `applicable_metadata` is the key thing
|
169
|
+
# that makes the memoization actually useful in practice, since each
|
170
|
+
# example and example group have different metadata (e.g. location and
|
171
|
+
# description). By filtering to the metadata keys our items care about,
|
172
|
+
# we can ignore extra metadata keys that differ for each example/group.
|
173
|
+
# For example, given `config.include DBHelpers, :db`, example groups
|
174
|
+
# can be split into these two sets: those that are tagged with `:db` and those
|
175
|
+
# that are not. For each set, this method for the first group in the set is
|
176
|
+
# still an `O(N)` calculation, but all subsequent groups in the set will be
|
177
|
+
# constant time lookups when they call this method.
|
178
|
+
applicable_metadata = applicable_metadata_from(metadata)
|
179
|
+
|
180
|
+
if applicable_metadata.any? { |k, _| @proc_keys.include?(k) }
|
181
|
+
# It's unsafe to memoize lookups involving procs (since they can
|
182
|
+
# be non-deterministic), so we skip the memoization in this case.
|
183
|
+
find_items_for(applicable_metadata)
|
184
|
+
else
|
185
|
+
@memoized_lookups[applicable_metadata]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
191
|
+
def handle_mutation(metadata)
|
192
|
+
@applicable_keys.merge(metadata.keys)
|
193
|
+
@proc_keys.merge(proc_keys_from metadata)
|
194
|
+
@memoized_lookups.clear
|
195
|
+
end
|
196
|
+
|
197
|
+
def applicable_metadata_from(metadata)
|
198
|
+
@applicable_keys.inject({}) do |hash, key|
|
199
|
+
hash[key] = metadata[key] if metadata.key?(key)
|
200
|
+
hash
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def proc_keys_from(metadata)
|
205
|
+
metadata.each_with_object([]) do |(key, value), to_return|
|
206
|
+
to_return << key if Proc === value
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
unless [].respond_to?(:each_with_object) # For 1.8.7
|
211
|
+
undef proc_keys_from
|
212
|
+
def proc_keys_from(metadata)
|
213
|
+
metadata.inject([]) do |to_return, (key, value)|
|
214
|
+
to_return << key if Proc === value
|
215
|
+
to_return
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
94
221
|
end
|
95
222
|
end
|