Ruby 2.5

  • Released at: Dec 25, 2017 (NEWS file)
  • Status (as of Feb 07, 2023): EOL, latest is 2.5.9
  • This document first published: Jun 6, 2019
  • Last change to this document: Feb 07, 2023

Highlights

Language

Top-level constant look-up is removed.

Foo::Bar doesn’t fallback to ::Bar anymore, when Foo::Bar is not found

  • Reason: The behavior was considered to be bringing more problems and bugs than benefits for a long time, and always issued a warning.
  • Discussion: Feature #11547
  • Code:
    Hash::String
    # Ruby 2.4:
    #   (irb):1: warning: toplevel constant String referenced by Hash::String
    #    => String
    # Ruby 2.5:
    #   NameError (uninitialized constant Hash::String)
    

rescue/else/ensure are allowed inside blocks

  • Reason: for consistency with other something / end constructs (class and method bodies), which allowed rescue without wrapping in additional begin/end
  • Discussion: Feature #12906
  • Code:
    (0..5).map do |val|
      puts 100 / val
    rescue ZeroDivisionError
      puts '<undividable>'
    end
    # prints:
    #  <undividable>
    #  100
    #  50
    #  33
    #  25
    #  20
    
  • Notice: The syntax is not available for {} blocks, even multiline ones.

Refinements work in string interpolations

  • Reason: Step in a consistent effort to make refinements more useful and consistent, working everywhere regular methods work
  • Discussion: Feature #13812 (Japanese)
  • Code:
    module Ref
      refine Time do
        def to_s
          "#{hour}:#{min} at #{month}/#{day}/#{year}"
        end
      end
    end
    
    using Ref
    
    puts "Now is #{Time.now}"
    # Ruby 2.4: "Now is 2019-05-27 17:49:27 +0300" -- default to_s
    # Ruby 2.5: "Now is 17:51 at 5/27/2019" -- refined to_s
    
  • Follow-up: Ruby 2.6 added more features making refinements available in various contexts.

Core classes and modules

Kernel

Note that methods defined in Kernel are typically available on any object, that’s why these changes are important.

#yield_self

  • Reason: Chaining singular value processing the same way Enumerable allows chaining of sequence processing methods.
  • Discussion: Feature #6721
  • Code:
    [BASE_URL, path].join('/')
        .yield_self { |url| open(url).read }
        .yield_self { |body| JSON.parse(body, symbolyze_names: true) }
        .dig(:data, :items)
        .yield_self { |items| File.write('response.yml', items.to_yaml) }
    
  • Follow-up: Ruby 2.6 renamed (aliased) unfortunate method name to #then

#pp

pp standard library is now automatically required on the first call to Kernel#pp

#warn: uplevel: keyword argument

When uplevel: N provided to Kernel#warn, the warning message includes the file and line of the call N steps up in the callstack.

  • Reason: It is sometimes hard to understand exactly what code caused a warning. This feature helps identify the offending caller.
  • Discussion: Feature #12882
  • Documentation: Kernel#warn (it became somewhat documented at Ruby 2.6, but the feature itself has been available since 2.5)
  • Code:
    def that_bad_function
      warn('do not call me')
      warn('I am talking to YOU', uplevel: 1)
      warn('and tell all your clients!', uplevel: 2)
    end
    
    def try_using_it
      that_bad_function # line 8
    end
    
    try_using_it # line 11
    
    # Prints:
    #  do not call me
    #  test.rb:8: warning: I am talking to YOU
    #  test.rb:11: warning: and tell all your clients!
    

#warn: call Warning.warn

Ruby 2.4 introduced redefinable Warning.warn method, which allowed to control how warnings are handled. But warn method wasn’t calling it, so initially only warnings issued from C code could’ve been handled. 2.5 fixes it. (Was not mentioned in NEWS-2.5.0)

  • Discussion: Feature #12299#note-14
  • Documentation:
  • Code:
    def Warning.warn(msg)
      puts ".warn called with: #{msg.inspect}"
    end
    
    warn 'foo', 'bar'
    # Ruby 2.4 prints:
    #  foo
    #  bar
    # Ruby 2.5 prints:
    #  .warn called with: "foo\nbar\n"
    
  • Notes: Kernel#warn accepts multiple arguments, and joins them with "\n" to pass to Warning#warn as a single string.

Module: methods for defining methods and accessors became public

  • Reason: The methods affected are frequently used in metaprogramming, which prior to 2.5 required reopening classes/modules or using send
  • Discussion: Feature #14132, Feature #14133
  • Affected methods: #attr, #attr_accessor, #attr_reader #attr_writer, #define_method, #alias_method, #undef_method, #remove_method.
  • Code:
    Array.define_method(:half_size) { size.to_f / 2 }
    # => :half_size
    [1, 2, 3].half_size
    # => 1.5
    

Method#===

  • Reason: Method object can be used in a lot of contexts where the Proc can be used, and this one context (case equality) was lacking.
  • Discussion: Feature #14142
  • Documentation: Method#===
  • Code:
    require 'prime'
    case 137
    when Prime.method(:prime?)
      puts 'prime'
    else
      puts 'not!'
    end
    # prints 'prime' in Ruby 2.5+, 'not' in Ruby 2.4
    

Integer

#pow: modulo argument

  • Reason: Modular exponentiation is a useful concept, especially in cryptography, and can be vastly ineffective if performed as to sequential operations.
  • Discussion: Feature #12508, Feature #11003
  • Documentation: Integer#pow
  • Code:
    2120078484650058507891187874713297895455.
      pow(5478118174010360425845660566650432540723,
          5263488859030795548286226023720904036518)
    # => 4481650795473624846969600733813414725093
    

#allbits?, #anybits?, #nobits?

  • Reason: It is noted that testing values against bitmasks in Ruby is a bit non-atomic: first, calculate value & flags, and then compare result with zero (no common flags / at least one common flag), or original value (all common flags); the new methods should make the tests clearer
  • Discussion: Feature #12753
  • Documentation: Integer#allbits?, Integer#anybits?, Integer#nobits?
  • Code:
    CARNIVOROUS = 0b001
    MAMMAL = 0b010
    TERRASTRIAL = 0b100
    lizard = 0b101
    lizard.allbits?(CARNIVOROUS | MAMMAL) # => false
    lizard.anybits?(CARNIVOROUS | MAMMAL) # => true
    lizard.nobits?(CARNIVOROUS | MAMMAL) # => false
    

.sqrt

  • Reason: Math.sqrt(x).to_i for large numbers loses precision, so Integer.sqrt implements effective and precise integer square root algorithm
  • Discussion: Feature #13219
  • Documentation: Integer.sqrt
  • Code:
    Integer.sqrt(10**33)
    # => 31622776601683793
    Integer.sqrt(10**33)**2
    # => 999999999999999979762122758866849
    Math.sqrt(10**33).to_i
    # => 31622776601683792
    Math.sqrt(10**33).to_i**2
    # => 999999999999999916516569555499264
    

String

#delete_prefix, #delete_prefix!, #delete_suffix, #delete_suffix!

  • Discussion: Feature #12694, Feature #13665
  • Documentation: String#delete_prefix, String#delete_suffix
  • Code:
    'foo'.delete_prefix('fo') # => 'o'
    'bar'.delete_suffix('r') # => 'ba'
    
    # Notice that bang-meethods return nil if they've deleted nothing:
    'foo'.delete_prefix!('ba') # => nil
    # This allows to use the methods in ifs:
    descr = RUBY_DESCRIPTION.dup # => "ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]"
    if descr.delete_prefix!('ruby ')
      puts "We are running on MRI #{descr}" # => "We are running on MRI 2.5.1p57 ...."
    end
    

#each_grapheme_cluster and #grapheme_clusters

  • Reason: In modern Unicode, lots of on-screen characters (graphemes) are represented by several characters, which are logically combined into “grapheme clusters”; now Ruby has a proper way to work with those concepts.
  • Discussion: Feature #13780
  • Documentation: String#each_grapheme_cluster, String#grapheme_clusters
  • Code:
    "🏳️‍🌈".length
    # => 4
    "🏳️‍🌈".chars
    # => ["🏳", "️", "‍", "🌈"]
    "🏳️‍🌈".grapheme_clusters
    # => ["🏳️‍🌈"]
    "🏳️‍🌈".each_grapheme_cluster.map { |s| CGI.escape(s) }
    # => ["%F0%9F%8F%B3%EF%B8%8F%E2%80%8D%F0%9F%8C%88"]
    

