activesupport 7.1.2 → 7.1.5

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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +134 -0
  3. data/lib/active_support/backtrace_cleaner.rb +5 -0
  4. data/lib/active_support/broadcast_logger.rb +15 -14
  5. data/lib/active_support/cache/mem_cache_store.rb +6 -6
  6. data/lib/active_support/cache/memory_store.rb +4 -4
  7. data/lib/active_support/cache/redis_cache_store.rb +16 -12
  8. data/lib/active_support/cache/strategy/local_cache.rb +9 -6
  9. data/lib/active_support/cache.rb +17 -6
  10. data/lib/active_support/code_generator.rb +15 -10
  11. data/lib/active_support/core_ext/module/delegation.rb +41 -26
  12. data/lib/active_support/core_ext/object/duplicable.rb +24 -15
  13. data/lib/active_support/core_ext/object/json.rb +5 -3
  14. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  15. data/lib/active_support/core_ext/string/indent.rb +1 -1
  16. data/lib/active_support/deprecation/behaviors.rb +18 -16
  17. data/lib/active_support/deprecation/reporting.rb +8 -5
  18. data/lib/active_support/gem_version.rb +1 -1
  19. data/lib/active_support/html_safe_translation.rb +16 -6
  20. data/lib/active_support/log_subscriber.rb +1 -0
  21. data/lib/active_support/messages/codec.rb +1 -1
  22. data/lib/active_support/notifications/instrumenter.rb +11 -3
  23. data/lib/active_support/number_helper.rb +379 -318
  24. data/lib/active_support/railtie.rb +3 -3
  25. data/lib/active_support/syntax_error_proxy.rb +12 -1
  26. data/lib/active_support/tagged_logging.rb +4 -0
  27. data/lib/active_support/testing/assertions.rb +1 -1
  28. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  29. data/lib/active_support/testing/time_helpers.rb +4 -0
  30. data/lib/active_support/values/time_zone.rb +9 -0
  31. data/lib/active_support.rb +1 -1
  32. metadata +51 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6db71966858e675d32617069b0df8dc17b1c5dfe8123c87ae96a28fdb6e2f5e6
4
- data.tar.gz: 81ffe410567130147cd0c6f74943d40404a4031b2cf30f12bfaa2ee698cd5a57
3
+ metadata.gz: 1d05cf683e37c600f522c4be11034dfa45e30ef52cea9e41dc0087a624fd4063
4
+ data.tar.gz: 7e7ba21207beeb10e1093ac45cd02a096fda507504e229f8df559b746937f1e9
5
5
  SHA512:
