Ruby 2.1 in detail (Part 2)

Original author: Mat Sadler
  • Transfer

Refinements

Refinements (refinements) are no longer experimental features and do not display the vorning, and several details have been added to their implementation that make their use more convenient.

Now, the # # method for activating within the module has been added to the #using method for activating refinements at the file level. However, the use of refinements is still limited by the lexical scope, i.e. they will not be active when the module is reopened.

moduleNumberQuery
  refine String dodefnumber?
      match(/\A(0|-?[1-9][0-9]*)\z/) ? true : falseendendendmoduleExample
  using NumberQuery
  "42".number?   #=> trueendmoduleExample"42".number?   #=> #<NoMethodError: undefined method `number?' for "42":String>end

Refinement declarations are now inherited using Module # include, i.e. you can group refinements defined in different modules in one and activate them all by calling #using only for this module.

moduleBlankQuery
  refine Object dodefblank?
      respond_to?(:empty?) ? empty? : falseendend
  refine String dodefblank?
      strip.length == 0endend
  refine NilClass dodefblank?trueendendendmoduleNumberQuery
  refine Object dodefnumber?falseendend
  refine String dodefnumber?
      match(/\A(0|-?[1-9][0-9]*)\z/) ? true : falseendend
  refine Numeric dodefnumber?trueendendendmoduleSupportinclude BlankQuery
  include NumberQuery
endclassUser
  using Support
  # ...defpoints=(obj)
    raise "points can't be blank"if obj.blank?
    raise "points must be a number"unless obj.number?
    @points = obj
  endend


String # scrub

The String # scrub method was added in Ruby 2.1 to help deal with strings containing invalid bytes.

# create a string that can't be sensibly printed# 'latin 1' encoded string with accented character
string = "öops".encode("ISO-8859-1")
# misrepresented as UTF-8
string.force_encoding("UTF-8")
# and mixed with a UTF-8 character
string = #{string}!"

It is unlikely that you will create lines in this way consciously (at least I hope so), but this happens with lines that have passed through several different systems.

If we have only the final result of such a “journey”, we can no longer recover all the incorrectly encoded characters, but we can at least delete them:

# replace with 'replacement character'
string.scrub        #=> "¡�ops!"# delete
string.scrub("")    #=> "¡ops!"# replace with chosen character
string.scrub("?")   #=> "¡?ops!"# yield to a block for custom replacement# (in this case the invalid bytes as hex)
string.scrub {|bytes|"<#{bytes.unpack("H*").join}>"}   #=> "¡<f6>ops!"

The same result can be achieved by calling the #encoding method with the current encoding and invalid:: replace as arguments:

string.encode("UTF-8", invalid::replace)                 #=> "¡�ops!"
string.encode("UTF-8", invalid::replace, replace:"?")   #=> "¡?ops!"


Performance Improvements in Bignum / Rational Classes

The Bignum and Rational classes now use the GNU Multiple Precision Arithmetic Library (GMP) to improve performance.

Level 4 removed $ SAFE

Setting $ SAFE = 4 was to put Ruby in sandbox mode and allow untrusted code to execute. However, this was not particularly effective because It required a considerable amount of code scattered throughout the interpreter, and almost never was used, which was why it was eventually deleted.

$SAFE = 4#=> #<ArgumentError: $SAFE=4 is obsolete>


clock_gettime

Ruby accessed the clock_gettime () system function using the Process.clock_gettime method, which provides access to various date values. As the first argument, the time id should be passed to the method:

Process.clock_gettime(Process::CLOCK_REALTIME)   #=> 1391705719.906066

By passing Process :: CLOCK_REALTIME, you get a Unix timestamp as the return value. It will also correspond to Time.now.to_f, but without creating a Time object, so it will run a little faster.

Process.clock_gettime can also be used to access the "monotonous" clock, which does not depend on the translation of the system clock. This can be used for critical time measurements or benchmarking.

However, the value of a monotonous clock makes sense only when comparing with another same mark:

start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
sleep 1
Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time   #=> 1.0051147330086678

Another value that can be used for benchmarking is CLOCK_PROCESS_CPUTIME_ID. It works the same as a monotonous clock, i.e. constantly increasing, but the difference is that it only increases when the CPU does some work. This mark also makes sense only in comparison with another similar mark.

start_time = Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID)
sleep 1
Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID) - start_time   #=> 0.005225999999999981

These three clock values, real, monotonous and processor, are always available. Depending on the system you use, it is also possible to access other types of watches, to find out, read the relevant documentation .

To check the availability of the availability of a particular type of watch, it is enough to check that the corresponding constant is defined.

