activesupport 7.1.3.4 → 8.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -1136
  3. data/lib/active_support/array_inquirer.rb +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +15 -3
  5. data/lib/active_support/benchmark.rb +21 -0
  6. data/lib/active_support/benchmarkable.rb +3 -2
  7. data/lib/active_support/broadcast_logger.rb +19 -18
  8. data/lib/active_support/cache/file_store.rb +27 -12
  9. data/lib/active_support/cache/mem_cache_store.rb +16 -74
  10. data/lib/active_support/cache/memory_store.rb +8 -3
  11. data/lib/active_support/cache/redis_cache_store.rb +21 -15
  12. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  13. data/lib/active_support/cache.rb +76 -78
  14. data/lib/active_support/callbacks.rb +79 -116
  15. data/lib/active_support/class_attribute.rb +33 -0
  16. data/lib/active_support/code_generator.rb +24 -10
  17. data/lib/active_support/concurrency/share_lock.rb +0 -1
  18. data/lib/active_support/configuration_file.rb +15 -6
  19. data/lib/active_support/core_ext/array/conversions.rb +3 -5
  20. data/lib/active_support/core_ext/benchmark.rb +6 -9
  21. data/lib/active_support/core_ext/class/attribute.rb +24 -20
  22. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  23. data/lib/active_support/core_ext/date/blank.rb +4 -0
  24. data/lib/active_support/core_ext/date/conversions.rb +2 -2
  25. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  26. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  27. data/lib/active_support/core_ext/date_time/conversions.rb +0 -4
  28. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  29. data/lib/active_support/core_ext/enumerable.rb +8 -3
  30. data/lib/active_support/core_ext/erb/util.rb +7 -2
  31. data/lib/active_support/core_ext/hash/except.rb +0 -12
  32. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  33. data/lib/active_support/core_ext/module/attr_internal.rb +16 -6
  34. data/lib/active_support/core_ext/module/delegation.rb +20 -148
  35. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  36. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  37. data/lib/active_support/core_ext/object/blank.rb +45 -1
  38. data/lib/active_support/core_ext/object/duplicable.rb +24 -15
  39. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  40. data/lib/active_support/core_ext/object/json.rb +21 -13
  41. data/lib/active_support/core_ext/object/with.rb +5 -3
  42. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  43. data/lib/active_support/core_ext/range/overlap.rb +1 -1
  44. data/lib/active_support/core_ext/securerandom.rb +4 -4
  45. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  46. data/lib/active_support/core_ext/string/filters.rb +1 -1
  47. data/lib/active_support/core_ext/string/multibyte.rb +1 -1
  48. data/lib/active_support/core_ext/string/output_safety.rb +0 -7
  49. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  50. data/lib/active_support/core_ext/time/calculations.rb +32 -30
  51. data/lib/active_support/core_ext/time/compatibility.rb +24 -0
  52. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  53. data/lib/active_support/core_ext/time/zones.rb +1 -1
  54. data/lib/active_support/core_ext.rb +0 -1
  55. data/lib/active_support/current_attributes.rb +38 -40
  56. data/lib/active_support/delegation.rb +200 -0
  57. data/lib/active_support/dependencies/autoload.rb +0 -12
  58. data/lib/active_support/dependencies.rb +0 -1
  59. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  60. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  61. data/lib/active_support/deprecation/reporting.rb +3 -17
  62. data/lib/active_support/deprecation.rb +8 -5
  63. data/lib/active_support/descendants_tracker.rb +9 -87
  64. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  65. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  66. data/lib/active_support/duration.rb +25 -16
  67. data/lib/active_support/encrypted_configuration.rb +20 -2
  68. data/lib/active_support/encrypted_file.rb +1 -1
  69. data/lib/active_support/error_reporter.rb +65 -3
  70. data/lib/active_support/evented_file_update_checker.rb +0 -2
  71. data/lib/active_support/execution_wrapper.rb +0 -1
  72. data/lib/active_support/file_update_checker.rb +1 -1
  73. data/lib/active_support/fork_tracker.rb +2 -38
  74. data/lib/active_support/gem_version.rb +4 -4
  75. data/lib/active_support/hash_with_indifferent_access.rb +21 -23
  76. data/lib/active_support/html_safe_translation.rb +7 -4
  77. data/lib/active_support/i18n_railtie.rb +19 -11
  78. data/lib/active_support/isolated_execution_state.rb +0 -2
  79. data/lib/active_support/json/encoding.rb +3 -3
  80. data/lib/active_support/log_subscriber.rb +1 -12
  81. data/lib/active_support/logger.rb +15 -2
  82. data/lib/active_support/logger_thread_safe_level.rb +0 -8
  83. data/lib/active_support/message_pack/extensions.rb +15 -2
  84. data/lib/active_support/message_verifier.rb +12 -0
  85. data/lib/active_support/messages/codec.rb +1 -1
  86. data/lib/active_support/multibyte/chars.rb +2 -2
  87. data/lib/active_support/notifications/fanout.rb +4 -8
  88. data/lib/active_support/notifications/instrumenter.rb +32 -21
  89. data/lib/active_support/notifications.rb +28 -27
  90. data/lib/active_support/number_helper/number_converter.rb +2 -2
  91. data/lib/active_support/number_helper.rb +22 -0
  92. data/lib/active_support/option_merger.rb +2 -2
  93. data/lib/active_support/ordered_options.rb +53 -15
  94. data/lib/active_support/railtie.rb +8 -11
  95. data/lib/active_support/string_inquirer.rb +1 -1
  96. data/lib/active_support/subscriber.rb +1 -0
  97. data/lib/active_support/syntax_error_proxy.rb +1 -11
  98. data/lib/active_support/tagged_logging.rb +9 -1
  99. data/lib/active_support/test_case.rb +3 -1
  100. data/lib/active_support/testing/assertions.rb +79 -21
  101. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  102. data/lib/active_support/testing/deprecation.rb +5 -12
  103. data/lib/active_support/testing/isolation.rb +19 -9
  104. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  105. data/lib/active_support/testing/parallelization/server.rb +3 -0
  106. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  107. data/lib/active_support/testing/strict_warnings.rb +8 -4
  108. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  109. data/lib/active_support/testing/time_helpers.rb +4 -3
  110. data/lib/active_support/time_with_zone.rb +30 -17
  111. data/lib/active_support/values/time_zone.rb +25 -14
  112. data/lib/active_support/xml_mini.rb +11 -2
  113. data/lib/active_support.rb +12 -4
  114. metadata +68 -19
  115. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  116. data/lib/active_support/proxy_object.rb +0 -17
  117. data/lib/active_support/ruby_features.rb +0 -7