6
- metadata.gz: 99a9131ff9f97e739719621df9ba0a33d3b2fad9b1315397382235f29d33ee130f701d0c0ed767eacf01ccb31607eb26d7f5bb95f37ad86ac57f18817cd9b253
7
- data.tar.gz: ffa6a0ffe90b88c87f65d85ddb32390d20f8c34ab2517f797c34d325839cbf434a0202650cdd54f118bee9a299cf478f83af4222c313f2746e267bbd19003914
6
+ metadata.gz: 96c1bc0122ee7fcd400846ada8641591d06ca82c42bb4d35bc87c7c8062826b1aa6eec7d2fe81144947353ff29195fd9ca3b4b307c9d21aa366a0717c974eb40
7
+ data.tar.gz: 668f85b8c485c9bc089781d623d3e6a6c84e8e2c57afb350d35ce346ad629679df837e9c9ea8db31b72a8b05839ab296f5e8ac8289103195ecd27b227ebe696d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,137 @@
1
+ ## Rails 7.1.5 (October 30, 2024) ##
2
+
3
+ * No changes.
4
+
5
+
6
+ ## Rails 7.1.4.2 (October 23, 2024) ##
7
+
8
+ * No changes.
9
+
10
+
11
+ ## Rails 7.1.4.1 (October 15, 2024) ##
12
+
13
+ * No changes.
14
+
15
+
16
+ ## Rails 7.1.4 (August 22, 2024) ##
17
+
18
+ * Improve compatibility for `ActiveSupport::BroadcastLogger`.
19
+
20
+ *Máximo Mussini*
21
+
22
+ * Pass options along to write_entry in handle_expired_entry method.
23
+
24
+ *Graham Cooper*
25
+
26
+ * Fix Active Support configurations deprecations.
27
+
28
+ *fatkodima*
29
+
30
+ * Fix teardown callbacks.
31
+
32
+ *Tristan Starck*
33
+
34
+ * `BacktraceCleaner` silence core internal methods by default.
35
+
36
+ *Jean Boussier*
37
+
38
+ * Fix `delegate_missing_to allow_nil: true` when called with implict self
39
+
40
+ ```ruby
41
+ class Person
42
+ delegate_missing_to :address, allow_nil: true
43
+
44
+ def address
45
+ nil
46
+ end
47
+
48
+ def berliner?
49
+ city == "Berlin"
50
+ end
51
+ end
52
+
53
+ Person.new.city # => nil
54
+ Person.new.berliner? # undefined local variable or method `city' for an instance of Person (NameError)
55
+ ```
56
+
57
+ *Jean Boussier*
58
+
59
+ * Work around a Ruby bug that can cause a VM crash.
60
+
61
+ This would happen if using `TaggerLogger` with a Proc
62
+ formatter on which you called `object_id`.
63
+
64
+ ```
65
+ [BUG] Object ID seen, but not in mapping table: proc
66
+ ```
67
+
68
+ *Jean Boussier*
69
+
70
+ * Fix `ActiveSupport::Notifications.publish_event` to preserve units.
71
+
72
+ This solves the incorrect reporting of time spent running Active Record
73
+ asynchronous queries (by a factor `1000`).
74
+
75
+ *Jean Boussier*
76
+
77
+
78
+ ## Rails 7.1.3.4 (June 04, 2024) ##
79
+
80
+ * No changes.
81
+
82
+
83
+ ## Rails 7.1.3.3 (May 16, 2024) ##
84
+
85
+ * No changes.
86
+
87
+
88
+ ## Rails 7.1.3.2 (February 21, 2024) ##
89
+
90
+ * No changes.
91
+
92
+
93
+ ## Rails 7.1.3.1 (February 21, 2024) ##
94
+
95
+ * No changes.
96
+
97
+
98
+ ## Rails 7.1.3 (January 16, 2024) ##
99
+
100
+ * Handle nil `backtrace_locations` in `ActiveSupport::SyntaxErrorProxy`.
101
+
102
+ *Eugene Kenny*
103
+
104
+ * Fix `ActiveSupport::JSON.encode` to prevent duplicate keys.
105
+
106
+ If the same key exist in both String and Symbol form it could
107
+ lead to the same key being emitted twice.
108
+
109
+ *Manish Sharma*
110
+
111
+ * Fix `ActiveSupport::Cache::Store#read_multi` when using a cache namespace
112
+ and local cache strategy.
113
+
114
+ *Mark Oleson*
115
+
116
+ * Fix `Time.now/DateTime.now/Date.today` to return results in a system timezone after `#travel_to`.
117
+
118
+ There is a bug in the current implementation of #travel_to:
119
+ it remembers a timezone of its argument, and all stubbed methods start
120
+ returning results in that remembered timezone. However, the expected
121
+ behaviour is to return results in a system timezone.
122
+
123
+ *Aleksei Chernenkov*
124
+
125
+ * Fix `:unless_exist` option for `MemoryStore#write` (et al) when using a
126
+ cache namespace.
127
+
128
+ *S. Brent Faulkner*
129
+
130
+ * Fix ActiveSupport::Deprecation to handle blaming generated code.
131
+
132
+ *Jean Boussier*, *fatkodima*
133
+
134
+
1
135
  ## Rails 7.1.2 (November 10, 2023) ##
2
136
 
3
137
  * Fix `:expires_in` option for `RedisCacheStore#write_multi`.
@@ -33,6 +33,7 @@ module ActiveSupport
33
33
  class BacktraceCleaner
34
34
  def initialize
35
35
  @filters, @silencers = [], []
36
+ add_core_silencer
36
37
  add_gem_filter
37
38
  add_gem_silencer
38
39
  add_stdlib_silencer
@@ -116,6 +117,10 @@ module ActiveSupport
116
117
  add_filter { |line| line.sub(gems_regexp, gems_result) }
117
118
  end
118
119
 
120
+ def add_core_silencer
121
+ add_silencer { |line| line.include?("<internal:") }
122
+ end
123
+
119
124
  def add_gem_silencer
120
125
  add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) }
121
126
  end
@@ -113,33 +113,33 @@ module ActiveSupport
113
113
  dispatch { |logger| logger.<<(message) }
114
114
  end
115
115
 
116
- def add(*args, &block)
117
- dispatch { |logger| logger.add(*args, &block) }
116
+ def add(...)
117
+ dispatch { |logger| logger.add(...) }
118
118
  end
119
119
  alias_method :log, :add
120
120
 
121
- def debug(*args, &block)
122
- dispatch { |logger| logger.debug(*args, &block) }
121
+ def debug(...)
122
+ dispatch { |logger| logger.debug(...) }
123
123
  end
124
124
 
125
- def info(*args, &block)
126
- dispatch { |logger| logger.info(*args, &block) }
125
+ def info(...)
126
+ dispatch { |logger| logger.info(...) }
127
127
  end
128
128
 
129
- def warn(*args, &block)
130
- dispatch { |logger| logger.warn(*args, &block) }
129
+ def warn(...)
130
+ dispatch { |logger| logger.warn(...) }
131
131
  end
132
132
 
133
- def error(*args, &block)
134
- dispatch { |logger| logger.error(*args, &block) }
133
+ def error(...)
134
+ dispatch { |logger| logger.error(...) }
135
135
  end
136
136
 
137
- def fatal(*args, &block)
138
- dispatch { |logger| logger.fatal(*args, &block) }
137
+ def fatal(...)
138
+ dispatch { |logger| logger.fatal(...) }
139
139
  end
140
140
 
141
- def unknown(*args, &block)
142
- dispatch { |logger| logger.unknown(*args, &block) }
141
+ def unknown(...)
142
+ dispatch { |logger| logger.unknown(...) }
143
143
  end
144
144
 
145
145
  def formatter=(formatter)
@@ -229,6 +229,7 @@ module ActiveSupport
229
229
  private
230
230
  def dispatch(&block)
231
231
  @broadcasts.each { |logger| block.call(logger) }
232
+ true
232
233
  end
233
234
 
234
235
  def method_missing(name, *args, **kwargs, &block)
@@ -24,11 +24,11 @@ module ActiveSupport
24
24
  #
25
25
  # Special features:
26
26
  # - Clustering and load balancing. One can specify multiple memcached servers,
27
- # and MemCacheStore will load balance between all available servers. If a
28
- # server goes down, then MemCacheStore will ignore it until it comes back up.
27
+ # and +MemCacheStore+ will load balance between all available servers. If a
28
+ # server goes down, then +MemCacheStore+ will ignore it until it comes back up.
29
29
  #
30
- # MemCacheStore implements the Strategy::LocalCache strategy which implements
31
- # an in-memory cache inside of a block.
30
+ # +MemCacheStore+ implements the Strategy::LocalCache strategy which
31
+ # implements an in-memory cache inside of a block.
32
32
  class MemCacheStore < Store
33
33
  # These options represent behavior overridden by this implementation and should
34
34
  # not be allowed to get down to the Dalli client
@@ -106,14 +106,14 @@ module ActiveSupport
106
106
  end
107
107
  end
108
108
 
109
- # Creates a new MemCacheStore object, with the given memcached server
109
+ # Creates a new +MemCacheStore+ object, with the given memcached server
110
110
  # addresses. Each address is either a host name, or a host-with-port string
111
111
  # in the form of "host_name:port". For example:
112
112
  #
113
113
  # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
114
114
  #
115
115
  # If no addresses are provided, but <tt>ENV['MEMCACHE_SERVERS']</tt> is defined, it will be used instead. Otherwise,
116
- # MemCacheStore will connect to localhost:11211 (the default memcached port).
116
+ # +MemCacheStore+ will connect to localhost:11211 (the default memcached port).
117
117
  # Passing a +Dalli::Client+ instance is deprecated and will be removed. Please pass an address instead.
118
118
  def initialize(*addresses)
119
119
  addresses = addresses.flatten
@@ -18,13 +18,13 @@ module ActiveSupport
18
18
  # a cleanup will occur which tries to prune the cache down to three quarters
19
19
  # of the maximum size by removing the least recently used entries.
20
20
  #
21
- # Unlike other Cache store implementations, MemoryStore does not compress
22
- # values by default. MemoryStore does not benefit from compression as much
21
+ # Unlike other Cache store implementations, +MemoryStore+ does not compress
22
+ # values by default. +MemoryStore+ does not benefit from compression as much
23
23
  # as other Store implementations, as it does not send data over a network.
24
24
  # However, when compression is enabled, it still pays the full cost of
25
25
  # compression in terms of cpu use.
26
26
  #
27
- # MemoryStore is thread-safe.
27
+ # +MemoryStore+ is thread-safe.
28
28
  class MemoryStore < Store
29
29
  module DupCoder # :nodoc:
30
30
  extend self
@@ -209,7 +209,7 @@ module ActiveSupport
209
209
  def write_entry(key, entry, **options)
210
210
  payload = serialize_entry(entry, **options)
211
211
  synchronize do
212
- return false if options[:unless_exist] && exist?(key)
212
+ return false if options[:unless_exist] && exist?(key, namespace: nil)
213
213
 
214
214
  old_payload = @data[key]
215
215
  if old_payload
@@ -19,22 +19,23 @@ module ActiveSupport
19
19
  module Cache
20
20
  # = Redis \Cache \Store
21
21
  #
22
- # Deployment note: Take care to use a *dedicated Redis cache* rather
23
- # than pointing this at your existing Redis server. It won't cope well
24
- # with mixed usage patterns and it won't expire cache entries by default.
22
+ # Deployment note: Take care to use a <b>dedicated Redis cache</b> rather
23
+ # than pointing this at a persistent Redis server (for example, one used as
24
+ # an Active Job queue). Redis won't cope well with mixed usage patterns and it
25
+ # won't expire cache entries by default.
25
26
  #
26
27
  # Redis cache server setup guide: https://redis.io/topics/lru-cache
27
28
  #
28
- # * Supports vanilla Redis, hiredis, and Redis::Distributed.
29
- # * Supports Memcached-like sharding across Redises with Redis::Distributed.
29
+ # * Supports vanilla Redis, hiredis, and +Redis::Distributed+.
30
+ # * Supports Memcached-like sharding across Redises with +Redis::Distributed+.
30
31
  # * Fault tolerant. If the Redis server is unavailable, no exceptions are
31
32
  # raised. Cache fetches are all misses and writes are dropped.
32
33
  # * Local cache. Hot in-memory primary cache within block/middleware scope.
33
- # * +read_multi+ and +write_multi+ support for Redis mget/mset. Use Redis::Distributed
34
- # 4.0.1+ for distributed mget support.
34
+ # * +read_multi+ and +write_multi+ support for Redis mget/mset. Use
35
+ # +Redis::Distributed+ 4.0.1+ for distributed mget support.
35
36
  # * +delete_matched+ support for Redis KEYS globs.
36
37
  class RedisCacheStore < Store
37
- # Keys are truncated with the ActiveSupport digest if they exceed 1kB
38
+ # Keys are truncated with the Active Support digest if they exceed 1kB
38
39
  MAX_KEY_BYTESIZE = 1024
39
40
 
40
41
  DEFAULT_REDIS_OPTIONS = {
@@ -110,8 +111,11 @@ module ActiveSupport
110
111
 
111
112
  # Creates a new Redis cache store.
112
113
  #
113
- # Handles four options: :redis block, :redis instance, single :url
114
- # string, and multiple :url strings.
114
+ # There are four ways to provide the Redis client used by the cache: the
115
+ # +:redis+ param can be a Redis instance or a block that returns a Redis
116
+ # instance, or the +:url+ param can be a string or an array of strings
117
+ # which will be used to create a Redis instance or a +Redis::Distributed+
118
+ # instance.
115
119
  #
116
120
  # Option Class Result
117
121
  # :redis Proc -> options[:redis].call
@@ -134,7 +138,7 @@ module ActiveSupport
134
138
  #
135
139
  # Race condition TTL is not set by default. This can be used to avoid
136
140
  # "thundering herd" cache writes when hot cache entries are expired.
137
- # See <tt>ActiveSupport::Cache::Store#fetch</tt> for more.
141
+ # See ActiveSupport::Cache::Store#fetch for more.
138
142
  #
139
143
  # Setting <tt>skip_nil: true</tt> will not cache nil results:
140
144
  #
@@ -242,7 +246,7 @@ module ActiveSupport
242
246
  # Decrement a cached integer value using the Redis decrby atomic operator.
243
247
  # Returns the updated value.
244
248
  #
245
- # If the key is unset or has expired, it will be set to -amount:
249
+ # If the key is unset or has expired, it will be set to +-amount+:
246
250
  #
247
251
  # cache.decrement("foo") # => -1
248
252
  #
@@ -131,17 +131,20 @@ module ActiveSupport
131
131
  end
132
132
  end
133
133
 
134
- def read_multi_entries(keys, **options)
134
+ def read_multi_entries(names, **options)
135
135
  return super unless local_cache
136
136
 
137
- local_entries = local_cache.read_multi_entries(keys)
137
+ keys_to_names = names.index_by { |name| normalize_key(name, options) }
138
+
139
+ local_entries = local_cache.read_multi_entries(keys_to_names.keys)
140
+ local_entries.transform_keys! { |key| keys_to_names[key] }
138
141
  local_entries.transform_values! do |payload|
139
- deserialize_entry(payload)&.value
142
+ deserialize_entry(payload, **options)&.value
140
143
  end
141
- missed_keys = keys - local_entries.keys
144
+ missed_names = names - local_entries.keys
142
145
 
143
- if missed_keys.any?
144
- local_entries.merge!(super(missed_keys, **options))
146
+ if missed_names.any?
147
+ local_entries.merge!(super(missed_names, **options))
145
148
  else
146
149
  local_entries
147
150
  end
@@ -160,8 +160,8 @@ module ActiveSupport
160
160
  # Some implementations may not support all methods beyond the basic cache
161
161
  # methods of #fetch, #write, #read, #exist?, and #delete.
162
162
  #
163
- # ActiveSupport::Cache::Store can store any Ruby object that is supported by
164
- # its +coder+'s +dump+ and +load+ methods.
163
+ # +ActiveSupport::Cache::Store+ can store any Ruby object that is supported
164
+ # by its +coder+'s +dump+ and +load+ methods.
165
165
  #
166
166
  # cache = ActiveSupport::Cache::MemoryStore.new
167
167
  #
@@ -370,8 +370,8 @@ module ActiveSupport
370
370
  #
371
371
  # ==== Options
372
372
  #
373
- # Internally, +fetch+ calls #read_entry, and calls #write_entry on a cache
374
- # miss. Thus, +fetch+ supports the same options as #read and #write.
373
+ # Internally, +fetch+ calls +read_entry+, and calls +write_entry+ on a
374
+ # cache miss. Thus, +fetch+ supports the same options as #read and #write.
375
375
  # Additionally, +fetch+ supports the following options:
376
376
  #
377
377
  # * <tt>force: true</tt> - Forces a cache "miss," meaning we treat the
@@ -818,7 +818,7 @@ module ActiveSupport
818
818
  end
819
819
  end
820
820
 
821
- def deserialize_entry(payload)
821
+ def deserialize_entry(payload, **)
822
822
  payload.nil? ? nil : @coder.load(payload)
823
823
  rescue DeserializationError
824
824
  nil
@@ -1038,7 +1038,8 @@ module ActiveSupport
1038
1038
  # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
1039
1039
  # for a brief period while the entry is being recalculated.
1040
1040
  entry.expires_at = Time.now.to_f + race_ttl
1041
- write_entry(key, entry, expires_in: race_ttl * 2)
1041
+ options[:expires_in] = race_ttl * 2
1042
+ write_entry(key, entry, **options)
1042
1043
  else
1043
1044
  delete_entry(key, **options)
1044
1045
  end
@@ -1064,6 +1065,10 @@ module ActiveSupport
1064
1065
  end
1065
1066
  end
1066
1067
 
1068
+ # Enables the dynamic configuration of Cache entry options while ensuring
1069
+ # that conflicting options are not both set. When a block is given to
1070
+ # ActiveSupport::Cache::Store#fetch, the second argument will be an
1071
+ # instance of +WriteOptions+.
1067
1072
  class WriteOptions
1068
1073
  def initialize(options) # :nodoc:
1069
1074
  @options = options
@@ -1081,6 +1086,9 @@ module ActiveSupport
1081
1086
  @options[:expires_in]
1082
1087
  end
1083
1088
 
1089
+ # Sets the Cache entry's +expires_in+ value. If an +expires_at+ option was
1090
+ # previously set, this will unset it since +expires_in+ and +expires_at+
1091
+ # cannot both be set.
1084
1092
  def expires_in=(expires_in)
1085
1093
  @options.delete(:expires_at)
1086
1094
  @options[:expires_in] = expires_in
@@ -1090,6 +1098,9 @@ module ActiveSupport
1090
1098
  @options[:expires_at]
1091
1099
  end
1092
1100
 
1101
+ # Sets the Cache entry's +expires_at+ value. If an +expires_in+ option was
1102
+ # previously set, this will unset it since +expires_at+ and +expires_in+
1103
+ # cannot both be set.
1093
1104
  def expires_at=(expires_at)
1094
1105
  @options.delete(:expires_in)
1095
1106
  @options[:expires_at] = expires_at
@@ -9,16 +9,19 @@ module ActiveSupport
9
9
  @cache = METHOD_CACHES[namespace]
10
10
  @sources = []
11
11
  @methods = {}
12
+ @canonical_methods = {}
12
13
  end
13
14
 
14
- def define_cached_method(name, as: name)
15
- name = name.to_sym
16
- as = as.to_sym
17
- @methods.fetch(name) do
18
- unless @cache.method_defined?(as)
15
+ def define_cached_method(canonical_name, as: nil)
16
+ canonical_name = canonical_name.to_sym
17
+ as = (as || canonical_name).to_sym
18
+
19
+ @methods.fetch(as) do
20
+ unless @cache.method_defined?(canonical_name) || @canonical_methods[canonical_name]
19
21
  yield @sources
20
22
  end
21
- @methods[name] = as
23
+ @canonical_methods[canonical_name] = true
24
+ @methods[as] = canonical_name
22
25
  end
23
26
  end
24
27
 
@@ -26,8 +29,10 @@ module ActiveSupport
26
29
  unless @sources.empty?
27
30
  @cache.module_eval("# frozen_string_literal: true\n" + @sources.join(";"), path, line)
28
31
  end
29
- @methods.each do |name, as|
30
- owner.define_method(name, @cache.instance_method(as))
32
+ @canonical_methods.clear
33
+
34
+ @methods.each do |as, canonical_name|
35
+ owner.define_method(as, @cache.instance_method(canonical_name))
31
36
  end
32
37
  end
33
38
  end
@@ -52,8 +57,8 @@ module ActiveSupport
52
57
  @namespaces = Hash.new { |h, k| h[k] = MethodSet.new(k) }
53
58
  end
54
59
 
55
- def define_cached_method(name, namespace:, as: name, &block)
56
- @namespaces[namespace].define_cached_method(name, as: as, &block)
60
+ def define_cached_method(canonical_name, namespace:, as: nil, &block)
61
+ @namespaces[namespace].define_cached_method(canonical_name, as: as, &block)
57
62
  end
58
63
 
59
64
  def execute
@@ -317,37 +317,52 @@ class Module
317
317
  # of <tt>object</tt> add or remove instance variables.
318
318
  def delegate_missing_to(target, allow_nil: nil)
319
319
  target = target.to_s
320
- target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
320
+ target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target) || target == "__target"
321
321
 