Process.const_defined?(:CLOCK_PROCESS_CPUTIME_ID)   #=> true
Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID)    #=> false

The Process.clock_getres method is also available, which allows you to find out the resolution provided by a particular type of clock.

RubyGems Update

The embedded version of RubyGems has been updated to version 2.2. By Gemfile support has been added Gemfile.lock support, as part of work to support all features Bundler'a in RubyGems .

The --file (or -g) option for gem install no longer requires a dependency file, it automatically detects the presence of a Gemfile. gem install will also generate Gemfile.lock if it has not already been created, and consider the versions indicated in it if it is already created.

$ ls
Gemfile
$ gem install -g
Fetching: going_postal-0.1.4.gem (100%)
Installing going_postal (0.1.4)
$ ls
Gemfile
Gemfile.lock

A complete list of changes can be found in the History file .

Removed obsolete features Rake

The built-in Rake has been updated to version 10.1.0, in which many deprecated features have been removed. In older versions of Rake, a warning has long been issued about these features, so there should be no compatibility issues.

If you need more details, see the full list of changes in Rake versions 10.0.3 and 10.1.0 .

Update RDoc Template

Ruby now includes RDoc version 4.1, which contains an update to the default template with some improvements in terms of accessibility. A complete list of changes can be found in the History file .

Process name

A new Process.setproctitle method has been added that allows you to set the process name without accessing the $ 0 variable. The Process.argv0 method has also been added to get the original value of $ 0.

For example, you have the following background script:

data.each_with_index do|datum, i|
  Process.setproctitle("#{Process.argv0} - job #{i} of #{data.length}")
  process(datum)
end

then when you start ps you will see something like the following:

$ ps
  PID TTY           TIME CMD
  339 ttys000    0:00.23 -bash
 7321 ttys000    0:00.06 background.rb - job 10 of 30


Frozen Symbol

Symbol objects now constitute a company of integers and real numbers as "frozen" (frozen) objects.

:foo.frozen?                               #=> true:foo.instance_variable_set(:@bar, "baz")   #=> #<RuntimeError: can't modify frozen Symbol>

This change was made to improve garbage collection for such objects in future versions of Ruby.

Scope leak fixed

When using the private, protected, public, or module_function keywords without arguments in a string executed using eval, instance_eval or module_eval, the scope of the method “flows” to the parent scope, i.e. in the example below, the foo method will be private:

classFoo
  eval "private"deffoo"foo"endend

In version 2.1, this is fixed and foo will be open.

#untrusted? is now an alias for #tainted?

Earlier in Ruby, there were two sets of methods to mark objects as untrusted, the first consists of the #tainted ?, #taint and #untaint methods, the second consists of #untrusted ?, #untrust and #trust. They work the same way, but set different flags for objects.

Now these methods are unified and set the same flag, with #tainted being more preferable? and company, and when calling #untrusted? and others will appear vorings.

string = "foo"
string.untrust
string.tainted?   #=> true

will display a warning

example.rb:2: warning: untrust is deprecated and its behavior is same as taint


return in lambdas

Lambdas differ from blocks and Proc objects in that return returns control from a lambda, and not from the calling method. However, there is an exception if lambda is passed with & and called with yield. This has now been fixed.

defcall_with_yieldyieldenddeftest
  call_with_yield(&lambda {return"hello from lambda"})
  "hello from method"end
test   #=> "hello from method"

The example above will output “hello from lambda” to Ruby <= 2.0.0.

Interface Addresses

Now you can get the details of network interfaces in the system using the Socket.getifaddrs method. It returns an array of Socket :: Ifaddr objects.

require"socket"
info = Socket.getifaddrs.find do|ifaddr|
  (ifaddr.flags & Socket::IFF_BROADCAST).nonzero? &&
    ifaddr.addr.afamily == Socket::AF_INET
end
info.addr.ip_address   #=> "10.0.1.2"


Named Group Support in StringScanner

StringScanner # [] now takes Symbol objects as arguments and returns the values ​​of the corresponding named groups.

require"strscan"defparse_ini(string)
  scanner = StringScanner.new(string)
  current_section = data = {}
  until scanner.eos?
    scanner.skip(/\s+/)
    if scanner.scan(/;/)
      scanner.skip_until(/[\r\n]+/)
    elsif scanner.scan(/\[(?<name>[^\]]+)\]/)
      current_section = current_section[scanner[:name]] = {}
    elsif scanner.scan(/(?<key>[^=]+)=(?<value>.*)/)
      current_section[scanner[:key]] = scanner[:value]
    endend
  data
end