@@ -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
@@ -50,16 +55,25 @@ module ActiveSupport
50
55
  @path = path
51
56
  @line = line
52
57
  @namespaces = Hash.new { |h, k| h[k] = MethodSet.new(k) }
58
+ @sources = []
59
+ end
60
+
61
+ def class_eval
62
+ yield @sources
53
63
  end
54
64
 
55
- def define_cached_method(name, namespace:, as: name, &block)
56
- @namespaces[namespace].define_cached_method(name, as: as, &block)
65
+ def define_cached_method(canonical_name, namespace:, as: nil, &block)
66
+ @namespaces[namespace].define_cached_method(canonical_name, as: as, &block)
57
67
  end
58
68
 
59
69
  def execute
60
70
  @namespaces.each_value do |method_set|
61
71
  method_set.apply(@owner, @path, @line - 1)
62
72
  end
73
+
74
+ unless @sources.empty?
75
+ @owner.class_eval("# frozen_string_literal: true\n" + @sources.join(";"), @path, @line - 1)
76
+ end
63
77
  end
64
78
  end
65
79
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "monitor"
5
4
 
6
5
  module ActiveSupport
@@ -19,11 +19,20 @@ module ActiveSupport
19
19
  end
20
20
 
21
21
  def parse(context: nil, **options)
22
- source = render(context)
23
- if YAML.respond_to?(:unsafe_load)
24
- YAML.unsafe_load(source, **options) || {}
22
+ source = @content.include?("<%") ? render(context) : @content
23
+
24
+ if source == @content
25
+ if YAML.respond_to?(:unsafe_load)
26
+ YAML.unsafe_load_file(@content_path, **options) || {}
27
+ else
28
+ YAML.load_file(@content_path, **options) || {}
29
+ end
25
30
  else
26
- YAML.load(source, **options) || {}
31
+ if YAML.respond_to?(:unsafe_load)
32
+ YAML.unsafe_load(source, **options) || {}
33
+ else
34
+ YAML.load(source, **options) || {}
35
+ end
27
36
  end
28
37
  rescue Psych::SyntaxError => error
29
38
  raise "YAML syntax error occurred while parsing #{@content_path}. " \
@@ -33,8 +42,7 @@ module ActiveSupport
33
42
 
34
43
  private
35
44
  def read(content_path)
36
- require "yaml"
37
- require "erb"
45
+ require "yaml" unless defined?(YAML)
38
46
 
39
47
  File.read(content_path).tap do |content|
40
48
  if content.include?("\u00A0")
@@ -44,6 +52,7 @@ module ActiveSupport
44
52
  end
45
53
 
46
54
  def render(context)
55
+ require "erb" unless defined?(ERB)
47
56
  erb = ERB.new(@content).tap { |e| e.filename = @content_path }
48
57
  context ? erb.result(context) : erb.result
49
58
  end
@@ -16,11 +16,11 @@ class Array
16
16
  # ==== Options
17
17
  #
18
18
  # * <tt>:words_connector</tt> - The sign or word used to join all but the last
19
- # element in arrays with three or more elements (default: ", ").
19
+ # element in arrays with three or more elements (default: <tt>", "</tt>).
20
20
  # * <tt>:last_word_connector</tt> - The sign or word used to join the last element
21
- # in arrays with three or more elements (default: ", and ").
21
+ # in arrays with three or more elements (default: <tt>", and "</tt>).
22
22
  # * <tt>:two_words_connector</tt> - The sign or word used to join the elements
23
- # in arrays with two elements (default: " and ").
23
+ # in arrays with two elements (default: <tt>" and "</tt>).
24
24
  # * <tt>:locale</tt> - If +i18n+ is available, you can set a locale and use
25
25
  # the connector options defined on the 'support.array' namespace in the
26
26
  # corresponding dictionary file.
@@ -104,8 +104,6 @@ class Array
104
104
  end
105
105
  end
106
106
  alias_method :to_formatted_s, :to_fs
107
- alias_method :to_default_s, :to_s
108
- deprecate to_default_s: :to_s, deprecator: ActiveSupport.deprecator
109
107
 
110
108
  # Returns a string that represents the array in XML by invoking +to_xml+
111
109
  # on each element. Active Record collections delegate their representation
@@ -3,14 +3,11 @@
3
3
  require "benchmark"
4
4
 
5
5
  class << Benchmark
6
- # Benchmark realtime in milliseconds.
7
- #
8
- # Benchmark.realtime { User.all }
9
- # # => 8.0e-05
10
- #
11
- # Benchmark.ms { User.all }
12
- # # => 0.074
13
- def ms(&block)
14
- 1000 * realtime(&block)
6
+ def ms(&block) # :nodoc
7
+ # NOTE: Please also remove the Active Support `benchmark` dependency when removing this
8
+ ActiveSupport.deprecator.warn <<~TEXT
9
+ `Benchmark.ms` is deprecated and will be removed in Rails 8.1 without replacement.
10
+ TEXT
11
+ ActiveSupport::Benchmark.realtime(:float_millisecond, &block)
15
12
  end
16
13
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/module/redefine_method"
4
+ require "active_support/class_attribute"
4
5
 
5
6
  class Class
6
7
  # Declare a class-level attribute whose value is inheritable by subclasses.
@@ -83,32 +84,37 @@ class Class
83
84
  #
84
85
  # class_attribute :settings, default: {}
85
86
  def class_attribute(*attrs, instance_accessor: true,
86
- instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil)
87
-
87
+ instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil
88
+ )
88
89
  class_methods, methods = [], []
89
90
  attrs.each do |name|
90
91
  unless name.is_a?(Symbol) || name.is_a?(String)
91
92
  raise TypeError, "#{name.inspect} is not a symbol nor a string"
92
93
  end
93
94
 
94
- class_methods << <<~RUBY # In case the method exists and is not public
95
- silence_redefinition_of_method def #{name}
96
- end
97
- RUBY
95
+ name = name.to_sym
96
+ namespaced_name = :"__class_attr_#{name}"
97
+ ::ActiveSupport::ClassAttribute.redefine(self, name, namespaced_name, default)
98
98
 