322
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
323
- def respond_to_missing?(name, include_private = false)
324
- # It may look like an oversight, but we deliberately do not pass
325
- # +include_private+, because they do not get delegated.
322
+ if allow_nil
323
+ module_eval <<~RUBY, __FILE__, __LINE__ + 1
324
+ def respond_to_missing?(name, include_private = false)
325
+ # It may look like an oversight, but we deliberately do not pass
326
+ # +include_private+, because they do not get delegated.
326
327
 
327
- return false if name == :marshal_dump || name == :_dump
328
- #{target}.respond_to?(name) || super
329
- end
328
+ return false if name == :marshal_dump || name == :_dump
329
+ #{target}.respond_to?(name) || super
330
+ end
330
331
 
331
- def method_missing(method, *args, &block)
332
- if #{target}.respond_to?(method)
333
- #{target}.public_send(method, *args, &block)
334
- else
335
- begin
332
+ def method_missing(method, *args, &block)
333
+ __target = #{target}
334
+ if __target.nil? && !nil.respond_to?(method)
335
+ nil
336
+ elsif __target.respond_to?(method)
337
+ __target.public_send(method, *args, &block)
338
+ else
336
339
  super
337
- rescue NoMethodError
338
- if #{target}.nil?
339
- if #{allow_nil == true}
340
- nil
341
- else
342
- raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
343
- end
344
- else
345
- raise
346
- end
347
340
  end
348
341
  end
349
- end
350
- ruby2_keywords(:method_missing)
351
- RUBY
342
+ ruby2_keywords(:method_missing)
343
+ RUBY
344
+ else
345
+ module_eval <<~RUBY, __FILE__, __LINE__ + 1
346
+ def respond_to_missing?(name, include_private = false)
347
+ # It may look like an oversight, but we deliberately do not pass
348
+ # +include_private+, because they do not get delegated.
349
+
350
+ return false if name == :marshal_dump || name == :_dump
351
+ #{target}.respond_to?(name) || super
352
+ end
353
+
354
+ def method_missing(method, *args, &block)
355
+ __target = #{target}
356
+ if __target.nil? && !nil.respond_to?(method)
357
+ raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
358
+ elsif __target.respond_to?(method)
359
+ __target.public_send(method, *args, &block)
360
+ else
361
+ super
362
+ end
363
+ end
364
+ ruby2_keywords(:method_missing)
365
+ RUBY
366
+ end
352
367
  end
353
368
  end
@@ -28,23 +28,32 @@ class Object
28
28
  end
29
29
  end
30
30
 
31
- class Method
32
- # Methods are not duplicable:
33
- #
34
- # method(:puts).duplicable? # => false
35
- # method(:puts).dup # => TypeError: allocator undefined for Method
36
- def duplicable?
37
- false
38
- end
31
+ methods_are_duplicable = begin
32
+ Object.instance_method(:duplicable?).dup
33
+ true
34
+ rescue TypeError
35
+ false
39
36
  end
40
37
 
41
- class UnboundMethod
42
- # Unbound methods are not duplicable:
43
- #
44
- # method(:puts).unbind.duplicable? # => false
45
- # method(:puts).unbind.dup # => TypeError: allocator undefined for UnboundMethod
46
- def duplicable?
47
- false
38
+ unless methods_are_duplicable
39
+ class Method
40
+ # Methods are not duplicable:
41
+ #
42
+ # method(:puts).duplicable? # => false
43
+ # method(:puts).dup # => TypeError: allocator undefined for Method
44
+ def duplicable?
45
+ false
46
+ end
47
+ end
48
+
49
+ class UnboundMethod
50
+ # Unbound methods are not duplicable:
51
+ #
52
+ # method(:puts).unbind.duplicable? # => false
53
+ # method(:puts).unbind.dup # => TypeError: allocator undefined for UnboundMethod
54
+ def duplicable?
55
+ false
56
+ end
48
57
  end
49
58
  end
50
59
 
@@ -233,9 +233,11 @@ class Pathname # :nodoc:
233
233
  end
234
234
  end
235
235
 
236
- class IPAddr # :nodoc:
237
- def as_json(options = nil)
238
- to_s
236
+ unless IPAddr.method_defined?(:as_json, false)
237
+ class IPAddr # :nodoc:
238
+ def as_json(options = nil)
239
+ to_s
240
+ end
239
241
  end
240
242
  end
241
243
 
@@ -68,7 +68,7 @@ class Object
68
68
  # You can access these methods using the class name instead:
69
69
  #
70
70
  # class Phone < ActiveRecord::Base
71
- # enum phone_number_type: { home: 0, office: 1, mobile: 2 }
71
+ # enum :phone_number_type, { home: 0, office: 1, mobile: 2 }
72
72
  #
73
73
  # with_options presence: true do
74
74
  # validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys }
@@ -24,7 +24,7 @@ class String
24
24
  #
25
25
  # The second argument, +indent_string+, specifies which indent string to
26
26
  # use. The default is +nil+, which tells the method to make a guess by
27
- # peeking at the first indented line, and fallback to a space if there is
27
+ # peeking at the first indented line, and fall back to a space if there is
28
28
  # none.
29
29
  #
30
30
  # " foo".indent(2) # => " foo"