#undump

The inverse of String#dump which escapes all non-printable and special characters.

  • Discussion: Feature #12275
  • Documentation: String#undump
  • Code:
    p <<~STR
      This is "fine".
      Or is it?
    STR
    # Prints "This is \"fine\".\nOr is it?\n"
    # ^ Now, this is dumped string, including quotes around.
    # How to get back to "nice" string?
    puts '"This is \"fine\".\nOr is it?\n"'.undump
    # Prints
    #  This is "fine".
    #  Or is it?
    

#casecmp and #casecmp? return nil for non-string arguments

Previously, those methods raised TypeError on arguments that can’t be coerced to String, now they just return nil

  • Reason: The behavior made consistent with == and <=>, as the methods are case-agnostic versions of those operators: when you compare incomparable things, you just get no result at all.
  • Discussion: Feature #13312
  • Documentation: String#casecmp, String#casecmp?
  • Code:
    "Foo".casecmp?(:foo)
    # Ruby 2.4: TypeError: no implicit conversion of Symbol into String
    # Ruby 2.5: nil
    

#start_with? accepts a regexp

string.start_with?(/foo/) is an equivalent of string.match?(/\Afoo/)

  • Discussion: Feature #13712
  • Documentation: String#start_with? (introduced in 2.5, but documented in 2.6)
  • Note: While looking a symmetric method, #end_with? does not support regexp; the possibility was discussed in the feature proposal and it was general agreement that the behavior should be symmetric, but eventually the implementation turned out to be too hard.

String#-@ optimized for memory preserving

String#- (freezing the string) is optimized to deduplicate frozen object

  • Discussion: Feature #13077, Feature #13295
  • Documentation: String#-@
  • Code:
    str = String.new('not frozen')
    10.times.map { -str }.map(&:object_id)
    # Ruby 2.4: 10 different ids => 10 different frozen string objects
    # Ruby 2.5: 10 same ids => always the same frozen object
    
    5.times.map { -'test' }.map(&:object_id)
    # Ruby 2.4: 5 different ids => 5 different frozen string objects
    # Ruby 2.5: 5 same ids => always the same frozen object
    

Regexp: absence operator

The underlying Onigmo Regexp library was updated, which brought new operator: (?~foo) means “match any string that do not ends with foo

  • Documentation:
  • Code:
    # Classic example of usefulness: matching C comments:
    code = <<~C
      /* This is a comment.
      It ends here: */
      // And not here: */
    C
    code.scan(%r{/\*.+\*/}m)
    # => ["/* This is a comment.\nIt ends here: */\n// And not here: */"]
    code.scan(%r{/\*(?~\*/)\*/})
    # => ["/* This is a comment.\nIt ends here: */"]
    

Struct with keyword arguments

Struct can be created with keyword_init: true, and then its instance will require keyword arguments to initialize

  • Discussion: Feature #11925
  • Documentation: Struct.new
  • Code:
    Person = Struct.new(:first, :last, :age)
    ModernPerson = Struct.new(:first, :last, :age, keyword_init: true)
    
    Person.new('Bob', 'Jones', 40)
    # => #<struct Person first="Bob", last="Jones", age=40>
    ModernPerson.new(first: 'Meredith', last: 'Williams', age: 28)
    # => #<struct ModernPerson first="Meredith", last="Williams", age=28>
    ModernPerson.new('Meredith', 'Williams', 28)
    # ArgumentError (wrong number of arguments (given 3, expected 0))
    
  • Follow-ups:
    • Ruby 3.1 introduced a method to check whether some struct should be initialized with keyword arguments, and a warning on erroneous initialization of non-keyword-args struct with a hash.
    • Ruby 3.2 allowed all structs without explicit keyword_init: parameter specified to be initialized by both positional and keyword args.

Time.at: units

Time.at second argument was meant to be microseconds, but in 2.5+ it can be micro-, milli- or nano-seconds, specified by third argument: :millisecond, :usec or :microsecond (default), :nanosecond

  • Reason: It was noted that when you have nanoseconds value (available as Time#nsec) and want to pass it to .at, it is inconvenient to divide it by 1000.
  • Discussion: Feature #13919
  • Documentation: Time.at
  • Code:
    nanoseconds = 123456789
    # The only way previously to create time with nsec value
    Time.at(946684800, nanoseconds / 1000.0).nsec # => 123456789
    # New way
    Time.at(946684800, nanoseconds, :nanosecond).nsec # => 123456789
    # Also convenient for some contexts: .123 seconds
    Time.at(946684800, 123, :millisecond).usec
    # => 123000
    

Collections

Enumerable#any?, #all?, #none?, and #one? accept patterns

Any argument that responds to #=== may now be passed (akin to grep)

Array#append and #prepend

Just aliases for #push and #unshift

  • Reason: The old methods are symmetric with #pop and #shift, respectively, but asymmetric with each other and (especially #unshift) hard to guess and remember
  • Discussion: Feature #12746
  • Documentation: Array#append, Array#prepend
  • Code:
    ary = [1, 2, 3]
    ary.append(4)
    # => [1, 2, 3, 4]
    ary.prepend(0)
    # => [0, 1, 2, 3, 4]
    ary
    # => [0, 1, 2, 3, 4]
    

Hash#transform_keys and #transform_keys!

  • Discussion: Feature #13583
  • Documentation: Hash#transform_keys, Hash#transform_keys!
  • Code:
    {a: 1, b: 2}.transform_keys { |sym| sym.to_s.capitalize }
    # => {"A"=>1, "B"=>2}
    {a: 1, b: 2}.transform_keys(&:to_s)
    # => {"a"=>1, "b"=>2}
    {a: 1, b: 2}.transform_keys(&:length) # non-unique - the last value is taken
    # => {1=>2}
    {a: 1, b: 2}.transform_keys           # without block - returns enumerator
    # => #<Enumerator: {:a=>1, :b=>2}:transform_keys>
    # The latter can be useful when modified by .with_index
    {a: 1, b: 2}.transform_keys.with_index(1) { |k, i| "#{i}: #{k}" }
    # => {"1: a"=>1, "2: b"=>2}
    
  • Follow-up: Since Ruby 2.5.1, the behavior of transform_keys! (bang version) is changed: Ruby 2.5.0 changes the hash as it goes (the same behavior as the old ActiveSupport method of the same name), whereas Ruby 2.5.1 first calculates the new hash and then replaces the old one:
    # Ruby 2.5.0, Ruby 2.4 with ActiveSupport
    h = {1 => :hello, 2 => 'world'}
    h.transform_keys!(&:succ) # first, 2 => :hello replaces 2 => 'world', then the same key is processed again
    # => {3=>:hello}
    
    # Ruby 2.5.1
    h = {1 => :hello, 2 => 'world'}
    h.transform_keys!(&:succ)
    # => {2=>:hello, 3=>"world"}
    

    Discussion: Bug #14380

  • Follow-up: In Ruby 3.0, a new form transform_keys(from_name: :to_name) was introduced.

Hash#slice

  • Discussion: Feature #13563
  • Documentation: Hash#slice
  • Code:
    h = { a: 100, b: 200, c: 300 }
    h.slice(:a)           #=> {:a=>100}
    h.slice(:b, :c, :d)   #=> {:b=>200, :c=>300}
    
  • Follow-up: Funnily enough, what started at #8499 as discussing of importing #slice/#slice!/#except and #except! from ActiveSupport, ended up in a several tickets: #slice (current one, accepted for 2.5), #slice! (#15863, rejected by Matz, “no real-life use-case”), and #except (#15822, recent ticket, not merged as of May 2019). UPD: Finally made its way to Ruby 3.0.

Exceptions

Backtrace and error message in reverse order

New behavior is activated if STDERR is TTY

  • Discussion: Feature #8661
  • Code:
    $ cat test.rb
    {a: 1}.fetch(:b)
    $ ruby test.rb
    # Ruby 2.4:
    test.rb:1:in `fetch': key not found: :b (KeyError)
      from test.rb:1:in `<main>'
    
    # Ruby 2.5:
    Traceback (most recent call last):
      1: from test.rb:1:in `<main>'
    test.rb:1:in `fetch': key not found: :b (KeyError)
    
  • Follow-up: Feature is tagged “experimental” in Ruby 2.5, somewhat limited in 2.6, and completely reverted in 3.0.

Exception#full_message

Exception#full_message provides the same string Ruby will print if exception is not caught.

  • Discussion: Feature #14141
  • Documentation: Exception#full_message
  • Code:
    # test.rb
    begin
      {a: 1}.fetch(:b)
    rescue => e
      puts e.full_message
    end
    # `ruby test.rb` prints:
    #   Traceback (most recent call last):
    #     1: from test.rb:2:in `<main>'
    #   test.rb:2:in `fetch': key not found: :b (KeyError)
    #
    # `ruby test.rb 2>err.log` prints:
    #   test.rb:2:in `fetch': key not found: :b (KeyError)
    #     from test.rb:2:in `<main>
    
  • Notice: The format is different depending of whether STDERR is TTY or not (even if you printing the returned string to STDOUT or just storing it in the variable)
  • Follow-up: Ruby 2.6 added options for #full_message for enforcing desired format regardless of whether STDERR is TTY

KeyError#receiver and #key

  • Discussion: Feature #12063
  • Documentation: KeyError
  • Code:
    begin
      {a: 1}.fetch(:b)
    rescue => e
      p e.receiver # => {:a=>1}
      p e.key      # => :b
    end
    
  • Follow-up: Since Ruby 2.6, receiver: and key: could also be passed when creating KeyError in your code (as of Ruby 2.5, only C code could’ve create “proper” KeyError)

New class: FrozenError

  • Discussion: Feature #13224
  • Documentation: FrozenError
  • Code:
    a = [1, 2, 3].freeze
    a << 4
    # Ruby 2.4: RuntimeError: can't modify frozen Array
    # Ruby 2.5: FrozenError (can't modify frozen Array)
    

Don’t hide coercion errors

Previously, some Numeric and Range methods caught errors when the provided values were incompatible, and thrown their own. This was hard to debug, so now the original error is thrown instead.

  • Discussion: Feature #7688
  • Affected methods: Numeric#step, #<, #>, #>=, #<= (when other.coerce method fails), Range#initialize (when <=> method of range’s ends fail)
  • Code:
    class MyString
      def initialize(str)
        @str = str
      end
    
      def <=>(other)
        # This will fail: we forgot to define attr_reader :str
        str <=> other.str
      end
    
      include Comparable
    end
    
    'bar'..MyString.new('foo')
    # Ruby 2.4: test.rb:13:in `<main>': bad value for range (ArgumentError)
    # Ruby 2.5: test.rb:7:in `<=>': undefined local variable or method `str' for #<MyString:0x0000556d024e7b08 @str="bar"> (NameError)
    

Filesystem and IO

IO#pread and #pwrite

Two atomic methods utilize corresponding system calls to read or write file at specified offset, instead of #seek + #read/#write

  • Reason:
  • Discussion: Feature #4532
  • Documentation: IO#pread, IO#pwrite
  • Code:
    File.write('tmp.txt', 'test me please')
    f = File.open('tmp.txt')
    f.pread(2, 5) # => "me"
    f.pos         # => 0, unchanged
    

IO#write accepts multiple arguments

File.open better supports newline: option

Somewhat obscurely present IO.new/IO.read/File.open and similar methods options for converting newlines now properly switches file reading to text mode.

  • Discussion: Bug #13350
  • Documentation: — (it is almost documented by referring from IO.new docs to String#encode docs, where at least longer form newline_universal: true is mentioned)
  • Code:
    File.write("crlf.txt", "a\nb\nc", newline: :crlf) # option to convert newlines to Windows format
    File.binread('crlf.txt')
    # => "a\r\nb\r\nc" -- what it really wrote
    File.read('crlf.txt', newline: :universal)
    # Ruby 2.4:
    #   => "a\r\nb\r\nc" -- newline option is ignored unless encodings passed too
    # Ruby 2.5:
    #   => "a\nb\nc" -- newline option is respected, file read in text mode & newlines converted
    

File#path raises when opened with File::Constants::TMPFILE option.

The constant TMPFILE is documented as “Create an unnamed temporary file”, but before Ruby 2.5, it was still possible to request this file’s path.

  • Discussion: Feature #13568
  • Documentation: File#path
  • Code:
    f = File.open('/tmp', File::RDWR | File::TMPFILE)
    f.path
    # Ruby 2.4: => "/tmp" -- what???
    # Ruby 2.5: IOError (File is unnamed (TMPFILE?))
    
  • Follow-up: Ruby 3.2 introduced a concept of generic IO#path which is allowed to be nil when the path is unknown, so the behavior changed:
    f = File.open('/tmp', File::RDWR | File::TMPFILE)
    f.path #=> nil
    

File.lutime

Same as utime, but for symlinks sets modification time of link itself, not the referred object

Dir.children and .each_child

  • Discussion: Feature #11302
  • Documentation: Dir.children, Dir.each_child
  • Code:
    Dir.children('.')
    # => ["_layouts", ".gitignore", "README.md", "_data", "_src", "_site", "Contributing.md", "_config.yml", "2.6.md", "js", "404.html", ".git", "images", "Gemfile", "css", "Gemfile.lock"]
    
  • Follow-up: Ruby 2.6 added instance methods with same behavior

Dir.glob: base: argument

  • Discussion: Feature #13056
  • Documentation: Dir.glob
  • Code:
    Dir.glob('*')
    # => ["_layouts", "README.md", "err.log", "_data", "_src", "_site", "Contributing.md", "_config.yml", "2.6.md", "js", "404.html", "images", "Gemfile", "css", "Gemfile.lock"]
    Dir.glob('*', base: '_src')
    # => ["2.5.md", "script", "tmp", "2.6.md"]
    

Process.last_status as an alias of $?

Thread#fetch

Much akin to Hash#fetch, allows to fetch thread-local variable or provide default value

  • Discussion: Feature #13009
  • Documentation: Thread#fetch (feature is present since Ruby 2.5, but explanations added later)
  • Code:
    threads = [Thread.new { Thread.current[:name] = "first" }, Thread.new {}]
    threads.each(&:join)
    threads.map { |t| t.fetch(:name, '<not set>') }
    # => ["first", "<not set>"]
    threads.last.fetch(:name)
    # KeyError (key not found: :name)
    threads.last.fetch(:name) { |key| "#{key} not set at #{Time.now}" }
    # => "name not set at 2019-05-30 16:50:44 +0300"
    

RubyVM::InstructionSequence new methods

Misc

  • Random.raw_seed renamed to Random.urandom. Discussion: Bug #9569.
  • Data is deprecated. It was a base class for C extensions, and it’s not necessary to expose in Ruby level. Feature #3072. Follow-up: Fully removed in 3.0; in 3.2, a new class with the same name but different meaning was reintroduced.

Standard library

Data types

Set

  • Set#to_s is an alias of #inspect. Discussion: Feature #13676
    puts Set[:foo, :bar]
    # Ruby 2.4: #<Set:0x00005614c9069e68>
    # Ruby 2.5: #<Set: {:foo, :bar}>
    
  • Set#=== is an alias of #include?. Discussion: Feature #13801. This allows the use Sets in case and grep:
    case RUBY_VERSION
    when Set['2.5.0', '2.5.1', '2.5.2', '2.5.3']
      # ...
    end
    
    [:open, :work, :pause, :close, :open, :break].grep(Set[:open, :close])
    # => [:open, :close, :open]
    
  • Set#reset (discussion: Feature #6589) allows to ensure set’s internal state consistency after modifying a mutable element of the set:
    processes = Set[[:open], [:work]]
    processes.first << :close
    processes
    # => #<Set: {[:open, :close], [:work]}>
    processes.include?([:open, :close]) # => false
    processes.reset
    processes.include?([:open, :close]) # => true
    

Text processing

  • StringScanner: methods #size, #captures, #values_at to introspect latest match, consistent with those of MatchData. Discussion: Feature #836
    scanner = StringScanner.new('y = x^2')
    scanner.scan(/(\w+)\s*=\s*/)
    # => "y = "
    scanner.size            # number of captures, including entire matched string
    # => 2
    scanner.values_at(0, 1) # 0 is "entire matched sequence", 1 is "first capture"
    # => ["y = ", "y"]
    scanner.captures        # only captures
    # => ["y"]
    
  • ERB#result_with_hash added to allow passing local variables via hash instead of Binding object. Discussion: Feature #8631
    require 'erb'
    ERB.new('x = <%= x %>').result_with_hash(x: 11)
    # => "x = 11"
    

Network and Web

  • open-uri: URI.open method defined as an alias to open-uri’s Kernel#open. open-uri’s Kernel#open will be deprecated in future. Follow-up: Deprecation of Kernel#open and docs for URI.open were added in Ruby 2.7. Kernel#open redefinition was fully removed in 3.0.

Net::HTTP

  • New no_proxy argument to Net::HTTP.new, allows to specify list of hosts that should be contacted directly, bypassing the proxy. Discussion: Feature #11195
  • Net::HTTP#proxy_user and Net::HTTP#proxy_pass now can be fetched from ENV['http_proxy'] if it contains them and if OS provides multiuser safe environment (read: Unix-based OS). Bug #12921:
    ENV['http_proxy'] = 'https://me:secret@proxy.server'
    http = Net::HTTP.new('https://google.com')
    http.proxy_user
    # => "me"
    http.proxy_pass
    # => "secret"
    
  • Net::HTTP#min_version and Net::HTTP#max_version allow to specify min/max SSL version to use. Docs of methods with same names in OpenSSL library provide some explanations. Discussion: Feature #9450
  • Net::HTTP::STATUS_CODES constant (no docs) is added as HTTP Status Code Repository. Discussion: Misc #12935
    require 'net/http/status'
    Net::HTTP::STATUS_CODES.fetch(417)
    # => "Expectation Failed"
    

WEBrick

  • Support for Proc (or any other callable) objects as body responses (no docs). Discussion: Feature #855:
    require 'webrick'
    
    server = WEBrick::HTTPServer.new :Port => 8000
    
    trap 'INT' do server.shutdown end
    
    server.mount_proc '/' do |req, res|
      res.chunked = true # or set headers['content-length'] explicitly
      res.body = proc { |out| out << Time.now.to_s }
    end
    
    server.start
    
  • Server Name Indication support via :ServerName option for Webrick::HTTPServer.new. Discussion: Feature #13729

Ruby development and introspection

  • Coverage: support branch coverage and method coverage measurement. Discussion: Feature #13901
  • Binding#irb (which is available, if not documented, since Ruby 2.4, meaning “start IRB session in the current scope”), now automatically requires irb. Discussion: Bug #13099
  • RbConfig::LIMITS is added to provide the limits of C types:
    require 'rbconfig/sizeof'
    RbConfig::LIMITS['FIXNUM_MAX']
    # => 4611686018427387903
    
  • Ripper::Filter#state to tell the state of scanner. Discussion Feature #13686

Misc

  • Pathname#glob Discussion: Feature #7360:
    require 'pathname'
    Pathname.new('.').glob('*.md')
    # => [#<Pathname:2.5.md>, #<Pathname:README.md>, #<Pathname:Contributing.md>, #<Pathname:2.6.md>]
    
  • SecureRandom.alphanumeric
  • mathn.rb removed from stdlib. Feature #10169. It was a library that patched some core classes to make Ruby’s math more “natural” (by providing Rational and Complex operations where necessary), but it was decided that side-effects of its implicit inclusion were too sudden, and explicit usage of Rational and Complex became much more natural in later Ruby versions.

Large updated libraries

Libraries promoted to default gems

stdgems.org project has a nice explanations of default and bundled gems concepts, as well as a list of currently gemified libraries.

“For the rest of us” this means libraries development extracted into separate GitHub repositories, and they are just packaged with main Ruby before release. It means you can do issue/PR to any of them independently, without going through more tough development process of the core Ruby.

Libraries extracted in 2.5:

Follow-up: