Ruby 2.1 в деталях (Часть 3)

Original author: Mat Sadler
  • Translation

Метод #singleton_class? для Module/Class

В классы Module и Class был добавлен метод #singleton_class?, который, как и следовало ожидать, возвращает, является ли получатель мета-классом (singleton)

class Example
  singleton_class?     #=> false
  class << self
    singleton_class?   #=> true
  end
end


Более логичный Module#ancestors

Метод #ancestors, вызванный по отношению в мета-классу, теперь возвращает массив, содержащий в том числе и мета-классы, что делает его поведение более последовательным. Он также упорядочивает вывод мета-классов, но это выполняется только если модуль был подключен к мета-классу с помощью prepend (а не include).

Object.ancestors.include?(Object)                                   #=> true
Object.singleton_class.ancestors.include?(Object.singleton_class)   #=> true


Object#singleton_method

Аналогичен #method и #instance_method, но возвращает только методы мета-класса

class Example
  def self.test
  end

  def test2
  end
end

# returns class method
Example.singleton_method(:test)    #=> #<Method: Example.test>
# doesn't return instance method
Example.singleton_method(:test2)   #=> #<NameError: undefined singleton method `test2' for `Example'>
# doesn't return inherited class method
Example.singleton_method(:name)    #=> #<NameError: undefined singleton method `name' for `Example'>


example = Object.new

def example.test
end

example.singleton_method(:test)   #=> #<Method: #<Object:0x007fc54997a610>.test>


Method#original_name

В классах Method и UnboundMethod появился метод #original_name, возвращающий имя метода без псевдонима.

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? больше не является экспериментальным, больше вобщем-то сказать о нем и нечего.

Hash#reject

Вызов метода Hash#reject в подклассе Hash выведет ворнинг. В Ruby 2.2 вызов #reject в подклассах Hash будет возвращать новый объект Hash, а не объект подкласса. Поэтому в качестве подготовки к этому изменению пока было добавлено предупреждение.

class MyHash < Hash
end

example = MyHash.new
example[:a] = 1
example[:b] = 2

example.reject {|k,v| v > 1}.class   #=> MyHash

Выведет следующее предупреждение:

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

В Ruby 2.1.1 случайно было включено полное изменение, которой возвращает объект Hash и не генерирует ворнинг, но в 2.1.2 это было исправлено назад.

Vector#cross_product

В класс Vector был добавлен метод cross_product.

require "matrix"

Vector[1, 0, 0].cross_product(Vector[0, 1, 0])   #=> Vector[0, 0, -1]


Fixnum/Bignum #bit_length

Вызов #bit_length по отношению к целому числу вернет количество цифр, необходимых для представления числа в двоичной системе.

128.bit_length                   #=> 8
32768.bit_length                 #=> 16
2147483648.bit_length            #=> 32
4611686018427387904.bit_length   #=> 63


pack/unpack и байтовое представление чисел

Методы Array#pack и String#unpack теперь могут работать с байтовым представлением длинных чисел с помощью директив Q_/Q! и 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 возвращает составные символы

Файловая система HFS Plus в Mac OS X использует кодировку UTF8-MAC для имен файлов с разделенными символами, например é представляется в виде e и U+0301, а не просто U+00E9 (с некоторыми исключениями). Dir.glob и Dir[] теперь обратно преобразуют их в UTF8-строки с составными символами.

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"


Улучшенное приведение типов для Numeric#quo

Метод Numeric#quo теперь вызывает #to_r на получателе, что должно улучшить поведение при реализации собственных подклассов Numeric. Это также означает, что в случае невозможности приведения будет возбуждено TypeError, а не ArgumentError, что правда не должно стать проблемой при переходе, т.к. TypeError является подклассом ArgumentError.

Binding#local_variable_get/_set/_defined?

В классе Binding появились методы получения/задания локальных переменных. Это может быть полезным если прямо-таки необходимо использовать именованный аргумент, совпадающий с зарезервированным ключевым словом.

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]

Или если вы хотите использовать хэш для задания переменных, например при выполнении шаблона:

def make_binding(hash)
  b = TOPLEVEL_BINDING.dup
  hash.each {|k,v| b.local_variable_set(k, v)}
  b
end

require "erb"

cover = %Q{<h1><%= title %></h1>\n<h2 class="big friendly"><%= subtitle %></h2>}
locals = {:title => "Hitchhiker's Guide to the Galaxy", :subtitle => "Don't Panic"}

ERB.new(cover).result(make_binding(locals))   #=> "<h1>Hitchhiker's Guide to the Galaxy</h1>\n<h2 class=\"big friendly\">Don't Panic</h2>"


Методы класса CGI теперь доступны из модуля CGI::Util

Класс CGI имеет несколько полезных методов экранирования url и html строк. Они были перенесены в модуль CGI::Util, который может быть включен в другие классы или скрипты.

require "cgi/util"

CGI.escape("hello world!")   #=> "hello+world%21"

include CGI::Util

escape("hello world!")       #=> "hello+world%21"


Digest::Class.file передает аргументы конструктору

Различные классы модуля Digest имеют метод для создания дайджеста для файла, этот метод был изменен, и теперь он передает конструктору все переданные аргументы кроме имени файла. Т.е. вместо:

require "digest"
Digest::SHA2.new(512).hexdigest(File.read("example.txt"))   #=> "f7fbba..."

Можно написать:

require "digest"
Digest::SHA2.file("example.txt", 512).hexdigest             #=> "f7fbba..."


Net::SMTP#rset

Теперь можно отменить SMTP-транзакцию, послав команду RSET с помощью метода Net::SMTP#rset.

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 поддерживает повторяющиеся заголовки

open-uri позволяет с помощью метода Kernel#open открывать ресурсы по URI, и расширяет возвращаемое значение с помощью OpenURI::Meta, куда был добавлен новый метод #metas, возвращающий массив значений, если заголовок был задан несколько раз, например 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


Запись в файл через Pathname

В класс Pathname добавлены методы #write и #binwrite для записи файлов.

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

В классе Tempfile теперь есть метод, аналогичный методу new, но вместо того, чтобы возвращать объект Tempfile, использующий финализатор(finaliser), удаляющий файл, когда объект подвергается сборке мусора, метод create передает объект File в блок, после выполнения которого файл удаляется.

require "tempfile"

path = nil
Tempfile.create("example") do |f|
  f                 #=> #<File:/tmp/example20140428-16851-15kf046>
  path = f.path
end
File.exist?(path)   #=> false


Поддержка группового вещания в Rinda

Теперь классы Rinda Ring могут слушать/соединяться с групповыми адресами.

Ниже пример использования Rinda для создания простого сервиса, прослушивающего групповой адрес 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

Регистрация сервиса:

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

Получение адреса сервиса:

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

У меня возникли некоторые проблемы со строкой tuple_space = ring_finger.lookup_ring_any и мне пришлось использовать:

tuple_space = nil
ring_finger.lookup_ring(0.01) {|x| break tuple_space = x}


Задание дополнительных HTTP-опций для XMLRPC

XMLRPC::Client#http возвращает объект Net::HTTP, используемый клиентом для задания некоторых конфигурационных опций, которые не могут быть заданы через сеттеры.

client = XMLRPC::Client.new("example.com")
client.http.keep_alive_timeout = 30 # keep connection open for longer
# use client ...


URI.encode_/decode_www_form обновлены под стандарт WHATWG

Методы URI.encode_www_form и URI.decode_www_form были обновлены для соответствия стандарту WHATWG.

URI.decode_www_form больше не воспринимает ; в качестве разделителя, & единственный разделитель по умолчанию, но можно задать значение разделителя с помощью именованного аргумента separator:.

require "uri"
URI.decode_www_form("foo=1;bar=2", separator: ";")   #=> [["foo", "1"], ["bar", "2"]]

URI.decode_www_form теперь также может успешно декодировать вывод URI.encode_www_form, когда его значение 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

Новый метод RbConfig::SIZEOF возвращает размер C-типов.

require "rbconfig/sizeof"

RbConfig::SIZEOF["short"]   #=> 2
RbConfig::SIZEOF["int"]     #=> 4
RbConfig::SIZEOF["long"]    #=> 8


Установка категории логгирования в Syslog::Logger

Syslog::Logger, Logger-совместимый интерфейс для Syslog, получил возможность установки категории.

require "syslog/logger"

facility = Syslog::LOG_LOCAL0
logger = Syslog::Logger.new("MyApp", facility)

logger.debug("test")


CSV.foreach без блока возвращает перечислитель

CSV.foreach, вызванный без блока в качестве аргумента, возвращает перечислитель, но при использовании в течении длительного времени это приводило к IOError, это было исправлено.

require "csv"

enum = CSV.foreach("example.csv")

enum.next   #=> ["1", "foo"]
enum.next   #=> ["2", "bar"]
enum.next   #=> ["3", "baz"]


OpenSSL bignum

OpenSSL::BN.new теперь принимает в качестве аргументов не только строки, но и целые числа.

require "openssl"

OpenSSL::BN.new(4_611_686_018_427_387_904)   #=> #<OpenSSL::BN:0x007fce7a0c56e8>


Аргумент size Enumerator.new принимает любой вызываемый объект

Enumerator.new принимает аргмент size, который может быть как целым числом, так и объектом с методом #call. Однако до 2.0.0 метод по факту работал только с целыми числами и Proc-объектами. Теперь это исправено и работает как сказано в документации.

require "thread"

queue = Queue.new
enum = Enumerator.new(queue.method(:size)) do |yielder|
  loop {yielder << queue.pop}
end
queue << "foo"
enum.size   #=> 1


Удалена библиотека curses

curses была удалена из стандартной библиотеки и доступна в качестве гема.

Методы класса в TSort

Класс TSort может быть полезен в определении порядка выполнения задач из списка зависимостей. Однако использовать его довольно хлопотно, для итого нужно реализовать класс, включающий TSort, а также методы #tsort_each_node и #tsort_each_child.

Но теперь TSort стало удобнее использовать с, например, хэшами. Методы, доступные как методы экземпляра теперь доступны в самом модуле, принимая два вызываемых объекта, один в качестве замены #tsort_each_node, второй — #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)

Выведет:

stream
fishing rod
fish
firewood
matches
camping site
fire
food
tent poles
canvas
tent
sleep


TCP Fast Open

В Ruby 2.1 добавлена поддержка TCP Fast Open, если он доступен в вашей системе. Для проверки на наличие его в системе можно проверить существование констант Socket::TCP_FASTOPEN и Socket::MSG_FASTOPEN.

Сервер:

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)

Клиент:

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

Первая часть Вторая часть
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 5

    0
    Круто! Узнал много нового о Ruby в контексте межпроцессного / межмашинного взаимодействия. Спасибо огромное!
      0
      Можно поинтересоваться, а зачем при переводе singleton был переведен, как «мета-класс»? Ни в одной публикации еще не видел такого перевода, более того — обычно — «мета-X» — это «что-нибудь, описывающее/задающее X», т.е. «мета-класс» для Ruby — это класс Class, а «мета-объект» — по аналогии — экземпляр класса Class. Если уж очень хочется русское слово — то в подавляющем большинстве переводов устойчивое я видел только «одиночка».
        0
        Встречал в русскоязычной литературе варианты: мета-класс, собственный класс, синглтон-класс. В англоязычной: singleton class, metaclass, eigen class (в последнее время не используется). Из этих вариантов я выбрал мета-класс. Слово «одиночка» в переводе singleton применяется в отношении шаблона проектирования и никакого отношения к сущностям, описываемым в статье не имеет.
          0
          поправьте на синглтон-класс, да.
          0
          Нет, мета-классы в Ruby — другое, объектная модель в Ruby хитрее, хорошо про это было написано на хабре в статье про include и extend.

          То есть singleton_method возвращает метод, хранящийся в синглтон-классе данного класса. То есть просто метод класса, а не его экземпляра.

        Only users with full accounts can post comments. Log in, please.