Ruby 2.1 in detail (Part 3)
- Transfer
The #singleton_class method? for Module / Class
The #singleton_class? Method has been added to the Module and Class classes, which, as you would expect, returns whether the recipient is a meta-class (singleton)
class Example
singleton_class? #=> false
class << self
singleton_class? #=> true
end
end
More logical Module # ancestors
The #ancestors method, called in relation to the meta class, now returns an array containing meta classes, which makes its behavior more consistent. It also arranges the output of the meta classes, but this only happens if the module has been connected to the meta class using prepend (rather than include).
Object.ancestors.include?(Object) #=> true
Object.singleton_class.ancestors.include?(Object.singleton_class) #=> true
Object # singleton_method
Similar to #method and #instance_method, but returns only meta class methods
class Example
def self.test
end
def test2
end
end
# returns class method
Example.singleton_method(:test) #=> #
# doesn't return instance method
Example.singleton_method(:test2) #=> #
# doesn't return inherited class method
Example.singleton_method(:name) #=> #
example = Object.new
def example.test
end
example.singleton_method(:test) #=> #.test>
Method # original_name
The #original_name method has appeared in the Method and UnboundMethod classes, which returns the method name without an alias.
class Example
def foo
"foo"
end
alias bar foo
end
example = Example.new
example.method(:foo).original_name #=> :foo
example.method(:bar).original_name #=> :foo
Example.instance_method(:bar).original_name #=> :foo
Mutex # owned?
Mutex # owned method ? it is no longer experimental, in general there is nothing more to say about it.
Hash # reject
Calling the Hash # reject method in a subclass of Hash will output the vorning. In Ruby 2.2, calling #reject in Hash subclasses will return a new Hash object, not a subclass object. Therefore, in preparation for this change, a warning has been added so far.
class MyHash < Hash
end
example = MyHash.new
example[:a] = 1
example[:b] = 2
example.reject {|k,v| v > 1}.class #=> MyHash
The following warning will be displayed:
example.rb:8: warning: copying unguaranteed attributes: {:a=>1, :b=>2}
example.rb:8: warning: following atributes will not be copied in the future version:
example.rb:8: warning: subclass: MyHash
In Ruby 2.1.1, a complete change was accidentally included, which the Hash object returns and does not generate corroding, but in 2.1.2 this was fixed back.
Vector # cross_product
The cross_product method has been added to the Vector class.
require "matrix"
Vector[1, 0, 0].cross_product(Vector[0, 1, 0]) #=> Vector[0, 0, -1]
Fixnum / Bignum #bit_length
Calling #bit_length with respect to an integer will return the number of digits needed to represent the number in the binary system.
128.bit_length #=> 8
32768.bit_length #=> 16
2147483648.bit_length #=> 32
4611686018427387904.bit_length #=> 63
pack / unpack and byte representation of numbers
The Array # pack and String # unpack methods can now work with the byte representation of long numbers using the Q_ / Q directives! and q_ / q !.
# output may differ depending on the endianness of your system
unsigned_long_long_max = [2**64 - 1].pack("Q!") #=> "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
signed_long_long_min = [-2**63].pack("q!") #=> "\x00\x00\x00\x00\x00\x00\x00\x80"
signed_long_long_max = [2**63 - 1].pack("q!") #=> "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F"
unsigned_long_long_max.unpack("Q!") #=> 18446744073709551615
signed_long_long_min.unpack("q!") #=> -9223372036854775808
signed_long_long_max.unpack("q!") #=> 9223372036854775807
Dir.glob returns compound characters
The HFS Plus file system on Mac OS X uses UTF8-MAC encoding for file names with separated characters, for example, é is represented as e and U + 0301, and not just U + 00E9 ( with some exceptions ). Dir.glob and Dir [] now convert them back to UTF8 strings with compound characters.
File.write("composed_e\u{301}xample.txt", "")
File.write("precomposed_\u{e9}xample.txt", "")
puts Dir["*"].map(&:dump)
"composed_\u{e9}xample.txt"
"example.rb"
"precomposed_\u{e9}xample.txt"
Improved type casting for Numeric # quo
The Numeric # quo method now calls #to_r on the receiver, which should improve behavior when implementing native Numeric subclasses. This also means that if the cast cannot be cast, a TypeError will be raised, not an ArgumentError, which should not become a problem during the transition, because TypeError is a subclass of ArgumentError.
Binding # local_variable_get / _set / _defined?
In the Binding class, methods for obtaining / setting local variables have appeared. This can be useful if you really need to use a named argument that matches the reserved keyword.
def primes(begin: 2, end: 1000)
[binding.local_variable_get(:begin), 2].max.upto(binding.local_variable_get(:end)).each_with_object([]) do |i, array|
array << i unless (2...i).any? {|j| (i % j).zero?}
end
end
primes(end: 10) #=> [2, 3, 5, 7]
Or if you want to use a hash to set variables, for example when executing a template:
def make_binding(hash)
b = TOPLEVEL_BINDING.dup
hash.each {|k,v| b.local_variable_set(k, v)}
b
end
require "erb"
cover = %Q{<%= title %>
\n<%= subtitle %>
}
locals = {:title => "Hitchhiker's Guide to the Galaxy", :subtitle => "Don't Panic"}
ERB.new(cover).result(make_binding(locals)) #=> "Hitchhiker's Guide to the Galaxy
\nDon't Panic
"
CGI class methods are now available from the CGI :: Util module
The CGI class has several useful methods for escaping url and html strings. They have been ported to the CGI :: Util module, which can be included in other classes or scripts.
require "cgi/util"
CGI.escape("hello world!") #=> "hello+world%21"
include CGI::Util
escape("hello world!") #=> "hello+world%21"
Digest :: Class.file passes arguments to the constructor
Various classes of the Digest module have a method for creating a digest for a file, this method has been changed, and now it passes to the constructor all the arguments passed except for the file name. Those. instead:
require "digest"
Digest::SHA2.new(512).hexdigest(File.read("example.txt")) #=> "f7fbba..."
You can write:
require "digest"
Digest::SHA2.file("example.txt", 512).hexdigest #=> "f7fbba..."
Net :: SMTP # rset
Now you can cancel the SMTP transaction by sending the RSET command using the Net :: SMTP # rset method.
require "net/smtp"
smtp = Net::SMTP.start("some.smtp.server")
notification = "Hi %s,\n ..."
users.each do |user|
begin
smtp.mailfrom("noreply@example.com")
smtp.rcptto(user.email)
smtp.data(sprintf(notification, user.name))
rescue
smtp.rset
end
end
smtp.finish
open-uri supports duplicate headers
open-uri allows using the Kernel # open method to open resources by URI, and extends the return value with OpenURI :: Meta, where a new #metas method was added, which returns an array of values if the header was set several times, for example, set-cookie.
require "open-uri"
f = open("http://google.com")
f.meta["set-cookie"].class #=> String
f.metas["set-cookie"].class #=> Array
f.metas["set-cookie"].length #=> 2
Writing to a file via Pathname
The #write and #binwrite methods for writing files have been added to the Pathname class.
require "pathname"
path = Pathname.new("test.txt").expand_path(__dir__)
path.write("foo")
path.write("bar", 3) # offset
path.write("baz", mode: "a") # append
Tempfile.create
The Tempfile class now has a method similar to the new method, but instead of returning a Tempfile object using a finaliser to delete the file when the object is garbage collected, the create method passes the File object to the block, after which the file is deleted.
require "tempfile"
path = nil
Tempfile.create("example") do |f|
f #=> #
path = f.path
end
File.exist?(path) #=> false
Broadcast Support in Rinda
Rinda Ring classes can now listen / connect to group addresses.
Below is an example of using Rinda to create a simple service listening on a multicast address of 239.0.0.1
require "rinda/ring"
require "rinda/tuplespace"
DRb.start_service
tuple_space = Rinda::TupleSpace.new
server = Rinda::RingServer.new(tuple_space, ["239.0.0.1"])
DRb.thread.join
Service registration:
require "rinda/ring"
DRb.start_service
ring_finger = Rinda::RingFinger.new(["239.0.0.1"])
tuple_space = ring_finger.lookup_ring_any
tuple_space.write([:message_service, "localhost", 8080])
# start messaging service on localhost:8080
Getting the address of the service:
require "rinda/ring"
DRb.start_service
ring_finger = Rinda::RingFinger.new(["239.0.0.1"])
tuple_space = ring_finger.lookup_ring_any
_, host, port = tuple_space.read([:message_service, String, Fixnum])
# connect to messaging service
I had some problems with the string tuple_space = ring_finger.lookup_ring_any and I had to use:
tuple_space = nil
ring_finger.lookup_ring(0.01) {|x| break tuple_space = x}
Setting Advanced HTTP Options for XMLRPC
XMLRPC :: Client # http returns a Net :: HTTP object used by the client to set some configuration options that cannot be set via setters.
client = XMLRPC::Client.new("example.com")
client.http.keep_alive_timeout = 30 # keep connection open for longer
# use client ...
URI.encode_ / decode_www_form updated to standard WHATWG
The URI.encode_www_form and URI.decode_www_form methods have been updated to comply with the WHATWG standard .
URI.decode_www_form no longer takes
;
a separator as the &
only separator by default, but you can set the separator value using the named argument separator :.require "uri"
URI.decode_www_form("foo=1;bar=2", separator: ";") #=> [["foo", "1"], ["bar", "2"]]
URI.decode_www_form can now also successfully decode the output of URI.encode_www_form when its value is nil.
require "uri"
string = URI.encode_www_form(foo: 1, bar: nil, baz: 3) #=> "foo=1&bar&baz=3"
URI.decode_www_form("foo=1&bar&baz=3") #=> [["foo", "1"], ["bar", ""], ["baz", "3"]]
RbConfig :: SIZEOF
The new RbConfig :: SIZEOF method returns the size of C types.
require "rbconfig/sizeof"
RbConfig::SIZEOF["short"] #=> 2
RbConfig::SIZEOF["int"] #=> 4
RbConfig::SIZEOF["long"] #=> 8
Setting the logging category in Syslog :: Logger
Syslog :: Logger, the Logger-compatible interface for Syslog, has the ability to install categories.
require "syslog/logger"
facility = Syslog::LOG_LOCAL0
logger = Syslog::Logger.new("MyApp", facility)
logger.debug("test")
CSV.foreach without block returns an enumerator
CSV.foreach, called without a block as an argument, returns an enumerator, but when used for a long time, this led to an IOError, this was fixed.
require "csv"
enum = CSV.foreach("example.csv")
enum.next #=> ["1", "foo"]
enum.next #=> ["2", "bar"]
enum.next #=> ["3", "baz"]
Openssl bignum
OpenSSL :: BN.new now accepts not only strings, but also integers as arguments.
require "openssl"
OpenSSL::BN.new(4_611_686_018_427_387_904) #=> #
Size Enumerator.new argument accepts any called object
Enumerator.new accepts the size argument, which can be either an integer or an object with the #call method. However, prior to 2.0.0, the method in fact worked only with integers and Proc objects. Now it is fixed and works as stated in the documentation.
require "thread"
queue = Queue.new
enum = Enumerator.new(queue.method(:size)) do |yielder|
loop {yielder << queue.pop}
end
queue << "foo"
enum.size #=> 1
Curses library removed
curses has been removed from the standard library and is available as a gem .
Class Methods in TSort
The TSort class can be useful in determining the execution order of tasks from the list of dependencies. However, using it is quite troublesome, for a total you need to implement a class that includes TSort, as well as the #tsort_each_node and #tsort_each_child methods.
But now TSort has become more convenient to use with, for example, hashes. The methods available as instance methods are now available in the module itself, accepting two called objects, one as a replacement for #tsort_each_node, and the second as #tsort_each_child.
require "tsort"
camping_steps = {
"sleep" => ["food", "tent"],
"tent" => ["camping site", "canvas"],
"canvas" => ["tent poles"],
"tent poles" => ["camping site"],
"food" => ["fish", "fire"],
"fire" => ["firewood", "matches", "camping site"],
"fish" => ["stream", "fishing rod"]
}
all_nodes = camping_steps.to_a.flatten
each_node = all_nodes.method(:each)
each_child = -> step, &b {camping_steps.fetch(step, []).each(&b)}
puts TSort.tsort(each_node, each_child)
Outputs:
stream
fishing rod
fish
firewood
matches
camping site
fire
food
tent poles
canvas
tent
sleep
TCP Fast Open
Ruby 2.1 adds TCP Fast Open support , if available on your system. To check for its presence in the system, you can check the existence of the constants Socket :: TCP_FASTOPEN and Socket :: MSG_FASTOPEN.
Server:
require "socket"
unless Socket.const_defined?(:TCP_FASTOPEN)
abort "TCP Fast Open not supported on this system"
end
server = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
server.setsockopt(Socket::SOL_TCP, Socket::TCP_FASTOPEN, 5)
addrinfo = Addrinfo.new(Socket.sockaddr_in(3000, "localhost"))
server.bind(addrinfo)
server.listen(1)
socket = server.accept
socket.write(socket.readline)
Client:
require "socket"
unless Socket.const_defined?(:MSG_FASTOPEN)
abort "TCP Fast Open not supported on this system"
end
socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
socket.send("foo\n", Socket::MSG_FASTOPEN, Socket.sockaddr_in(3000, "localhost"))
puts socket.readline
socket.close
Part One Part Two