📜 ⬆️ ⬇️

Ruby 2.1 in detail (Part 2)


Refinements

Refinements (refinements) are no longer an experimental feature and do not display a worning, and several details have been added to their implementation that make their use more convenient.

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

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

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

 module BlankQuery refine Object do def blank? respond_to?(:empty?) ? empty? : false end end refine String do def blank? strip.length == 0 end end refine NilClass do def blank? true end end end module NumberQuery refine Object do def number? false end end refine String do def number? match(/\A(0|-?[1-9][0-9]*)\z/) ? true : false end end refine Numeric do def number? true end end end module Support include BlankQuery include NumberQuery end class User using Support # ... def points=(obj) raise "points can't be blank" if obj.blank? raise "points must be a number" unless obj.number? @points = obj end end 

')
String # scrub

The String # scrub method was added in Ruby 2.1 to assist in working 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 only have the end 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.

Removed level 4 $ SAFE

Specifying $ SAFE = 4 should have put Ruby into sandbox mode and allowed the execution of untrusted code. However, this was not particularly effective, since 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 will 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 execute somewhat faster.

Process.clock_gettime can also be used to access “monotonous” clocks that are independent of the translation of the system clock. This can be used for critical time measurements or benchmarking.

However, the value of the monotonous clock makes sense only when compared with another of the 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 in the same way as a monotone clock, i.e. constantly increasing, but the difference is that it only increases when the CPU does any 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 are using, it is also possible to access other types of watches, to learn this, read the relevant documentation .

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

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

Also available is the Process.clock_getres method, which allows you to see the resolution provided by a particular type of clock.

RubyGems Update

The built-in version of RubyGems has been updated to version 2.2. Support for Gemfile has been added support for Gemfile.lock, as part of the work to support all the features of Bundler in RubyGems .

The option --file (or -g) for gem install now does not require the specification of a dependency file, it automatically detects the presence of a Gemfile. gem install will also generate a gemfile.lock if it has not already been created, and consider the versions specified in it if it has already been 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

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 about these features has long been displayed, so compatibility problems should not be.

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

RDoc template update

Ruby now includes version 4.1 of RDoc, which contains a default template update with some improvements in access management. 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 a process name without accessing the $ 0 variable. The Process.argv0 method was also added to get the initial 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 run 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 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.

Fixed scope leakage

When using the private, protected, public, or module_function keywords with no 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 closed:

 class Foo eval "private" def foo "foo" end end 

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

#untrusted? now alias for #tainted?

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

Now these methods are unified and set the same flag, and #tainted is more preferable? and company, and when calling #untrusted? and others. Will appear.

 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 are different from blocks and Proc objects in that return returns control from lambda, not from the caller. However, there is an exception if lambda is passed with & and called with yield. Now it is fixed.

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

The example above displays “hello from lambda” in 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 accepts Symbol objects as arguments and returns the values ​​of the corresponding named groups.

 require "strscan" def parse_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] end end data end 


YAML.safe_load

The safe_load method has been added to YAML (or more precisely in Psych, the underlying implementation). 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 security, you need to pass them as an argument.

If an object of an unresolved class is passed, the exception Psych :: DisallowedClass will be raised, which can also be received 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 MDNS and LOC records in Resolv

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

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

A bundle with the resolv-replace library makes it possible to use mDNS names with most of the online 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 received the ability to request 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 get complete 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 was significantly increased, which, combined with the ordered insert that appeared in Ruby 1.9, makes it possible to implement the LRU cache .

 class LRUCache def initialize(size) @size, @hash = size, {} end def [](key) @hash[key] = @hash.delete(key) end def []=(key, value) @hash.delete(key) @hash[key] = value @hash.shift if @hash.size > @size end end 


Improving the performance of the Queue, SizedQueue and ConditionVariable classes

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

Interception of an internal exception in Timeout

Now it became impossible to catch the exceptions used inside the Timeout class to interrupt block execution. This is an internal implementation detail, while the external exception Timeout :: Error remains unchanged and can be intercepted.

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


The sets

#Intersect methods have been added to the Set class? and #disjoint ?. The #intersect method? returns true if the object and argument have at least one common element and false otherwise, #disjoint? it 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 


Simplify the processing of responses WEBrick

Now the HTTP response body from WEBrick can be assigned to any object with #read and #readpartial methods. Previously, it 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" class EnumeratorIOAdapter def initialize(enum) @enum, @buffer, @more = enum, "", true end def read(length=nil, out_buffer="") return nil unless @more until (length && @buffer.length >= length) || !fill_buffer; end if length part = @buffer.slice!(0, length) else part, @buffer = @buffer, "" end out_buffer.replace(part) end def readpartial(length, out_buffer="") raise EOFError if @buffer.empty? && !fill_buffer out_buffer.replace(@buffer.slice!(0, length)) end private def fill_buffer @buffer << @enum.next rescue StopIteration @more = false end end 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" end end response.chunked = true response.body = EnumeratorIOAdapter.new(enum) end trap(:INT) {server.shutdown} server.start 


Numeric # step

The Numeric class #step method can now take the named by: and to: arguments instead of positional arguments. The to: argument is optional; if it is not specified, the sequence will be infinite. When using 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 lead

 0 5 10 15 20 

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

in both cases will derive

 0 3 6 9 12 ... 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 a second argemnt, you can now pass IO :: SEEK_DATA and IO :: SEEK_HOLE (or: DATA and: HOLE). When they are specified, the first argument is used as the minimum data size / 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 checked using IO.const_defined? (: SEEK_DATA) and IO.const_defined? (: SEEK_HOLE).

Using IO _nonblock Without Exciting 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) break unless result == :wait_writeable end response = "" loop do IO.select([io]) result = io.read_nonblock(32, exception: false) break unless result next if result == :wait_readable response << result end puts response.lines.first 


IO ignores internal encoding if external ASCII-8BIT

If you specify the default internal and external encodings, 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 takes place.

The same exception should be made if encodings are passed to the IO method as an argument, but this was not the case and the conversion was done. 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.

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

 require "bigdecimal" module FloatingPointFormat def to_s(format="F") super end end 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.
The first part The third part

Source: https://habr.com/ru/post/223209/


All Articles