YAML.safe_load

In YAML (or more precisely in Psych, the underlying implementation), the safe_load method was added. By default, the following classes can be deserialized: TrueClass, FalseClass, NilClass, Numeric, String, Array, and Hash. To deserialize other classes, if you are sure of their safety, you need to pass them as an argument.

If an unresolved class object is passed, a Psych :: DisallowedClass exception will be thrown, which can also be thrown as YAML :: DisallowedClass.

require"yaml"
YAML.safe_load(":foo: 1")             #=> #<Psych::DisallowedClass: Tried to load unspecified class: Symbol>
YAML.safe_load(":foo: 1", [Symbol])   #=> {:foo=>1}


Support for MDNS and LOC records in Resolv

The Resolv DNS library has received basic support for multicast DNS lookups. It does not support continuous requests, and cannot perform service discovery, but it is still a very convenient innovation (for full support for DNS Service Discovery, try the dnssd gem ).

require"resolv"
resolver = Resolv::MDNS.new
resolver.getaddress("example.local")   #=> #<Resolv::IPv4 10.0.1.2>

The resolv-replace library link allows you to use mDNS names with most network libraries in Ruby.

require"resolv-replace"require"net/http"
Resolv::DefaultResolver.replace_resolvers([Resolv::Hosts.new, Resolv::MDNS.new])
Net::HTTP.get_response(URI.parse("http://example.local"))   #=> #<Net::HTTPOK 200 OK readbody=true>

Resolv also got the ability to query DNS LOC records .

require"resolv"
dns = Resolv::DNS.new
# find.me.uk has LOC records for all UK postcodes
resource = dns.getresource("W1A1AA.find.me.uk", Resolv::DNS::Resource::IN::LOC)
resource.latitude    #=> #<Resolv::LOC::Coord 51 31 6.827 N>
resource.longitude   #=> #<Resolv::LOC::Coord 0 8 37.585 W>

Finally, the last change in Resolv, you can now receive full DNS messages using the Resolv :: DNS # fetch_resource method.

require"resolv"
dns = Resolv::DNS.new
dns.fetch_resource("example.com", Resolv::DNS::Resource::IN::A) do|reply, reply_name|
  reply        #=> #<Resolv::DNS::Message:0x007f88192e2cc0 @id=55405, @qr=1, @opcode=0, @aa=0, @tc=0, @rd=1, @ra=1, @rcode=0, @question=[[#<Resolv::DNS::Name: example.com.>, Resolv::DNS::Resource::IN::A]], @answer=[[#<Resolv::DNS::Name: example.com.>, 79148, #<Resolv::DNS::Resource::IN::A:0x007f88192e1c80 @address=#<Resolv::IPv4 93.184.216.119>, @ttl=79148>]], @authority=[], @additional=[]>
  reply_name   #=> #<Resolv::DNS::Name: example.com.>end


Socket class error messages

Socket addresses have been added to error messages.

require"socket"
TCPSocket.new("localhost", 8080)   #=> #<Errno::ECONNREFUSED: Connection refused - connect(2) for "localhost" port 8080>


Accelerated Hash # shift

The performance of Hash # shift has been significantly increased, which, combined with the streamlined insert introduced in Ruby 1.9, makes it possible to implement the LRU cache .

classLRUCachedefinitialize(size)
    @size, @hash = size, {}
  enddef[](key)
    @hash[key] = @hash.delete(key)
  enddef[]=(key, value)
    @hash.delete(key)
    @hash[key] = value
    @hash.shift if @hash.size > @size
  endend


Improving the performance of the Queue, SizedQueue, and ConditionVariable classes

The Queue, SizedQueue, and ConditionVariable classes have been accelerated by implementing them in C (previously implemented in Ruby).

Interception of an internal exception in Timeout

Now it has become impossible to catch exceptions used inside the Timeout class to interrupt block execution. This is a detail of the internal implementation, while the external exception Timeout :: Error remains unchanged and can be caught.

require"timeout"begin
  Timeout.timeout(1) dobegin
      sleep 2rescue Exception
      # no longer swallows the timeout exceptionendendrescue StandardError => e
  e   #=> #<Timeout::Error: execution expired>end


Many

#Intersect methods have been added to the Set class? and #disjoint ?. #Intersect method? returns true if the object and argument have at least one common element and false otherwise, #disjoint? works the other way around.

require"set"
a = Set[1,2,3]
b = Set[3,4,5]
c = Set[4,5,6]
a.intersect?(b)   #=> true
b.intersect?(c)   #=> true
a.intersect?(c)   #=> false
a.disjoint?(b)   #=> false
b.disjoint?(c)   #=> false
a.disjoint?(c)   #=> true

Another important change to Set is that the #to_set method will return the object itself, not the created copy.

require"set"
set = Set["foo", "bar", "baz"]
set.object_id          #=> 70286489985620
set.to_set.object_id   #=> 70286489985620


Streamlined WEBrick Response Processing

Now the body of the HTTP response from WEBrick can be assigned to any object with the #read and #readpartial methods. Previously, these could only be IO or String objects. The example below implements a class that displays the received response every second for 10 seconds.

require"webrick"classEnumeratorIOAdapterdefinitialize(enum)
    @enum, @buffer, @more = enum, "", trueenddefread(length=nil, out_buffer="")returnnilunless @more
    until (length && @buffer.length >= length) || !fill_buffer; endif length
      part = @buffer.slice!(0, length)
    else
      part, @buffer = @buffer, ""end
    out_buffer.replace(part)
  enddefreadpartial(length, out_buffer="")
    raise EOFError if @buffer.empty? && !fill_buffer
    out_buffer.replace(@buffer.slice!(0, length))
  end
  private
  deffill_buffer
    @buffer << @enum.nextrescue StopIteration
    @more = falseendend
server = WEBrick::HTTPServer.new(Port:8080)
server.mount_proc "/"do|request, response|
  enum = Enumerator.new do|yielder|10.times do
      sleep 1
      yielder << "#{Time.now}\r\n"endend
  response.chunked = true
  response.body = EnumeratorIOAdapter.new(enum)
end
trap(:INT) {server.shutdown}
server.start


Numeric # step

The #step method of the Numeric class can now take named arguments by: and to: instead of positional arguments. The to: argument is optional; if it is not specified, the sequence will be infinite. With positional arguments, this can be achieved by specifying nil as the first argument.

0.step(by:5, to:20) do|i|
  puts i
end

will bring

05101520

0.step(by:3) do|i|
  puts i
end0.step(nil, 3) do|i|
  puts i
end

in both cases

036912
... and so on


IO

The IO # seek method now along with the constants IO :: SEEK_CUR, IO :: SEEK_END and IO :: SEEK_SET accepts Symbol: CUR,: END and: SET objects.

As the second argument, IO :: SEEK_DATA and IO :: SEEK_HOLE ( or: DATA and: HOLE). When specified, the first argument is used as the minimum data / empty space for the transition.

f = File.new("example.txt")
# sets the offset to the start of the next data chunk at least 8 bytes long
f.seek(8, IO::SEEK_DATA)
# sets the offset to the start of the next empty space at least 32 bytes long
f.seek(32, IO::SEEK_HOLE)

This may not be supported on all platforms, which can be verified using IO.const_defined? (: SEEK_DATA) and IO.const_defined? (: SEEK_HOLE).

Using IO _nonblock without throwing exceptions

The IO # read_nonblock and IO # write_nonblock methods can take a named exception: argument. If it is set to false (true by default), methods will return the corresponding Symbol object on error instead of throwing exceptions.

require"socket"
io = TCPSocket.new("www.example.com", 80)
message = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: close\r\n\r\n"
loop do
  IO.select(nil, [io])
  result = io.write_nonblock(message, exception:false)
  breakunless result == :wait_writeableend
response = ""
loop do
  IO.select([io])
  result = io.read_nonblock(32, exception:false)
  breakunless result
  nextif result == :wait_readable
  response << result
end
puts response.lines.first


IO ignores internal encoding if external ASCII-8BIT

If you specify internal and external encodings by default, Ruby will convert from external encoding to internal. An exception is the case when the external encoding is ASCII-8BIT, in this case no conversion occurs.

The same exception should be made if the encodings are passed to the IO method as an argument, but this was not and the conversion was performed. The bug has been fixed.

File.read("example.txt", encoding:"ascii-8bit:utf-8").encoding   #=> #<Encoding:ASCII-8BIT>


#include and #prepend are now open

The #include and #prepend methods are now open, this applies to the Module and Class classes.

moduleNumberQuerydefnumber?
    match(/\A(0|-?[1-9][0-9]*)\z/) ? true : falseendend
String.include(NumberQuery)
"123".number?   #=> true

require"bigdecimal"moduleFloatingPointFormatdefto_s(format="F")superendend
BigDecimal.prepend(FloatingPointFormat)
decimal = BigDecimal("1.23")
decimal.to_s   #=> "1.23" # rather than "0.123E1"


In the third part there will be new methods in the Module and Object classes, changes in network classes and other updates in the standard library.
Part One Part Three

Also popular now: