pg 0.18.4 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/BSDL +2 -2
- data/ChangeLog +0 -5911
- data/History.rdoc +240 -0
- data/Manifest.txt +8 -20
- data/README-Windows.rdoc +4 -4
- data/README.ja.rdoc +1 -2
- data/README.rdoc +64 -15
- data/Rakefile +20 -21
- data/Rakefile.cross +67 -69
- data/ext/errorcodes.def +101 -0
- data/ext/errorcodes.rb +1 -1
- data/ext/errorcodes.txt +33 -2
- data/ext/extconf.rb +26 -36
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +27 -39
- data/ext/pg.c +156 -145
- data/ext/pg.h +74 -98
- data/ext/pg_binary_decoder.c +82 -15
- data/ext/pg_binary_encoder.c +20 -19
- data/ext/pg_coder.c +103 -21
- data/ext/pg_connection.c +917 -523
- data/ext/pg_copy_coder.c +50 -12
- data/ext/pg_record_coder.c +491 -0
- data/ext/pg_result.c +590 -208
- data/ext/pg_text_decoder.c +606 -40
- data/ext/pg_text_encoder.c +245 -94
- data/ext/pg_tuple.c +549 -0
- data/ext/pg_type_map.c +14 -7
- data/ext/pg_type_map_all_strings.c +4 -4
- data/ext/pg_type_map_by_class.c +9 -4
- data/ext/pg_type_map_by_column.c +7 -6
- data/ext/pg_type_map_by_mri_type.c +1 -1
- data/ext/pg_type_map_by_oid.c +3 -2
- data/ext/pg_type_map_in_ruby.c +1 -1
- data/ext/{util.c → pg_util.c} +10 -10
- data/ext/{util.h → pg_util.h} +2 -2
- data/lib/pg.rb +23 -13
- data/lib/pg/basic_type_mapping.rb +155 -32
- data/lib/pg/binary_decoder.rb +23 -0
- data/lib/pg/coder.rb +23 -2
- data/lib/pg/connection.rb +73 -13
- data/lib/pg/constants.rb +2 -1
- data/lib/pg/exceptions.rb +2 -1
- data/lib/pg/result.rb +24 -7
- data/lib/pg/text_decoder.rb +24 -22
- data/lib/pg/text_encoder.rb +40 -8
- data/lib/pg/tuple.rb +30 -0
- data/lib/pg/type_map_by_column.rb +3 -2
- data/spec/helpers.rb +61 -36
- data/spec/pg/basic_type_mapping_spec.rb +415 -36
- data/spec/pg/connection_spec.rb +732 -327
- data/spec/pg/connection_sync_spec.rb +41 -0
- data/spec/pg/result_spec.rb +253 -21
- data/spec/pg/tuple_spec.rb +333 -0
- data/spec/pg/type_map_by_class_spec.rb +4 -4
- data/spec/pg/type_map_by_column_spec.rb +6 -2
- data/spec/pg/type_map_by_mri_type_spec.rb +2 -2
- data/spec/pg/type_map_by_oid_spec.rb +3 -3
- data/spec/pg/type_map_in_ruby_spec.rb +1 -1
- data/spec/pg/type_map_spec.rb +1 -1
- data/spec/pg/type_spec.rb +446 -20
- data/spec/pg_spec.rb +2 -2
- metadata +63 -72
- metadata.gz.sig +0 -0
- data/sample/array_insert.rb +0 -20
- data/sample/async_api.rb +0 -106
- data/sample/async_copyto.rb +0 -39
- data/sample/async_mixed.rb +0 -56
- data/sample/check_conn.rb +0 -21
- data/sample/copyfrom.rb +0 -81
- data/sample/copyto.rb +0 -19
- data/sample/cursor.rb +0 -21
- data/sample/disk_usage_report.rb +0 -186
- data/sample/issue-119.rb +0 -94
- data/sample/losample.rb +0 -69
- data/sample/minimal-testcase.rb +0 -17
- data/sample/notify_wait.rb +0 -72
- data/sample/pg_statistics.rb +0 -294
- data/sample/replication_monitor.rb +0 -231
- data/sample/test_binary_values.rb +0 -33
- data/sample/wal_shipper.rb +0 -434
- data/sample/warehouse_partitions.rb +0 -320
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module PG
|
5
|
+
module BinaryDecoder
|
6
|
+
# Convenience classes for timezone options
|
7
|
+
class TimestampUtc < Timestamp
|
8
|
+
def initialize(params={})
|
9
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
class TimestampUtcToLocal < Timestamp
|
13
|
+
def initialize(params={})
|
14
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
class TimestampLocal < Timestamp
|
18
|
+
def initialize(params={})
|
19
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end # module PG
|
data/lib/pg/coder.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module PG
|
4
5
|
|
@@ -28,6 +29,7 @@ module PG
|
|
28
29
|
{
|
29
30
|
oid: oid,
|
30
31
|
format: format,
|
32
|
+
flags: flags,
|
31
33
|
name: name,
|
32
34
|
}
|
33
35
|
end
|
@@ -52,6 +54,18 @@ module PG
|
|
52
54
|
str[-1,0] = "#{name_str} #{oid_str}#{format_str}"
|
53
55
|
str
|
54
56
|
end
|
57
|
+
|
58
|
+
def inspect_short
|
59
|
+
str = case format
|
60
|
+
when 0 then "T"
|
61
|
+
when 1 then "B"
|
62
|
+
else format.to_s
|
63
|
+
end
|
64
|
+
str += "E" if respond_to?(:encode)
|
65
|
+
str += "D" if respond_to?(:decode)
|
66
|
+
|
67
|
+
"#{name || self.class.name}:#{str}"
|
68
|
+
end
|
55
69
|
end
|
56
70
|
|
57
71
|
class CompositeCoder < Coder
|
@@ -79,5 +93,12 @@ module PG
|
|
79
93
|
})
|
80
94
|
end
|
81
95
|
end
|
82
|
-
end # module PG
|
83
96
|
|
97
|
+
class RecordCoder < Coder
|
98
|
+
def to_h
|
99
|
+
super.merge!({
|
100
|
+
type_map: type_map,
|
101
|
+
})
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end # module PG
|
data/lib/pg/connection.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'pg' unless defined?( PG )
|
4
5
|
require 'uri'
|
@@ -47,7 +48,7 @@ class PG::Connection
|
|
47
48
|
|
48
49
|
if args.length == 1
|
49
50
|
case args.first
|
50
|
-
when URI, URI
|
51
|
+
when URI, /\A#{URI::ABS_URI_REF}\z/
|
51
52
|
uri = URI(args.first)
|
52
53
|
options.merge!( Hash[URI.decode_www_form( uri.query )] ) if uri.query
|
53
54
|
when /=/
|
@@ -85,7 +86,7 @@ class PG::Connection
|
|
85
86
|
|
86
87
|
|
87
88
|
# call-seq:
|
88
|
-
# conn.copy_data( sql ) {|sql_result| ... } -> PG::Result
|
89
|
+
# conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
|
89
90
|
#
|
90
91
|
# Execute a copy process for transfering data to or from the server.
|
91
92
|
#
|
@@ -109,13 +110,26 @@ class PG::Connection
|
|
109
110
|
# of blocking mode of operation, #copy_data is preferred to raw calls
|
110
111
|
# of #put_copy_data, #get_copy_data and #put_copy_end.
|
111
112
|
#
|
113
|
+
# _coder_ can be a PG::Coder derivation
|
114
|
+
# (typically PG::TextEncoder::CopyRow or PG::TextDecoder::CopyRow).
|
115
|
+
# This enables encoding of data fields given to #put_copy_data
|
116
|
+
# or decoding of fields received by #get_copy_data.
|
117
|
+
#
|
112
118
|
# Example with CSV input format:
|
113
|
-
# conn.exec "create table my_table (a text,b text,c text,d text
|
119
|
+
# conn.exec "create table my_table (a text,b text,c text,d text)"
|
114
120
|
# conn.copy_data "COPY my_table FROM STDIN CSV" do
|
115
|
-
# conn.put_copy_data "some,
|
116
|
-
# conn.put_copy_data "more,
|
121
|
+
# conn.put_copy_data "some,data,to,copy\n"
|
122
|
+
# conn.put_copy_data "more,data,to,copy\n"
|
123
|
+
# end
|
124
|
+
# This creates +my_table+ and inserts two CSV rows.
|
125
|
+
#
|
126
|
+
# The same with text format encoder PG::TextEncoder::CopyRow
|
127
|
+
# and Array input:
|
128
|
+
# enco = PG::TextEncoder::CopyRow.new
|
129
|
+
# conn.copy_data "COPY my_table FROM STDIN", enco do
|
130
|
+
# conn.put_copy_data ['some', 'data', 'to', 'copy']
|
131
|
+
# conn.put_copy_data ['more', 'data', 'to', 'copy']
|
117
132
|
# end
|
118
|
-
# This creates +my_table+ and inserts two rows.
|
119
133
|
#
|
120
134
|
# Example with CSV output format:
|
121
135
|
# conn.copy_data "COPY my_table TO STDOUT CSV" do
|
@@ -124,8 +138,21 @@ class PG::Connection
|
|
124
138
|
# end
|
125
139
|
# end
|
126
140
|
# This prints all rows of +my_table+ to stdout:
|
127
|
-
# "some,
|
128
|
-
# "more,
|
141
|
+
# "some,data,to,copy\n"
|
142
|
+
# "more,data,to,copy\n"
|
143
|
+
#
|
144
|
+
# The same with text format decoder PG::TextDecoder::CopyRow
|
145
|
+
# and Array output:
|
146
|
+
# deco = PG::TextDecoder::CopyRow.new
|
147
|
+
# conn.copy_data "COPY my_table TO STDOUT", deco do
|
148
|
+
# while row=conn.get_copy_data
|
149
|
+
# p row
|
150
|
+
# end
|
151
|
+
# end
|
152
|
+
# This receives all rows of +my_table+ as ruby array:
|
153
|
+
# ["some", "data", "to", "copy"]
|
154
|
+
# ["more", "data", "to", "copy"]
|
155
|
+
|
129
156
|
def copy_data( sql, coder=nil )
|
130
157
|
res = exec( sql )
|
131
158
|
|
@@ -165,7 +192,7 @@ class PG::Connection
|
|
165
192
|
raise
|
166
193
|
else
|
167
194
|
res = get_last_result
|
168
|
-
if res.result_status != PGRES_COMMAND_OK
|
195
|
+
if !res || res.result_status != PGRES_COMMAND_OK
|
169
196
|
while get_copy_data
|
170
197
|
end
|
171
198
|
while get_result
|
@@ -224,8 +251,41 @@ class PG::Connection
|
|
224
251
|
end
|
225
252
|
end
|
226
253
|
|
227
|
-
|
254
|
+
# Method 'ssl_attribute' was introduced in PostgreSQL 9.5.
|
255
|
+
if self.instance_methods.find{|m| m.to_sym == :ssl_attribute }
|
256
|
+
# call-seq:
|
257
|
+
# conn.ssl_attributes -> Hash<String,String>
|
258
|
+
#
|
259
|
+
# Returns SSL-related information about the connection as key/value pairs
|
260
|
+
#
|
261
|
+
# The available attributes varies depending on the SSL library being used,
|
262
|
+
# and the type of connection.
|
263
|
+
#
|
264
|
+
# See also #ssl_attribute
|
265
|
+
def ssl_attributes
|
266
|
+
ssl_attribute_names.each.with_object({}) do |n,h|
|
267
|
+
h[n] = ssl_attribute(n)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
228
271
|
|
229
|
-
|
230
|
-
|
272
|
+
REDIRECT_METHODS = {
|
273
|
+
:exec => [:async_exec, :sync_exec],
|
274
|
+
:query => [:async_exec, :sync_exec],
|
275
|
+
:exec_params => [:async_exec_params, :sync_exec_params],
|
276
|
+
:prepare => [:async_prepare, :sync_prepare],
|
277
|
+
:exec_prepared => [:async_exec_prepared, :sync_exec_prepared],
|
278
|
+
:describe_portal => [:async_describe_portal, :sync_describe_portal],
|
279
|
+
:describe_prepared => [:async_describe_prepared, :sync_describe_prepared],
|
280
|
+
}
|
231
281
|
|
282
|
+
def self.async_api=(enable)
|
283
|
+
REDIRECT_METHODS.each do |ali, (async, sync)|
|
284
|
+
remove_method(ali) if method_defined?(ali)
|
285
|
+
alias_method( ali, enable ? async : sync )
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# pg-1.1.0+ defaults to libpq's async API for query related blocking methods
|
290
|
+
self.async_api = true
|
291
|
+
end # class PG::Connection
|
data/lib/pg/constants.rb
CHANGED
data/lib/pg/exceptions.rb
CHANGED
data/lib/pg/result.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'pg' unless defined?( PG )
|
4
5
|
|
@@ -9,18 +10,34 @@ class PG::Result
|
|
9
10
|
#
|
10
11
|
# +type_map+: a PG::TypeMap instance.
|
11
12
|
#
|
12
|
-
#
|
13
|
+
# This method is equal to #type_map= , but returns self, so that calls can be chained.
|
14
|
+
#
|
15
|
+
# See also PG::BasicTypeMapForResults
|
13
16
|
def map_types!(type_map)
|
14
17
|
self.type_map = type_map
|
15
|
-
self
|
18
|
+
return self
|
19
|
+
end
|
20
|
+
|
21
|
+
# Set the data type for all field name returning methods.
|
22
|
+
#
|
23
|
+
# +type+: a Symbol defining the field name type.
|
24
|
+
#
|
25
|
+
# This method is equal to #field_name_type= , but returns self, so that calls can be chained.
|
26
|
+
def field_names_as(type)
|
27
|
+
self.field_name_type = type
|
28
|
+
return self
|
16
29
|
end
|
17
30
|
|
31
|
+
### Return a String representation of the object suitable for debugging.
|
18
32
|
def inspect
|
19
33
|
str = self.to_s
|
20
|
-
str[-1,0] =
|
21
|
-
|
34
|
+
str[-1,0] = if cleared?
|
35
|
+
" cleared"
|
36
|
+
else
|
37
|
+
" status=#{res_status(result_status)} ntuples=#{ntuples} nfields=#{nfields} cmd_tuples=#{cmd_tuples}"
|
38
|
+
end
|
39
|
+
return str
|
22
40
|
end
|
41
|
+
|
23
42
|
end # class PG::Result
|
24
43
|
|
25
|
-
# Backward-compatible alias
|
26
|
-
PGresult = PG::Result
|
data/lib/pg/text_decoder.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'date'
|
5
|
+
require 'json'
|
4
6
|
|
5
7
|
module PG
|
6
8
|
module TextDecoder
|
7
9
|
class Date < SimpleDecoder
|
8
|
-
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
9
|
-
|
10
10
|
def decode(string, tuple=nil, field=nil)
|
11
|
-
if string =~
|
11
|
+
if string =~ /\A(\d{4})-(\d\d)-(\d\d)\z/
|
12
12
|
::Date.new $1.to_i, $2.to_i, $3.to_i
|
13
13
|
else
|
14
14
|
string
|
@@ -16,29 +16,31 @@ module PG
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
class
|
20
|
-
ISO_DATETIME_WITHOUT_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
21
|
-
|
19
|
+
class JSON < SimpleDecoder
|
22
20
|
def decode(string, tuple=nil, field=nil)
|
23
|
-
|
24
|
-
Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r
|
25
|
-
else
|
26
|
-
string
|
27
|
-
end
|
21
|
+
::JSON.parse(string, quirks_mode: true)
|
28
22
|
end
|
29
23
|
end
|
30
24
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
25
|
+
# Convenience classes for timezone options
|
26
|
+
class TimestampUtc < Timestamp
|
27
|
+
def initialize(params={})
|
28
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
class TimestampUtcToLocal < Timestamp
|
32
|
+
def initialize(params={})
|
33
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL))
|
40
34
|
end
|
41
35
|
end
|
36
|
+
class TimestampLocal < Timestamp
|
37
|
+
def initialize(params={})
|
38
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# For backward compatibility:
|
43
|
+
TimestampWithoutTimeZone = TimestampLocal
|
44
|
+
TimestampWithTimeZone = Timestamp
|
42
45
|
end
|
43
46
|
end # module PG
|
44
|
-
|
data/lib/pg/text_encoder.rb
CHANGED
@@ -1,27 +1,59 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'ipaddr'
|
2
6
|
|
3
7
|
module PG
|
4
8
|
module TextEncoder
|
5
9
|
class Date < SimpleEncoder
|
6
|
-
STRFTIME_ISO_DATE = "%Y-%m-%d".freeze
|
7
10
|
def encode(value)
|
8
|
-
value.respond_to?(:strftime) ? value.strftime(
|
11
|
+
value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d") : value
|
9
12
|
end
|
10
13
|
end
|
11
14
|
|
12
15
|
class TimestampWithoutTimeZone < SimpleEncoder
|
13
|
-
STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE = "%Y-%m-%d %H:%M:%S.%N".freeze
|
14
16
|
def encode(value)
|
15
|
-
value.respond_to?(:strftime) ? value.strftime(
|
17
|
+
value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S.%N") : value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class TimestampUtc < SimpleEncoder
|
22
|
+
def encode(value)
|
23
|
+
value.respond_to?(:utc) ? value.utc.strftime("%Y-%m-%d %H:%M:%S.%N") : value
|
16
24
|
end
|
17
25
|
end
|
18
26
|
|
19
27
|
class TimestampWithTimeZone < SimpleEncoder
|
20
|
-
STRFTIME_ISO_DATETIME_WITH_TIMEZONE = "%Y-%m-%d %H:%M:%S.%N %:z".freeze
|
21
28
|
def encode(value)
|
22
|
-
value.respond_to?(:strftime) ? value.strftime(
|
29
|
+
value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S.%N %:z") : value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class JSON < SimpleEncoder
|
34
|
+
def encode(value)
|
35
|
+
::JSON.generate(value, quirks_mode: true)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Inet < SimpleEncoder
|
40
|
+
def encode(value)
|
41
|
+
case value
|
42
|
+
when IPAddr
|
43
|
+
default_prefix = (value.family == Socket::AF_INET ? 32 : 128)
|
44
|
+
s = value.to_s
|
45
|
+
if value.respond_to?(:prefix)
|
46
|
+
prefix = value.prefix
|
47
|
+
else
|
48
|
+
range = value.to_range
|
49
|
+
prefix = default_prefix - Math.log(((range.end.to_i - range.begin.to_i) + 1), 2).to_i
|
50
|
+
end
|
51
|
+
s << "/" << prefix.to_s if prefix != default_prefix
|
52
|
+
s
|
53
|
+
else
|
54
|
+
value
|
55
|
+
end
|
23
56
|
end
|
24
57
|
end
|
25
58
|
end
|
26
59
|
end # module PG
|
27
|
-
|
data/lib/pg/tuple.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'pg' unless defined?( PG )
|
5
|
+
|
6
|
+
|
7
|
+
class PG::Tuple
|
8
|
+
|
9
|
+
### Return a String representation of the object suitable for debugging.
|
10
|
+
def inspect
|
11
|
+
"#<#{self.class} #{self.map{|k,v| "#{k}: #{v.inspect}" }.join(", ") }>"
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_key?(key)
|
15
|
+
field_map.has_key?(key)
|
16
|
+
end
|
17
|
+
alias key? has_key?
|
18
|
+
|
19
|
+
def keys
|
20
|
+
field_names || field_map.keys.freeze
|
21
|
+
end
|
22
|
+
|
23
|
+
def each_key(&block)
|
24
|
+
if fn=field_names
|
25
|
+
fn.each(&block)
|
26
|
+
else
|
27
|
+
field_map.each_key(&block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'pg' unless defined?( PG )
|
4
5
|
|
@@ -9,7 +10,7 @@ class PG::TypeMapByColumn
|
|
9
10
|
end
|
10
11
|
|
11
12
|
def inspect
|
12
|
-
type_strings = coders.map{|c| c ?
|
13
|
+
type_strings = coders.map{|c| c ? c.inspect_short : 'nil' }
|
13
14
|
"#<#{self.class} #{type_strings.join(' ')}>"
|
14
15
|
end
|
15
16
|
end
|