sentry-raven 2.13.0 → 3.0.4
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 +4 -4
- data/.craft.yml +15 -0
- data/.github/workflows/test.yml +87 -0
- data/.github/workflows/zeus_upload.yml +32 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +44 -9
- data/.scripts/bump-version.sh +9 -0
- data/{changelog.md → CHANGELOG.md} +38 -1
- data/CONTRIUTING.md +26 -0
- data/Gemfile +20 -25
- data/README.md +24 -14
- data/lib/raven/backtrace.rb +7 -5
- data/lib/raven/base.rb +5 -2
- data/lib/raven/breadcrumbs.rb +1 -1
- data/lib/raven/breadcrumbs/activesupport.rb +10 -10
- data/lib/raven/breadcrumbs/logger.rb +3 -3
- data/lib/raven/cli.rb +9 -20
- data/lib/raven/client.rb +9 -4
- data/lib/raven/configuration.rb +20 -6
- data/lib/raven/core_ext/object/deep_dup.rb +57 -0
- data/lib/raven/core_ext/object/duplicable.rb +153 -0
- data/lib/raven/event.rb +4 -2
- data/lib/raven/instance.rb +6 -3
- data/lib/raven/integrations/delayed_job.rb +13 -14
- data/lib/raven/integrations/rack-timeout.rb +2 -3
- data/lib/raven/integrations/rack.rb +4 -3
- data/lib/raven/integrations/rails.rb +1 -0
- data/lib/raven/integrations/rails/active_job.rb +5 -4
- data/lib/raven/integrations/rails/overrides/debug_exceptions_catcher.rb +2 -2
- data/lib/raven/interface.rb +2 -2
- data/lib/raven/interfaces/stack_trace.rb +1 -1
- data/lib/raven/linecache.rb +5 -2
- data/lib/raven/logger.rb +3 -2
- data/lib/raven/processor/cookies.rb +16 -6
- data/lib/raven/processor/post_data.rb +2 -0
- data/lib/raven/processor/removecircularreferences.rb +3 -1
- data/lib/raven/processor/sanitizedata.rb +65 -17
- data/lib/raven/processor/utf8conversion.rb +2 -0
- data/lib/raven/transports.rb +4 -0
- data/lib/raven/transports/http.rb +5 -5
- data/lib/raven/utils/exception_cause_chain.rb +1 -0
- data/lib/raven/utils/real_ip.rb +1 -1
- data/lib/raven/version.rb +2 -2
- data/sentry-raven.gemspec +2 -2
- metadata +11 -11
- data/.travis.yml +0 -47
data/lib/raven/backtrace.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
## Inspired by Rails' and Airbrake's backtrace parsers.
|
2
4
|
|
3
5
|
module Raven
|
@@ -5,16 +7,16 @@ module Raven
|
|
5
7
|
class Backtrace
|
6
8
|
# Handles backtrace parsing line by line
|
7
9
|
class Line
|
8
|
-
RB_EXTENSION = ".rb"
|
10
|
+
RB_EXTENSION = ".rb"
|
9
11
|
# regexp (optional leading X: on windows, or JRuby9000 class-prefix)
|
10
12
|
RUBY_INPUT_FORMAT = /
|
11
13
|
^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>):
|
12
14
|
(\d+)
|
13
15
|
(?: :in \s `([^']+)')?$
|
14
|
-
/x
|
16
|
+
/x.freeze
|
15
17
|
|
16
18
|
# org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:170)
|
17
|
-
JAVA_INPUT_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)
|
19
|
+
JAVA_INPUT_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/.freeze
|
18
20
|
|
19
21
|
# The file portion of the line (such as app/models/user.rb)
|
20
22
|
attr_reader :file
|
@@ -74,7 +76,7 @@ module Raven
|
|
74
76
|
|
75
77
|
def self.in_app_pattern
|
76
78
|
@in_app_pattern ||= begin
|
77
|
-
project_root = Raven.configuration.project_root
|
79
|
+
project_root = Raven.configuration.project_root&.to_s
|
78
80
|
Regexp.new("^(#{project_root}/)?#{Raven.configuration.app_dirs_pattern || APP_DIRS_PATTERN}")
|
79
81
|
end
|
80
82
|
end
|
@@ -84,7 +86,7 @@ module Raven
|
|
84
86
|
attr_writer :file, :number, :method, :module_name
|
85
87
|
end
|
86
88
|
|
87
|
-
APP_DIRS_PATTERN = /(bin|exe|app|config|lib|test)
|
89
|
+
APP_DIRS_PATTERN = /(bin|exe|app|config|lib|test)/.freeze
|
88
90
|
|
89
91
|
# holder for an Array of Backtrace::Line instances
|
90
92
|
attr_reader :lines
|
data/lib/raven/base.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'raven/version'
|
2
|
+
require 'raven/core_ext/object/deep_dup'
|
2
3
|
require 'raven/backtrace'
|
3
4
|
require 'raven/breadcrumbs'
|
4
5
|
require 'raven/processor'
|
@@ -85,12 +86,13 @@ module Raven
|
|
85
86
|
|
86
87
|
def load_integration(integration)
|
87
88
|
require "raven/integrations/#{integration}"
|
88
|
-
rescue Exception =>
|
89
|
-
logger.warn "Unable to load raven/integrations/#{integration}: #{
|
89
|
+
rescue Exception => e
|
90
|
+
logger.warn "Unable to load raven/integrations/#{integration}: #{e}"
|
90
91
|
end
|
91
92
|
|
92
93
|
def safely_prepend(module_name, opts = {})
|
93
94
|
return if opts[:to].nil? || opts[:from].nil?
|
95
|
+
|
94
96
|
if opts[:to].respond_to?(:prepend, true)
|
95
97
|
opts[:to].send(:prepend, opts[:from].const_get(module_name))
|
96
98
|
else
|
@@ -101,6 +103,7 @@ module Raven
|
|
101
103
|
def sys_command(command)
|
102
104
|
result = `#{command} 2>&1` rescue nil
|
103
105
|
return if result.nil? || result.empty? || ($CHILD_STATUS && $CHILD_STATUS.exitstatus != 0)
|
106
|
+
|
104
107
|
result.strip
|
105
108
|
end
|
106
109
|
end
|
data/lib/raven/breadcrumbs.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
module Raven
|
2
2
|
module ActiveSupportBreadcrumbs
|
3
3
|
class << self
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
4
|
+
def add(name, started, _finished, _unique_id, data)
|
5
|
+
Raven.breadcrumbs.record do |crumb|
|
6
|
+
crumb.data = data
|
7
|
+
crumb.category = name
|
8
|
+
crumb.timestamp = started.to_i
|
10
9
|
end
|
10
|
+
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
12
|
+
def inject
|
13
|
+
ActiveSupport::Notifications.subscribe(/.*/) do |name, started, finished, unique_id, data|
|
14
|
+
add(name, started, finished, unique_id, data)
|
16
15
|
end
|
16
|
+
end
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -4,13 +4,13 @@ module Raven
|
|
4
4
|
module BreadcrumbLogger
|
5
5
|
LEVELS = {
|
6
6
|
::Logger::DEBUG => 'debug',
|
7
|
-
::Logger::INFO
|
8
|
-
::Logger::WARN
|
7
|
+
::Logger::INFO => 'info',
|
8
|
+
::Logger::WARN => 'warn',
|
9
9
|
::Logger::ERROR => 'error',
|
10
10
|
::Logger::FATAL => 'fatal'
|
11
11
|
}.freeze
|
12
12
|
|
13
|
-
EXC_FORMAT = /^([a-zA-Z0-9]+)\:\s(.*)
|
13
|
+
EXC_FORMAT = /^([a-zA-Z0-9]+)\:\s(.*)$/.freeze
|
14
14
|
|
15
15
|
def self.parse_exception(message)
|
16
16
|
lines = message.split(/\n\s*/)
|
data/lib/raven/cli.rb
CHANGED
@@ -18,7 +18,7 @@ module Raven
|
|
18
18
|
|
19
19
|
# wipe out env settings to ensure we send the event
|
20
20
|
unless config.capture_allowed?
|
21
|
-
env_name = config.environments.
|
21
|
+
env_name = config.environments.last || 'production'
|
22
22
|
config.current_environment = env_name
|
23
23
|
end
|
24
24
|
|
@@ -29,31 +29,20 @@ module Raven
|
|
29
29
|
|
30
30
|
begin
|
31
31
|
1 / 0
|
32
|
-
rescue ZeroDivisionError =>
|
33
|
-
evt = instance.capture_exception(
|
32
|
+
rescue ZeroDivisionError => e
|
33
|
+
evt = instance.capture_exception(e)
|
34
34
|
end
|
35
35
|
|
36
|
-
if evt
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
-
elsif evt # async configuration
|
43
|
-
if evt.value.is_a? Hash
|
44
|
-
instance.logger.debug "-> event ID: #{evt.value[:event_id]}"
|
45
|
-
else
|
46
|
-
instance.logger.debug "-> event ID: #{evt.value.id}"
|
47
|
-
end
|
36
|
+
if evt
|
37
|
+
instance.logger.debug "-> event ID: #{evt.id}"
|
38
|
+
instance.logger.debug ""
|
39
|
+
instance.logger.debug "Done!"
|
40
|
+
evt
|
48
41
|
else
|
49
42
|
instance.logger.debug ""
|
50
43
|
instance.logger.debug "An error occurred while attempting to send the event."
|
51
|
-
|
44
|
+
false
|
52
45
|
end
|
53
|
-
|
54
|
-
instance.logger.debug ""
|
55
|
-
instance.logger.debug "Done!"
|
56
|
-
evt
|
57
46
|
end
|
58
47
|
end
|
59
48
|
end
|
data/lib/raven/client.rb
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'base64'
|
3
4
|
require 'json'
|
4
5
|
require 'zlib'
|
5
6
|
|
7
|
+
require "raven/transports"
|
8
|
+
|
6
9
|
module Raven
|
7
10
|
# Encodes events and sends them to the Sentry server.
|
8
11
|
class Client
|
9
|
-
PROTOCOL_VERSION = '5'
|
10
|
-
USER_AGENT = "raven-ruby/#{Raven::VERSION}"
|
11
|
-
CONTENT_TYPE = 'application/json'
|
12
|
+
PROTOCOL_VERSION = '5'
|
13
|
+
USER_AGENT = "raven-ruby/#{Raven::VERSION}"
|
14
|
+
CONTENT_TYPE = 'application/json'
|
12
15
|
|
13
16
|
attr_accessor :configuration
|
14
17
|
|
@@ -120,7 +123,9 @@ module Raven
|
|
120
123
|
configuration.logger.warn "Not sending event due to previous failure(s)."
|
121
124
|
end
|
122
125
|
configuration.logger.warn("Failed to submit event: #{get_log_message(event)}")
|
123
|
-
|
126
|
+
|
127
|
+
# configuration.transport_failure_callback can be false & nil
|
128
|
+
configuration.transport_failure_callback.call(event) if configuration.transport_failure_callback # rubocop:disable Style/SafeNavigation
|
124
129
|
end
|
125
130
|
end
|
126
131
|
|
data/lib/raven/configuration.rb
CHANGED
@@ -236,6 +236,7 @@ module Raven
|
|
236
236
|
|
237
237
|
def server=(value)
|
238
238
|
return if value.nil?
|
239
|
+
|
239
240
|
uri = URI.parse(value)
|
240
241
|
uri_path = uri.path.split('/')
|
241
242
|
|
@@ -253,13 +254,14 @@ module Raven
|
|
253
254
|
|
254
255
|
# For anyone who wants to read the base server string
|
255
256
|
@server = "#{scheme}://#{host}"
|
256
|
-
@server
|
257
|
-
@server
|
257
|
+
@server += ":#{port}" unless port == { 'http' => 80, 'https' => 443 }[scheme]
|
258
|
+
@server += path
|
258
259
|
end
|
259
260
|
alias dsn= server=
|
260
261
|
|
261
262
|
def encoding=(encoding)
|
262
263
|
raise(Error, 'Unsupported encoding') unless %w(gzip json).include? encoding
|
264
|
+
|
263
265
|
@encoding = encoding
|
264
266
|
end
|
265
267
|
|
@@ -267,6 +269,7 @@ module Raven
|
|
267
269
|
unless value == false || value.respond_to?(:call)
|
268
270
|
raise(ArgumentError, "async must be callable (or false to disable)")
|
269
271
|
end
|
272
|
+
|
270
273
|
@async = value
|
271
274
|
end
|
272
275
|
|
@@ -274,6 +277,7 @@ module Raven
|
|
274
277
|
unless value == false || value.respond_to?(:call)
|
275
278
|
raise(ArgumentError, "transport_failure_callback must be callable (or false to disable)")
|
276
279
|
end
|
280
|
+
|
277
281
|
@transport_failure_callback = value
|
278
282
|
end
|
279
283
|
|
@@ -281,6 +285,7 @@ module Raven
|
|
281
285
|
unless value == false || value.respond_to?(:call)
|
282
286
|
raise ArgumentError, "should_capture must be callable (or false to disable)"
|
283
287
|
end
|
288
|
+
|
284
289
|
@should_capture = value
|
285
290
|
end
|
286
291
|
|
@@ -288,6 +293,7 @@ module Raven
|
|
288
293
|
unless value == false || value.respond_to?(:call)
|
289
294
|
raise ArgumentError, "before_send must be callable (or false to disable)"
|
290
295
|
end
|
296
|
+
|
291
297
|
@before_send = value
|
292
298
|
end
|
293
299
|
|
@@ -336,6 +342,10 @@ module Raven
|
|
336
342
|
end
|
337
343
|
end
|
338
344
|
|
345
|
+
def enabled_in_current_env?
|
346
|
+
environments.empty? || environments.include?(current_environment)
|
347
|
+
end
|
348
|
+
|
339
349
|
private
|
340
350
|
|
341
351
|
def detect_project_root
|
@@ -351,8 +361,8 @@ module Raven
|
|
351
361
|
detect_release_from_git ||
|
352
362
|
detect_release_from_capistrano ||
|
353
363
|
detect_release_from_heroku
|
354
|
-
rescue =>
|
355
|
-
logger.error "Error detecting release: #{
|
364
|
+
rescue => e
|
365
|
+
logger.error "Error detecting release: #{e.message}"
|
356
366
|
end
|
357
367
|
|
358
368
|
def excluded_exception?(incoming_exception)
|
@@ -417,19 +427,22 @@ module Raven
|
|
417
427
|
end
|
418
428
|
|
419
429
|
def capture_in_current_environment?
|
420
|
-
return true
|
430
|
+
return true if enabled_in_current_env?
|
431
|
+
|
421
432
|
@errors << "Not configured to send/capture in environment '#{current_environment}'"
|
422
433
|
false
|
423
434
|
end
|
424
435
|
|
425
436
|
def capture_allowed_by_callback?(message_or_exc)
|
426
|
-
return true if !should_capture || message_or_exc.nil? || should_capture.call(
|
437
|
+
return true if !should_capture || message_or_exc.nil? || should_capture.call(message_or_exc)
|
438
|
+
|
427
439
|
@errors << "should_capture returned false"
|
428
440
|
false
|
429
441
|
end
|
430
442
|
|
431
443
|
def valid?
|
432
444
|
return true if %w(server host path public_key project_id).all? { |k| public_send(k) }
|
445
|
+
|
433
446
|
if server
|
434
447
|
%w(server host path public_key project_id).map do |key|
|
435
448
|
@errors << "No #{key} specified" unless public_send(key)
|
@@ -442,6 +455,7 @@ module Raven
|
|
442
455
|
|
443
456
|
def sample_allowed?
|
444
457
|
return true if sample_rate == 1.0
|
458
|
+
|
445
459
|
if Random::DEFAULT.rand >= sample_rate
|
446
460
|
@errors << "Excluded by random sample"
|
447
461
|
false
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'raven/core_ext/object/duplicable'
|
2
|
+
|
3
|
+
#########################################
|
4
|
+
# This file was copied from Rails 5.2 #
|
5
|
+
#########################################
|
6
|
+
|
7
|
+
class Object
|
8
|
+
# Returns a deep copy of object if it's duplicable. If it's
|
9
|
+
# not duplicable, returns +self+.
|
10
|
+
#
|
11
|
+
# object = Object.new
|
12
|
+
# dup = object.deep_dup
|
13
|
+
# dup.instance_variable_set(:@a, 1)
|
14
|
+
#
|
15
|
+
# object.instance_variable_defined?(:@a) # => false
|
16
|
+
# dup.instance_variable_defined?(:@a) # => true
|
17
|
+
def deep_dup
|
18
|
+
duplicable? ? dup : self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Array
|
23
|
+
# Returns a deep copy of array.
|
24
|
+
#
|
25
|
+
# array = [1, [2, 3]]
|
26
|
+
# dup = array.deep_dup
|
27
|
+
# dup[1][2] = 4
|
28
|
+
#
|
29
|
+
# array[1][2] # => nil
|
30
|
+
# dup[1][2] # => 4
|
31
|
+
def deep_dup
|
32
|
+
map(&:deep_dup)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Hash
|
37
|
+
# Returns a deep copy of hash.
|
38
|
+
#
|
39
|
+
# hash = { a: { b: 'b' } }
|
40
|
+
# dup = hash.deep_dup
|
41
|
+
# dup[:a][:c] = 'c'
|
42
|
+
#
|
43
|
+
# hash[:a][:c] # => nil
|
44
|
+
# dup[:a][:c] # => "c"
|
45
|
+
def deep_dup
|
46
|
+
hash = dup
|
47
|
+
each_pair do |key, value|
|
48
|
+
if key.frozen? && ::String === key
|
49
|
+
hash[key] = value.deep_dup
|
50
|
+
else
|
51
|
+
hash.delete(key)
|
52
|
+
hash[key.deep_dup] = value.deep_dup
|
53
|
+
end
|
54
|
+
end
|
55
|
+
hash
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#########################################
|
4
|
+
# This file was copied from Rails 5.2 #
|
5
|
+
#########################################
|
6
|
+
|
7
|
+
#--
|
8
|
+
# Most objects are cloneable, but not all. For example you can't dup methods:
|
9
|
+
#
|
10
|
+
# method(:puts).dup # => TypeError: allocator undefined for Method
|
11
|
+
#
|
12
|
+
# Classes may signal their instances are not duplicable removing +dup+/+clone+
|
13
|
+
# or raising exceptions from them. So, to dup an arbitrary object you normally
|
14
|
+
# use an optimistic approach and are ready to catch an exception, say:
|
15
|
+
#
|
16
|
+
# arbitrary_object.dup rescue object
|
17
|
+
#
|
18
|
+
# Rails dups objects in a few critical spots where they are not that arbitrary.
|
19
|
+
# That rescue is very expensive (like 40 times slower than a predicate), and it
|
20
|
+
# is often triggered.
|
21
|
+
#
|
22
|
+
# That's why we hardcode the following cases and check duplicable? instead of
|
23
|
+
# using that rescue idiom.
|
24
|
+
#++
|
25
|
+
class Object
|
26
|
+
# Can you safely dup this object?
|
27
|
+
#
|
28
|
+
# False for method objects;
|
29
|
+
# true otherwise.
|
30
|
+
def duplicable?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class NilClass
|
36
|
+
begin
|
37
|
+
nil.dup
|
38
|
+
rescue TypeError
|
39
|
+
# +nil+ is not duplicable:
|
40
|
+
#
|
41
|
+
# nil.duplicable? # => false
|
42
|
+
# nil.dup # => TypeError: can't dup NilClass
|
43
|
+
def duplicable?
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class FalseClass
|
50
|
+
begin
|
51
|
+
false.dup
|
52
|
+
rescue TypeError
|
53
|
+
# +false+ is not duplicable:
|
54
|
+
#
|
55
|
+
# false.duplicable? # => false
|
56
|
+
# false.dup # => TypeError: can't dup FalseClass
|
57
|
+
def duplicable?
|
58
|
+
false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class TrueClass
|
64
|
+
begin
|
65
|
+
true.dup
|
66
|
+
rescue TypeError
|
67
|
+
# +true+ is not duplicable:
|
68
|
+
#
|
69
|
+
# true.duplicable? # => false
|
70
|
+
# true.dup # => TypeError: can't dup TrueClass
|
71
|
+
def duplicable?
|
72
|
+
false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class Symbol
|
78
|
+
begin
|
79
|
+
:symbol.dup # Ruby 2.4.x.
|
80
|
+
"symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.
|
81
|
+
rescue TypeError
|
82
|
+
# Symbols are not duplicable:
|
83
|
+
#
|
84
|
+
# :my_symbol.duplicable? # => false
|
85
|
+
# :my_symbol.dup # => TypeError: can't dup Symbol
|
86
|
+
def duplicable?
|
87
|
+
false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class Numeric
|
93
|
+
begin
|
94
|
+
1.dup
|
95
|
+
rescue TypeError
|
96
|
+
# Numbers are not duplicable:
|
97
|
+
#
|
98
|
+
# 3.duplicable? # => false
|
99
|
+
# 3.dup # => TypeError: can't dup Integer
|
100
|
+
def duplicable?
|
101
|
+
false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
require "bigdecimal"
|
107
|
+
class BigDecimal
|
108
|
+
# BigDecimals are duplicable:
|
109
|
+
#
|
110
|
+
# BigDecimal("1.2").duplicable? # => true
|
111
|
+
# BigDecimal("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
|
112
|
+
def duplicable?
|
113
|
+
true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class Method
|
118
|
+
# Methods are not duplicable:
|
119
|
+
#
|
120
|
+
# method(:puts).duplicable? # => false
|
121
|
+
# method(:puts).dup # => TypeError: allocator undefined for Method
|
122
|
+
def duplicable?
|
123
|
+
false
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class Complex
|
128
|
+
begin
|
129
|
+
Complex(1).dup
|
130
|
+
rescue TypeError
|
131
|
+
# Complexes are not duplicable:
|
132
|
+
#
|
133
|
+
# Complex(1).duplicable? # => false
|
134
|
+
# Complex(1).dup # => TypeError: can't copy Complex
|
135
|
+
def duplicable?
|
136
|
+
false
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class Rational
|
142
|
+
begin
|
143
|
+
Rational(1).dup
|
144
|
+
rescue TypeError
|
145
|
+
# Rationals are not duplicable:
|
146
|
+
#
|
147
|
+
# Rational(1).duplicable? # => false
|
148
|
+
# Rational(1).dup # => TypeError: can't copy Rational
|
149
|
+
def duplicable?
|
150
|
+
false
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|