99
- methods << <<~RUBY if instance_reader
100
- silence_redefinition_of_method def #{name}
101
- defined?(@#{name}) ? @#{name} : self.class.#{name}
102
- end
103
- RUBY
99
+ delegators = [
100
+ "def #{name}; #{namespaced_name}; end",
101
+ "def #{name}=(value); self.#{namespaced_name} = value; end",
102
+ ]
104
103
 
105
- class_methods << <<~RUBY
106
- silence_redefinition_of_method def #{name}=(value)
107
- redefine_method(:#{name}) { value } if singleton_class?
108
- redefine_singleton_method(:#{name}) { value }
109
- value
110
- end
111
- RUBY
104
+ class_methods.concat(delegators)
105
+ if singleton_class?
106
+ methods.concat(delegators)
107
+ else
108
+ methods << <<~RUBY if instance_reader
109
+ silence_redefinition_of_method def #{name}
110
+ if defined?(@#{name})
111
+ @#{name}
112
+ else
113
+ self.class.#{name}
114
+ end
115
+ end
116
+ RUBY
117
+ end
112
118
 
113
119
  methods << <<~RUBY if instance_writer
114
120
  silence_redefinition_of_method(:#{name}=)
@@ -125,7 +131,5 @@ class Class
125
131
 
126
132
  location = caller_locations(1, 1).first
127
133
  class_eval(["class << self", *class_methods, "end", *methods].join(";").tr("\n", ";"), location.path, location.lineno)
128
-
129
- attrs.each { |name| public_send("#{name}=", default) }
130
134
  end
131
135
  end
@@ -1,43 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/ruby_features"
4
3
  require "active_support/descendants_tracker"
5
4
 
6
5
  class Class
7
- if ActiveSupport::RubyFeatures::CLASS_SUBCLASSES
8
- # Returns an array with all classes that are < than its receiver.
9
- #
10
- # class C; end
11
- # C.descendants # => []
12
- #
13
- # class B < C; end
14
- # C.descendants # => [B]
15
- #
16
- # class A < B; end
17
- # C.descendants # => [B, A]
18
- #
19
- # class D < C; end
20
- # C.descendants # => [B, A, D]
21
- def descendants
22
- subclasses.concat(subclasses.flat_map(&:descendants))
23
- end
24
- else
25
- def descendants
26
- ObjectSpace.each_object(singleton_class).reject do |k|
27
- k.singleton_class? || k == self
28
- end
29
- end
30
-
31
- # Returns an array with the direct children of +self+.
32
- #
33
- # class Foo; end
34
- # class Bar < Foo; end
35
- # class Baz < Bar; end
36
- #
37
- # Foo.subclasses # => [Bar]
38
- def subclasses
39
- descendants.select { |descendant| descendant.superclass == self }
40
- end
6
+ # Returns an array with all classes that are < than its receiver.
7
+ #
8
+ # class C; end
9
+ # C.descendants # => []
10
+ #
11
+ # class B < C; end
12
+ # C.descendants # => [B]
13
+ #
14
+ # class A < B; end
15
+ # C.descendants # => [B, A]
16
+ #
17
+ # class D < C; end
18
+ # C.descendants # => [B, A, D]
19
+ def descendants
20
+ subclasses.concat(subclasses.flat_map(&:descendants))
41
21
  end
42
22
 
43
23
  prepend ActiveSupport::DescendantsTracker::ReloadedClassesFiltering
@@ -11,4 +11,8 @@ class Date # :nodoc:
11
11
  def blank?
12
12
  false
13
13
  end
14
+
15
+ def present?
16
+ true
17
+ end
14
18
  end
@@ -17,6 +17,7 @@ class Date
17
17
  date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007"
18
18
  },
19
19
  rfc822: "%d %b %Y",
20
+ rfc2822: "%d %b %Y",
20
21
  iso8601: lambda { |date| date.iso8601 }
21
22
  }
22
23
 
@@ -34,6 +35,7 @@ class Date
34
35
  # date.to_fs(:long) # => "November 10, 2007"
35
36
  # date.to_fs(:long_ordinal) # => "November 10th, 2007"
36
37
  # date.to_fs(:rfc822) # => "10 Nov 2007"
38
+ # date.to_fs(:rfc2822) # => "10 Nov 2007"
37
39
  # date.to_fs(:iso8601) # => "2007-11-10"
38
40
  #
39
41
  # == Adding your own date formats to to_fs
@@ -56,8 +58,6 @@ class Date
56
58
  end
57
59
  end
58
60
  alias_method :to_formatted_s, :to_fs
59
- alias_method :to_default_s, :to_s
60
- deprecate to_default_s: :to_s, deprecator: ActiveSupport.deprecator
61
61
 
62
62
  # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005"
63
63
  def readable_inspect
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/module/attribute_accessors"
4
+ require "active_support/core_ext/module/redefine_method"
4
5
 
5
6
  module DateAndTime
6
7
  module Compatibility
@@ -11,7 +12,33 @@ module DateAndTime
11
12
  # of the receiver. For backwards compatibility we're overriding
12
13
  # this behavior, but new apps will have an initializer that sets
13
14
  # this to true, because the new behavior is preferred.
14
- mattr_accessor :preserve_timezone, instance_writer: false, default: false
15
+ mattr_accessor :preserve_timezone, instance_accessor: false, default: nil
16
+
17
+ singleton_class.silence_redefinition_of_method :preserve_timezone
18
+
19
+ #--
20
+ # This re-implements the behaviour of the mattr_reader, instead
21
+ # of prepending on to it, to avoid overcomplicating a module that
22
+ # is in turn included in several places. This will all go away in
23
+ # Rails 8.0 anyway.
24
+ def self.preserve_timezone # :nodoc:
25
+ if @@preserve_timezone.nil?
26
+ # Only warn once, the first time the value is used (which should
27
+ # be the first time #to_time is called).
28
+ ActiveSupport.deprecator.warn(
29
+ "`to_time` will always preserve the receiver timezone rather than system local time in Rails 8.1." \
30
+ "To opt in to the new behavior, set `config.active_support.to_time_preserves_timezone = :zone`."
31
+ )
32
+
33
+ @@preserve_timezone = false
34
+ end
35
+
36
+ @@preserve_timezone
37
+ end
38
+
39
+ def preserve_timezone # :nodoc:
40
+ Compatibility.preserve_timezone
41
+ end
15
42
 
16
43
  # Change the output of <tt>ActiveSupport::TimeZone.utc_to_local</tt>.
17
44
  #
@@ -11,4 +11,8 @@ class DateTime # :nodoc:
11
11
  def blank?
12
12
  false
13
13
  end
14
+
15
+ def present?
16
+ true
17
+ end
14
18
  end
@@ -40,10 +40,6 @@ class DateTime
40
40
  end
41
41
  end
42
42
  alias_method :to_formatted_s, :to_fs
43
- if instance_methods(false).include?(:to_s)
44
- alias_method :to_default_s, :to_s
45
- deprecate to_default_s: :to_s, deprecator: ActiveSupport.deprecator
46
- end
47
43
 
48
44
 
49
45
  # Returns a formatted string of the offset from UTC, or an alternative
@@ -53,6 +53,12 @@ module Digest
53
53
  SecureRandom.uuid
54
54
  end
55
55
 
56
+ # Returns the nil UUID. This is a special form of UUID that is specified to
57
+ # have all 128 bits set to zero.
58
+ def self.nil_uuid
59
+ "00000000-0000-0000-0000-000000000000"
60
+ end
61
+
56
62
  def self.pack_uuid_namespace(namespace)
57
63
  if [DNS_NAMESPACE, OID_NAMESPACE, URL_NAMESPACE, X500_NAMESPACE].include?(namespace)
58
64
  namespace
@@ -192,9 +192,14 @@ module Enumerable
192
192
  # # => [ Person.find(1), Person.find(5), Person.find(3) ]
193
193
  #
194
194
  # If the +series+ include keys that have no corresponding element in the Enumerable, these are ignored.
195
- # If the Enumerable has additional elements that aren't named in the +series+, these are not included in the result.
196
- def in_order_of(key, series)
197
- group_by(&key).values_at(*series).flatten(1).compact
195
+ # If the Enumerable has additional elements that aren't named in the +series+, these are not included in the result, unless
196
+ # the +filter+ option is set to +false+.
197
+ def in_order_of(key, series, filter: true)
198
+ if filter
199
+ group_by(&key).values_at(*series).flatten(1).compact
200
+ else
201
+ sort_by { |v| series.index(v.public_send(key)) || series.size }.compact
202
+ end
198
203
  end
199
204
 
200
205
  # Returns the sole item in the enumerable. If there are no items, or more
@@ -174,7 +174,7 @@ class ERB
174
174
 
175
175
  case source.matched
176
176
  when start_re
177
- tokens << [:TEXT, source.string[pos, len]] if len > 0
177
+ tokens << [:TEXT, source.string.byteslice(pos, len)] if len > 0
178
178
  tokens << [:OPEN, source.matched]
179
179
  if source.scan(/(.*?)(?=#{finish_re}|\z)/m)
180
180
  tokens << [:CODE, source.matched] unless source.matched.empty?
@@ -183,11 +183,16 @@ class ERB
183
183
  raise NotImplementedError
184
184
  end
185
185
  when finish_re
186
- tokens << [:CODE, source.string[pos, len]] if len > 0
186
+ tokens << [:CODE, source.string.byteslice(pos, len)] if len > 0
187
187
  tokens << [:CLOSE, source.matched]
188
188
  else
189
189
  raise NotImplementedError, source.matched
190
190
  end
191
+
192
+ unless source.eos? || source.exist?(start_re) || source.exist?(finish_re)
193
+ tokens << [:TEXT, source.rest]
194
+ source.terminate
195
+ end
191
196
  end
192
197
 
193
198
  tokens
@@ -1,18 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Hash
4
- # Returns a hash that includes everything except given keys.
5
- # hash = { a: true, b: false, c: nil }
6
- # hash.except(:c) # => { a: true, b: false }
7
- # hash.except(:a, :b) # => { c: nil }
8
- # hash # => { a: true, b: false, c: nil }
9
- #
10
- # This is useful for limiting a set of parameters to everything but a few known toggles:
11
- # @person.update(params[:person].except(:admin))
12
- def except(*keys)
13
- slice(*self.keys - keys)
14
- end unless method_defined?(:except)
15
-
16
4
  # Removes the given keys from hash and returns it.
17
5
  # hash = { a: true, b: false, c: nil }
18
6
  # hash.except!(:c) # => { a: true, b: false }
@@ -8,13 +8,13 @@ class Hash
8
8
  # hash.stringify_keys
9
9
  # # => {"name"=>"Rob", "age"=>"28"}
10
10
  def stringify_keys
11
- transform_keys(&:to_s)
11
+ transform_keys { |k| Symbol === k ? k.name : k.to_s }
12
12
  end
13
13
 
14
14
  # Destructively converts all keys to strings. Same as
15
15
  # +stringify_keys+, but modifies +self+.
16
16
  def stringify_keys!
17
- transform_keys!(&:to_s)
17
+ transform_keys! { |k| Symbol === k ? k.name : k.to_s }
18
18
  end
19
19
 
20
20
  # Returns a new hash with all keys converted to symbols, as long as
@@ -82,14 +82,14 @@ class Hash
82
82
  # hash.deep_stringify_keys
83
83
  # # => {"person"=>{"name"=>"Rob", "age"=>"28"}}
84
84
  def deep_stringify_keys
85
- deep_transform_keys(&:to_s)
85
+ deep_transform_keys { |k| Symbol === k ? k.name : k.to_s }
86
86
  end
87
87
 
88
88
  # Destructively converts all keys to strings.
89
89
  # This includes the keys from the root hash and from all
90
90
  # nested hashes and arrays.
91
91
  def deep_stringify_keys!
92
- deep_transform_keys!(&:to_s)
92
+ deep_transform_keys! { |k| Symbol === k ? k.name : k.to_s }
93
93
  end
94
94
 
95
95
  # Returns a new hash with all keys converted to symbols, as long as
@@ -19,16 +19,26 @@ class Module
19
19
  end
20
20
  alias_method :attr_internal, :attr_internal_accessor
21
21
 
22
- class << self; attr_accessor :attr_internal_naming_format end
23
- self.attr_internal_naming_format = "@_%s"
22
+ class << self
23
+ attr_reader :attr_internal_naming_format
24
24
 
25
- private
26
- def attr_internal_ivar_name(attr)
27
- Module.attr_internal_naming_format % attr
25
+ def attr_internal_naming_format=(format)
26
+ if format.start_with?("@")
27
+ raise ArgumentError, <<~MESSAGE.squish
28
+ Setting `attr_internal_naming_format` with a `@` prefix is not supported.
29
+
30
+ You can simply replace #{format.inspect} by #{format.delete_prefix("@").inspect}.
31
+ MESSAGE
32
+ end
33
+
34
+ @attr_internal_naming_format = format
28
35
  end
36
+ end
37
+ self.attr_internal_naming_format = "_%s"
29
38
 
39
+ private
30
40
  def attr_internal_define(attr_name, type)
31
- internal_name = attr_internal_ivar_name(attr_name).delete_prefix("@")
41
+ internal_name = Module.attr_internal_naming_format % attr_name
32
42
  # use native attr_* methods as they are faster on some Ruby implementations
33
43
  public_send("attr_#{type}", internal_name)
34
44
  attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer