mongo 0.18.3 → 0.19
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +41 -50
- data/Rakefile +14 -4
- data/examples/gridfs.rb +25 -70
- data/lib/mongo.rb +4 -2
- data/lib/mongo/collection.rb +70 -89
- data/lib/mongo/connection.rb +203 -43
- data/lib/mongo/cursor.rb +7 -7
- data/lib/mongo/db.rb +61 -18
- data/lib/mongo/exceptions.rb +7 -1
- data/lib/mongo/gridfs.rb +8 -1
- data/lib/mongo/gridfs/chunk.rb +2 -1
- data/lib/mongo/gridfs/grid.rb +90 -0
- data/lib/mongo/gridfs/grid_file_system.rb +113 -0
- data/lib/mongo/gridfs/grid_io.rb +339 -0
- data/lib/mongo/gridfs/grid_store.rb +43 -18
- data/lib/mongo/types/binary.rb +5 -1
- data/lib/mongo/types/code.rb +1 -1
- data/lib/mongo/types/dbref.rb +3 -1
- data/lib/mongo/types/min_max_keys.rb +1 -1
- data/lib/mongo/types/objectid.rb +16 -55
- data/lib/mongo/types/regexp_of_holding.rb +1 -1
- data/lib/mongo/util/bson_c.rb +2 -2
- data/lib/mongo/util/bson_ruby.rb +22 -11
- data/lib/mongo/util/byte_buffer.rb +1 -1
- data/lib/mongo/util/conversions.rb +1 -1
- data/lib/mongo/util/ordered_hash.rb +6 -1
- data/lib/mongo/util/server_version.rb +1 -1
- data/lib/mongo/util/support.rb +1 -1
- data/mongo-ruby-driver.gemspec +1 -1
- data/test/auxillary/authentication_test.rb +68 -0
- data/test/auxillary/autoreconnect_test.rb +41 -0
- data/test/binary_test.rb +15 -0
- data/test/{test_bson.rb → bson_test.rb} +63 -6
- data/test/{test_byte_buffer.rb → byte_buffer_test.rb} +0 -0
- data/test/{test_chunk.rb → chunk_test.rb} +0 -0
- data/test/{test_collection.rb → collection_test.rb} +35 -39
- data/test/{test_connection.rb → connection_test.rb} +33 -3
- data/test/{test_conversions.rb → conversions_test.rb} +0 -0
- data/test/{test_cursor.rb → cursor_test.rb} +0 -7
- data/test/{test_db_api.rb → db_api_test.rb} +3 -6
- data/test/{test_db_connection.rb → db_connection_test.rb} +0 -0
- data/test/{test_db.rb → db_test.rb} +33 -15
- data/test/grid_file_system_test.rb +210 -0
- data/test/grid_io_test.rb +78 -0
- data/test/{test_grid_store.rb → grid_store_test.rb} +33 -2
- data/test/grid_test.rb +87 -0
- data/test/{test_objectid.rb → objectid_test.rb} +2 -33
- data/test/{test_ordered_hash.rb → ordered_hash_test.rb} +4 -0
- data/test/{test_slave_connection.rb → slave_connection_test.rb} +0 -0
- data/test/test_helper.rb +2 -2
- data/test/{test_threading.rb → threading_test.rb} +0 -0
- data/test/unit/collection_test.rb +12 -3
- data/test/unit/connection_test.rb +85 -24
- data/test/unit/cursor_test.rb +1 -2
- data/test/unit/db_test.rb +70 -69
- metadata +27 -23
- data/bin/objectid_benchmark.rb +0 -23
- data/bin/perf.rb +0 -30
- data/lib/mongo/admin.rb +0 -95
- data/lib/mongo/util/xml_to_ruby.rb +0 -112
- data/test/test_admin.rb +0 -67
- data/test/test_round_trip.rb +0 -114
@@ -1,5 +1,5 @@
|
|
1
1
|
# --
|
2
|
-
# Copyright (C) 2008-
|
2
|
+
# Copyright (C) 2008-2010 10gen Inc.
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -46,12 +46,16 @@ module GridFS
|
|
46
46
|
# GridStore.open(database, 'filename', 'r') do |f|
|
47
47
|
# puts f.read
|
48
48
|
# end
|
49
|
+
#
|
50
|
+
# @deprecated
|
49
51
|
class GridStore
|
52
|
+
include Enumerable
|
50
53
|
|
51
54
|
DEFAULT_ROOT_COLLECTION = 'fs'
|
55
|
+
|
52
56
|
DEFAULT_CONTENT_TYPE = 'text/plain'
|
53
57
|
|
54
|
-
|
58
|
+
DEPRECATION_WARNING = "GridFS::GridStore is deprecated. Use either Grid or GridFileSystem."
|
55
59
|
|
56
60
|
attr_accessor :filename
|
57
61
|
|
@@ -77,6 +81,14 @@ module GridFS
|
|
77
81
|
|
78
82
|
attr_reader :md5
|
79
83
|
|
84
|
+
def self.default_root_collection
|
85
|
+
@@default_root_collection ||= DEFAULT_ROOT_COLLECTION
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.default_root_collection=(name)
|
89
|
+
@@default_root_collection = name
|
90
|
+
end
|
91
|
+
|
80
92
|
# Determine whether a given file exists in the GridStore.
|
81
93
|
#
|
82
94
|
# @param [Mongo::DB] a MongoDB database.
|
@@ -84,7 +96,9 @@ module GridFS
|
|
84
96
|
# @param [String] root_collection the name of the gridfs root collection.
|
85
97
|
#
|
86
98
|
# @return [Boolean]
|
87
|
-
|
99
|
+
# @deprecated
|
100
|
+
def self.exist?(db, name, root_collection=GridStore.default_root_collection)
|
101
|
+
warn DEPRECATION_WARNING
|
88
102
|
db.collection("#{root_collection}.files").find({'filename' => name}).next_document != nil
|
89
103
|
end
|
90
104
|
|
@@ -93,13 +107,14 @@ module GridFS
|
|
93
107
|
#
|
94
108
|
# @param [Mongo::DB] a MongoDB database.
|
95
109
|
# @param [String] name the filename.
|
96
|
-
# @param [String] mode one of 'r', 'w', or 'w+' for reading, writing,
|
110
|
+
# @param [String] mode one of 'r', 'w', or 'w+' for reading, writing,
|
97
111
|
# and appending, respectively.
|
98
|
-
# @param [Hash] options any of the options available on
|
112
|
+
# @param [Hash] options any of the options available on
|
99
113
|
# GridStore initialization.
|
100
114
|
#
|
101
115
|
# @see GridStore#initialize.
|
102
116
|
# @see The various GridStore class methods, e.g., GridStore.open, GridStore.read etc.
|
117
|
+
# @deprecated
|
103
118
|
def self.open(db, name, mode, options={})
|
104
119
|
gs = self.new(db, name, mode, options)
|
105
120
|
result = nil
|
@@ -120,6 +135,7 @@ module GridFS
|
|
120
135
|
# beginning of the file to start reading.
|
121
136
|
#
|
122
137
|
# @return [String] the file data
|
138
|
+
# @deprecated
|
123
139
|
def self.read(db, name, length=nil, offset=nil)
|
124
140
|
GridStore.open(db, name, 'r') do |gs|
|
125
141
|
gs.seek(offset) if offset
|
@@ -134,13 +150,15 @@ module GridFS
|
|
134
150
|
# @param [String] root_collection the name of the root collection.
|
135
151
|
#
|
136
152
|
# @return [Array]
|
137
|
-
|
153
|
+
# @deprecated
|
154
|
+
def self.list(db, root_collection=GridStore.default_root_collection)
|
155
|
+
warn DEPRECATION_WARNING
|
138
156
|
db.collection("#{root_collection}.files").find().map do |f|
|
139
157
|
f['filename']
|
140
158
|
end
|
141
159
|
end
|
142
160
|
|
143
|
-
# Get each line of data from the specified file
|
161
|
+
# Get each line of data from the specified file
|
144
162
|
# as an array of strings.
|
145
163
|
#
|
146
164
|
# @param [Mongo::DB] db a MongoDB database.
|
@@ -148,6 +166,7 @@ module GridFS
|
|
148
166
|
# @param [String, Reg] separator
|
149
167
|
#
|
150
168
|
# @return [Array]
|
169
|
+
# @deprecated
|
151
170
|
def self.readlines(db, name, separator=$/)
|
152
171
|
GridStore.open(db, name, 'r') do |gs|
|
153
172
|
gs.readlines(separator)
|
@@ -160,10 +179,11 @@ module GridFS
|
|
160
179
|
# @param [Array<String>] names the filenames to remove
|
161
180
|
#
|
162
181
|
# @return [True]
|
182
|
+
# @deprecated
|
163
183
|
def self.unlink(db, *names)
|
164
184
|
names.each do |name|
|
165
185
|
gs = GridStore.new(db, name)
|
166
|
-
gs.
|
186
|
+
gs.delete_chunks
|
167
187
|
gs.collection.remove('_id' => gs.files_id)
|
168
188
|
end
|
169
189
|
end
|
@@ -172,13 +192,16 @@ module GridFS
|
|
172
192
|
end
|
173
193
|
|
174
194
|
# Rename a file in this collection. Note that this method uses
|
175
|
-
# Collection#update, which means that you will not be notified
|
195
|
+
# Collection#update, which means that you will not be notified of the
|
196
|
+
# success of the operation.
|
176
197
|
#
|
177
198
|
# @param [Mongo::DB] a MongoDB database.
|
178
199
|
# @param [String] src the name of the source file.
|
179
200
|
# @param [String] dest the name of the destination file.
|
180
201
|
# @param [String] root_collection the name of the default root collection.
|
181
|
-
|
202
|
+
# @deprecated
|
203
|
+
def self.mv(db, src, dest, root_collection=GridStore.default_root_collection)
|
204
|
+
warn DEPRECATION_WARNING
|
182
205
|
db.collection("#{root_collection}.files").update({ :filename => src }, { '$set' => { :filename => dest } })
|
183
206
|
end
|
184
207
|
|
@@ -197,11 +220,13 @@ module GridFS
|
|
197
220
|
# @option options [Integer] :chunk_size (Chunk::DEFAULT_CHUNK_SIZE) (w) Sets chunk size for files opened for writing.
|
198
221
|
# See also GridStore#chunk_size=.
|
199
222
|
#
|
200
|
-
# @option options [String] :content_type ('text/plain') Set the content type stored as the
|
223
|
+
# @option options [String] :content_type ('text/plain') Set the content type stored as the
|
201
224
|
# file's metadata. See also GridStore#content_type=.
|
225
|
+
# @deprecated
|
202
226
|
def initialize(db, name, mode='r', options={})
|
227
|
+
warn DEPRECATION_WARNING
|
203
228
|
@db, @filename, @mode = db, name, mode
|
204
|
-
@root = options[:root] ||
|
229
|
+
@root = options[:root] || GridStore.default_root_collection
|
205
230
|
|
206
231
|
doc = collection.find({'filename' => @filename}).next_document
|
207
232
|
if doc
|
@@ -488,6 +513,11 @@ module GridFS
|
|
488
513
|
@db == nil
|
489
514
|
end
|
490
515
|
|
516
|
+
def delete_chunks
|
517
|
+
chunk_collection.remove({'files_id' => @files_id}) if @files_id
|
518
|
+
@curr_chunk = nil
|
519
|
+
end
|
520
|
+
|
491
521
|
#---
|
492
522
|
# ================ protected ================
|
493
523
|
#+++
|
@@ -537,12 +567,7 @@ module GridFS
|
|
537
567
|
buf
|
538
568
|
end
|
539
569
|
|
540
|
-
|
541
|
-
chunk_collection.remove({'files_id' => @files_id}) if @files_id
|
542
|
-
@curr_chunk = nil
|
543
|
-
end
|
544
|
-
|
545
|
-
def nth_chunk(n)
|
570
|
+
def nth_chunk(n)
|
546
571
|
mongo_chunk = chunk_collection.find({'files_id' => @files_id, 'n' => n}).next_document
|
547
572
|
Chunk.new(self, mongo_chunk || {})
|
548
573
|
end
|
data/lib/mongo/types/binary.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# --
|
2
|
-
# Copyright (C) 2008-
|
2
|
+
# Copyright (C) 2008-2010 10gen Inc.
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -44,5 +44,9 @@ module Mongo
|
|
44
44
|
@subtype = subtype
|
45
45
|
end
|
46
46
|
|
47
|
+
def inspect
|
48
|
+
"<Mongo::Binary:#{object_id}>"
|
49
|
+
end
|
50
|
+
|
47
51
|
end
|
48
52
|
end
|
data/lib/mongo/types/code.rb
CHANGED
data/lib/mongo/types/dbref.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# --
|
2
|
-
# Copyright (C) 2008-
|
2
|
+
# Copyright (C) 2008-2010 10gen Inc.
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -25,6 +25,8 @@ module Mongo
|
|
25
25
|
#
|
26
26
|
# @param [String] a collection name
|
27
27
|
# @param [ObjectID] an object id
|
28
|
+
#
|
29
|
+
# @core dbrefs constructor_details
|
28
30
|
def initialize(namespace, object_id)
|
29
31
|
@namespace = namespace
|
30
32
|
@object_id = object_id
|
data/lib/mongo/types/objectid.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# --
|
2
|
-
# Copyright (C) 2008-
|
2
|
+
# Copyright (C) 2008-2010 10gen Inc.
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -20,21 +20,15 @@ require 'digest/md5'
|
|
20
20
|
|
21
21
|
module Mongo
|
22
22
|
|
23
|
-
#
|
23
|
+
# Generates MongoDB object ids.
|
24
|
+
#
|
25
|
+
# @core objectids
|
24
26
|
class ObjectID
|
25
|
-
# This is the legacy byte ordering for Babble. Versions of the Ruby
|
26
|
-
# driver prior to 0.14 used this byte ordering when converting ObjectID
|
27
|
-
# instances to and from strings. If you have string representations of
|
28
|
-
# ObjectIDs using the legacy byte ordering make sure to use the
|
29
|
-
# to_s_legacy and from_string_legacy methods, or convert your strings
|
30
|
-
# with ObjectID#legacy_string_convert
|
31
|
-
BYTE_ORDER = [7, 6, 5, 4, 3, 2, 1, 0, 11, 10, 9, 8]
|
32
|
-
|
33
27
|
@@lock = Mutex.new
|
34
28
|
@@index = 0
|
35
29
|
|
36
30
|
# Create a new object id. If no parameter is given, an id corresponding
|
37
|
-
# to the ObjectID BSON data type will be created. This is a 12-byte value
|
31
|
+
# to the ObjectID BSON data type will be created. This is a 12-byte value
|
38
32
|
# consisting of a 4-byte timestamp, a 3-byte machine id, a 2-byte process id,
|
39
33
|
# and a 3-byte counter.
|
40
34
|
#
|
@@ -44,8 +38,14 @@ module Mongo
|
|
44
38
|
@data = data || generate
|
45
39
|
end
|
46
40
|
|
41
|
+
# Determine if the supplied string is legal. Legal strings will
|
42
|
+
# consist of 24 hexadecimal characters.
|
43
|
+
#
|
44
|
+
# @param [String] str
|
45
|
+
#
|
46
|
+
# @return [Boolean]
|
47
47
|
def self.legal?(str)
|
48
|
-
len =
|
48
|
+
len = 24
|
49
49
|
str =~ /([0-9a-f]+)/i
|
50
50
|
match = $1
|
51
51
|
str && str.length == len && match == str
|
@@ -115,21 +115,6 @@ module Mongo
|
|
115
115
|
self.new(data)
|
116
116
|
end
|
117
117
|
|
118
|
-
# @deprecated
|
119
|
-
# Create a new ObjectID given a string representation of an ObjectID
|
120
|
-
# using the legacy byte ordering. This method may eventually be
|
121
|
-
# removed. If you are not sure that you need this method you should be
|
122
|
-
# using the regular from_string.
|
123
|
-
def self.from_string_legacy(str)
|
124
|
-
warn "Support for legacy object ids has been DEPRECATED."
|
125
|
-
raise InvalidObjectID, "illegal ObjectID format" unless legal?(str)
|
126
|
-
data = []
|
127
|
-
BYTE_ORDER.each_with_index { |string_position, data_index|
|
128
|
-
data[data_index] = str[string_position * 2, 2].to_i(16)
|
129
|
-
}
|
130
|
-
self.new(data)
|
131
|
-
end
|
132
|
-
|
133
118
|
# Get a string representation of this object id.
|
134
119
|
#
|
135
120
|
# @return [String]
|
@@ -140,7 +125,10 @@ module Mongo
|
|
140
125
|
end
|
141
126
|
str
|
142
127
|
end
|
143
|
-
|
128
|
+
|
129
|
+
def inspect
|
130
|
+
"ObjectID('#{to_s}')"
|
131
|
+
end
|
144
132
|
|
145
133
|
# Convert to MongoDB extended JSON format. Since JSON includes type information,
|
146
134
|
# but lacks an ObjectID type, this JSON format encodes the type using an $id key.
|
@@ -150,33 +138,6 @@ module Mongo
|
|
150
138
|
"{\"$oid\": \"#{to_s}\"}"
|
151
139
|
end
|
152
140
|
|
153
|
-
# @deprecated
|
154
|
-
# Get a string representation of this ObjectID using the legacy byte
|
155
|
-
# ordering. This method may eventually be removed. If you are not sure
|
156
|
-
# that you need this method you should be using the regular to_s.
|
157
|
-
def to_s_legacy
|
158
|
-
warn "Support for legacy object ids has been DEPRECATED."
|
159
|
-
str = ' ' * 24
|
160
|
-
BYTE_ORDER.each_with_index { |string_position, data_index|
|
161
|
-
str[string_position * 2, 2] = '%02x' % @data[data_index]
|
162
|
-
}
|
163
|
-
str
|
164
|
-
end
|
165
|
-
|
166
|
-
# @deprecated
|
167
|
-
# Convert a string representation of an ObjectID using the legacy byte
|
168
|
-
# ordering to the proper byte ordering. This method may eventually be
|
169
|
-
# removed. If you are not sure that you need this method it is probably
|
170
|
-
# unnecessary.
|
171
|
-
def self.legacy_string_convert(str)
|
172
|
-
warn "Support for legacy object ids has been DEPRECATED."
|
173
|
-
legacy = ' ' * 24
|
174
|
-
BYTE_ORDER.each_with_index do |legacy_pos, pos|
|
175
|
-
legacy[legacy_pos * 2, 2] = str[pos * 2, 2]
|
176
|
-
end
|
177
|
-
legacy
|
178
|
-
end
|
179
|
-
|
180
141
|
# Return the UTC time at which this ObjectID was generated. This may
|
181
142
|
# be used in lieu of a created_at timestamp since this information
|
182
143
|
# is always encoded in the object id.
|
data/lib/mongo/util/bson_c.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# A thin wrapper for the CBson class
|
2
2
|
class BSON_C
|
3
3
|
|
4
|
-
def self.serialize(obj, check_keys=false)
|
5
|
-
ByteBuffer.new(CBson.serialize(obj, check_keys))
|
4
|
+
def self.serialize(obj, check_keys=false, move_id=false)
|
5
|
+
ByteBuffer.new(CBson.serialize(obj, check_keys, move_id))
|
6
6
|
end
|
7
7
|
|
8
8
|
def self.deserialize(buf=nil)
|
data/lib/mongo/util/bson_ruby.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# --
|
2
|
-
# Copyright (C) 2008-
|
2
|
+
# Copyright (C) 2008-2010 10gen Inc.
|
3
3
|
#
|
4
4
|
# This program is free software: you can redistribute it and/or modify it
|
5
5
|
# under the terms of the GNU Affero General Public License, version 3, as
|
@@ -87,15 +87,15 @@ class BSON_RUBY
|
|
87
87
|
|
88
88
|
# Serializes an object.
|
89
89
|
# Implemented to ensure an API compatible with BSON extension.
|
90
|
-
def self.serialize(obj, check_keys=false)
|
91
|
-
new.serialize(obj, check_keys)
|
90
|
+
def self.serialize(obj, check_keys=false, move_id=false)
|
91
|
+
new.serialize(obj, check_keys, move_id)
|
92
92
|
end
|
93
93
|
|
94
94
|
def self.deserialize(buf=nil)
|
95
95
|
new.deserialize(buf)
|
96
96
|
end
|
97
97
|
|
98
|
-
def serialize(obj, check_keys=false)
|
98
|
+
def serialize(obj, check_keys=false, move_id=false)
|
99
99
|
raise "Document is null" unless obj
|
100
100
|
|
101
101
|
@buf.rewind
|
@@ -103,14 +103,20 @@ class BSON_RUBY
|
|
103
103
|
@buf.put_int(0)
|
104
104
|
|
105
105
|
# Write key/value pairs. Always write _id first if it exists.
|
106
|
-
if
|
107
|
-
|
108
|
-
|
109
|
-
|
106
|
+
if move_id
|
107
|
+
if obj.has_key? '_id'
|
108
|
+
serialize_key_value('_id', obj['_id'], false)
|
109
|
+
elsif obj.has_key? :_id
|
110
|
+
serialize_key_value('_id', obj[:_id], false)
|
111
|
+
end
|
112
|
+
obj.each {|k, v| serialize_key_value(k, v, check_keys) unless k == '_id' || k == :_id }
|
113
|
+
else
|
114
|
+
if obj.has_key?('_id') && obj.has_key?(:_id)
|
115
|
+
obj['_id'] = obj.delete(:_id)
|
116
|
+
end
|
117
|
+
obj.each {|k, v| serialize_key_value(k, v, check_keys) }
|
110
118
|
end
|
111
119
|
|
112
|
-
obj.each {|k, v| serialize_key_value(k, v, check_keys) unless k == '_id' || k == :_id }
|
113
|
-
|
114
120
|
serialize_eoo_element(@buf)
|
115
121
|
if @buf.size > 4 * 1024 * 1024
|
116
122
|
raise InvalidDocument, "Document is too large (#{@buf.size}). BSON documents are limited to 4MB (#{4 * 1024 * 1024})."
|
@@ -323,7 +329,12 @@ class BSON_RUBY
|
|
323
329
|
options |= Regexp::MULTILINE if options_str.include?('m')
|
324
330
|
options |= Regexp::EXTENDED if options_str.include?('x')
|
325
331
|
options_str.gsub!(/[imx]/, '') # Now remove the three we understand
|
326
|
-
|
332
|
+
if options_str == ''
|
333
|
+
Regexp.new(str, options)
|
334
|
+
else
|
335
|
+
warn("Using deprecated Regexp options #{options_str}; future versions of this MongoDB driver will support only i, m, and x. See deprecated class RegexpOfHolding for more info.")
|
336
|
+
RegexpOfHolding.new(str, options, options_str)
|
337
|
+
end
|
327
338
|
end
|
328
339
|
|
329
340
|
def deserialize_string_data(buf)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# --
|
2
|
-
# Copyright (C) 2008-
|
2
|
+
# Copyright (C) 2008-2010 10gen Inc.
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -71,6 +71,11 @@ class OrderedHash < Hash
|
|
71
71
|
end
|
72
72
|
alias :each_pair :each
|
73
73
|
|
74
|
+
def to_a
|
75
|
+
@ordered_keys ||= []
|
76
|
+
@ordered_keys.map { |k| [k, self[k]] }
|
77
|
+
end
|
78
|
+
|
74
79
|
def values
|
75
80
|
collect { |k, v| v }
|
76
81
|
end
|