Ruby 2.5

Note: As already explained in Introduction, this site is dedicated to changes in the language, not the implementation, therefore the list below lacks mentions of lots of important optimization introduced in 2.6, including the whole JIT big thing. That’s not because they are not important, just because this site’s goals are different.

Official source: NEWS file for Ruby 2.5.

Language

Top-level constant look-up is removed.

  • Details: Foo::Bar doesn’t fallback to ::Bar anymore, when Foo::Bar is not found
  • Reason: The behavior was considered bringing more problems and bugs than doing good 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

Notice 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

  • Details: pp standard library is now automatically required on the first call to Kernel#pp
  • Discussion: Feature #14123

#warn: uplevel: keyword argument

  • Details: When uplevel: N provided to Kernel#warn, then warning message includes file and line of the call, N steps up in the callstack.
  • Reason: It is sometimes hard to understand what exact code caused the warning, this feature helps to show the offensive calling code.
  • Discussion: Feature #12882
  • Documentation: Kernel#warn (it became somewhat documented at Ruby 2.6, but the feature itself is 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!
    

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 criptography, and can be wastly ineffective if performed as to sequentual 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, lot of on-screen characters (graphemes) represented, in fact, by several chars, which are logically combined into “grapheme cluster”; 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

  • Details: 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

  • Details: Previously, those method 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

  • Details: 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)

String#-@ optimized for memory preserving

  • Details: 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

  • Details: 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

  • Details: 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))
    

Time.at: units

  • Details: 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

  • Details: Any argument that responds to #=== may now be passed (akin to grep)
  • Discussion: Feature #11286
  • Documentation: Enumerable#all?, Enumerable#any?, Enumerable#none?, Enumerable#one?
  • Code:
    [1, 2, nil].all?(Numeric) # => false
    ['America', 'Europe', 'Africa', 'Asia'].any?(/^E/) # => true
    (1..20).one?(20..30) # => true
    require 'set'
    [:foo, :bar, :baz].none?(Set[:foo, :test]) # => false
    

Array#append and #prepend

  • Details: 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 hash as it goes (the same behavior as old ActiveSupport’s method with the same name), while Ruby 2.5.1 first calculates 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

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: Funny 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)

Exceptions

Backtrace and error message in reverse order

  • Details: 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, but as of Ruby 2.7-preview1 it is still active, so should be considered stable.

Exception#full_message

  • Details: 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

  • Details: Some Numeric and Range methods have previously caught errors when the provided values were incompatible, and thrown their own; it was hard to debug, so now original error is thrown.
  • 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

  • Details: 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

  • Details: 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 refering 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.

  • Details: 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?))
    

File.lutime

  • Details: Same as utime, but for symlinks sets modification time of link itself, not the referred object
  • Discussion: Feature #4052
  • Documentation: File.lutime

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

  • Details: 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

Standard library

Data types

Set

  • Set#to_s is an alias to #inspect. Discussion: Feature #13676
    puts Set[:foo, :bar]
    # Ruby 2.4: #<Set:0x00005614c9069e68>
    # Ruby 2.5: #<Set: {:foo, :bar}>
    
  • Set#=== as alias to #include?. Discussion: Feature #13801. This allows to 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. Notice: as of June 2019, there’s no documentation for the change, and Kernel.open is not deprecated in Ruby 2.6. But discussion is at Misc #15893

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 of 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 it implicit inclusion too sudden, and explicit usage of Rational and Complex became much more natural in latest 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: 14 more libraries gemified in 2.6