[Перевод] Ruby 2.1 в деталях (Часть 2)

ccdb8fbabd2b13bae6145fa4da9c7ac2.pngRefinementsУточнения (refinements) больше не являются экспериментальной фичей и не выводят ворнинг, а также в их реализацию добавилось несколько деталей, делающих их использование более удобным.Теперь к методу #using для активации уточнений на уровне файла добавился метод Module#using для активации в пределах модуля. Однако использование уточнений по-прежнему ограничено лексической областью видимости, т.е. они не будут активны при повторном открытии модуля.

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? #=> # end Объявления уточнений теперь наследуются при использовании Module#include, т.е. вы можете группировать уточнения, определенные в разных модулях, в одном и активировать их все, вызывая #using только для этого модуля. 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 Метод String#scrub был добавлен в Ruby 2.1 для помощи в работе со строками, содержащими некорректные байты. # 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}!» Вряд ли вы будете создавать строки подобным образом сознательно (по крайней мере я на это надеюсь), но такое случается со строками, прошедшими через несколько различных систем.Если у нас есть только конечный результат такого «путешествия», мы уже не можем восстановить все неверно закодированные символы, но мы можем хотя бы удалить их:

# 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}>»} #=> »¡ops!» Тот же результат может быть достигнут вызовом метода #encoding с текущей кодировкой и invalid: : replace в качестве аргументов: string.encode («UTF-8», invalid: : replace) #=> »¡�ops!» string.encode («UTF-8», invalid: : replace, replace:»?») #=> »¡? ops!» Улучшения производительности в классах Bignum/Rational Классы Bignum и Rational теперь используют GNU Multiple Precision Arithmetic Library (GMP) для улучшения производительности.Удален 4 уровень $SAFE Задание $SAFE = 4 должно было переводить Ruby в режим «песочницы» и позволять выполнение недоверенного кода. Однако это не было особенно эффективным, т.к. требовало немалого количества кода, разбросанного по всему интерпретатору, да и практически никогда не использовалось, почему и было в итоге удалено. $SAFE = 4 #=> # clock_gettime Ruby получил доступ к системной функции clock_gettime () с помощью метода Process.clock_gettime, который предоставляет доступ к различным значениям даты. В качестве первого аргмента методу должен передаваться id времени: Process.clock_gettime (Process: CLOCK_REALTIME) #=> 1391705719.906066 Передав Process: CLOCK_REALTIME, вы получите отметку времени Unix в качестве возвращаемого значения. Оно будет также соответствовать Time.now.to_f, но без создания объекта Time, поэтому выполнится несколько быстрее.Process.clock_gettime можно также использовать для доступа к «монотонным» часам, которые не зависят от перевода системных часов. Это может применяться для критичных временных замеров или бенчмаркинга.

Однако значение монотонных часов имеет смысл только при сравнении с другой такой же отметкой:

start_time = Process.clock_gettime (Process: CLOCK_MONOTONIC) sleep 1 Process.clock_gettime (Process: CLOCK_MONOTONIC) — start_time #=> 1.0051147330086678 Еще одним значением, которое может быть использовано для бенчмаркинга, является CLOCK_PROCESS_CPUTIME_ID. Оно работает так же, как и монотонные часы, т.е. постоянно увеличивается, но отличие в том, что оно увеличивается только когда CPU выполняет какую-либо работу. Эта отметка также имеет смысл только в сравнении с другой подобной отметкой. start_time = Process.clock_gettime (Process: CLOCK_PROCESS_CPUTIME_ID) sleep 1 Process.clock_gettime (Process: CLOCK_PROCESS_CPUTIME_ID) — start_time #=> 0.005225999999999981 Эти три значения часов, реальное, монотонное и процессорное, доступны всегда. В зависимости от используемой вами системы также возможен доступ и к другим видам часов, чтобы узнать это, читайте соответствующую документацию.Чтобы проверить доступность доступность конкретного вида часов, достаточно проверить то, что определена соответствующая константа.

Process.const_defined?(: CLOCK_PROCESS_CPUTIME_ID) #=> true Process.const_defined?(: CLOCK_THREAD_CPUTIME_ID) #=> false Также доступен метод Process.clock_getres, который позволяет узнать разрешение, предоставляемое конкретным видом часов.Обновление RubyGems Встроенная версия RubyGems была обновлена до версии 2.2. К поддержке Gemfile была добавлена поддержка Gemfile.lock, как часть работы по поддержке всех фич Bundler’а в RubyGems.Опция --file (или -g) для gem install теперь не требует обязательного задания файла зависимостей, она автоматчески определяет наличие Gemfile. gem install также будет генерировать Gemfile.lock, если он еще не создан, и учитывать версии, указанные в нем, если он уже создан.

$ ls Gemfile $ gem install -g Fetching: going_postal-0.1.4.gem (100%) Installing going_postal (0.1.4) $ ls Gemfile Gemfile.lock Полный список изменений можно найти в фале History.Удалены устаревшие фичи Rake Встроенный Rake обновлен до версии 10.1.0, в котором удалено множество deprecated-фич. В более старых версиях Rake уже давно выводилось предупреждение по поводу этих фич, поэтому проблем с совместимостью быть не должно.Если вам нужно больше подробностей, смотрите полный список изменений в Rake версий 10.0.3 и 10.1.0.

Обновление шаблона RDoc Теперь в Ruby включена версия RDoc версии 4.1, в которой содержится обновление шаблона по умолчанию с некоторыми улучшениями в плане организации доступа. Полный список изменений можно посмотреть в файле History.Имя процесса Был добавлен новый метод Process.setproctitle, позволяющий задавать имя процесса без обращения к переменной $0. Также был добавлен метод Process.argv0 чтобы получить исходное значение $0.Например, у вас есть следующий фоновый скрипт:

data.each_with_index do |datum, i| Process.setproctitle (»#{Process.argv0} — job #{i} of #{data.length}») process (datum) end тогда при запуске ps вы увидите примерно следующее: $ ps PID TTY TIME CMD 339 ttys000 0:00.23 -bash 7321 ttys000 0:00.06 background.rb — job 10 of 30 Замороженный Symbol Объекты Symbol теперь составляют компанию целым и вещественным числам в качестве «замороженных» (frozen) объектов. : foo.frozen? #=> true : foo.instance_variable_set (:@bar, «baz») #=> # Это изменение было сделано для улучшения сборки мусора для таких объектов в будущих версиях Ruby.Исправлена утечка области видимости При использовании ключевых слов private, protected, public или module_function без аргументов в строке, выполняемой с помощью eval, instance_eval или module_eval, область видимости метода «протекает» в родительскую область видимости, т.е. в примере ниже метод foo будет закрытым: class Foo eval «private» def foo «foo» end end В версии 2.1 это исправлено и foo будет открытым.#untrusted? теперь псевдоним для #tainted? Ранее в Ruby было два набора методов, чтобы помечать объекты как недоверенные, первый состоит из методов #tainted?, #taint и #untaint, второй — #untrusted?, #untrust и #trust. Они работают одинаково, но при этом выставляют разные флаги у объектов.Теперь эти методы унифицированы и выставляют один и тот же флаг, причем более предпочтительным является #tainted? и компания, а при вызове #untrusted? и др. будут появляться ворнинги.

string = «foo» string.untrust string.tainted? #=> true выведет предупреждение example.rb:2: warning: untrust is deprecated and its behavior is same as taint return в лямбдах Лямбды отличаются от блоков и Proc-объектов тем, что return возвращает управление из лямбды, а не из вызывающего метода. Однако есть исключение, если лямбда передается с & и вызывается с помощью yield. Теперь это исправлено. def call_with_yield yield end

def test call_with_yield (&lambda {return «hello from lambda»}) «hello from method» end

test #=> «hello from method» Пример выше выведет «hello from lambda» в Ruby Адреса интерфейсов Появилась возможность получить детали сетевых интерфейсов в системе с помощью метода Socket.getifaddrs. Он возвращает массив объектов Socket: Ifaddr. 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» Поддержка именованных групп в StringScanner StringScanner#[] теперь принимает в качестве аргментов объекты Symbol и возвращает значения соответствующих именованных групп. 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 (/\[(? [^\]]+)\]/) current_section = current_section[scanner[: name]] = {} elsif scanner.scan (/(? [^=]+)=(? .*)/) current_section[scanner[: key]] = scanner[: value] end end

data end YAML.safe_load В YAML (или точнее в Psych, лежащий в основе реализации) был добавлен метод safe_load. По умолчанию могут быть десериализованы следующие классы: TrueClass, FalseClass, NilClass, Numeric, String, Array и Hash. Для десериализации других классов, если вы уверены в их безопасности, нужно передать их в качестве аргумента.Если будет передан объект неразрешенного класса, будет возбуждено исключение Psych: DisallowedClass, которое также может быть получено как YAML: DisallowedClass.

require «yaml» YAML.safe_load (»: foo: 1») #=> # YAML.safe_load (»: foo: 1», [Symbol]) #=> {: foo=>1} Поддержка MDNS и LOC записей в Resolv Библиотека Resolv DNS получила базовую поддержку многоадресного DNS поиска. Он не поддерживает непрерывные запросы, и не может выполнять обнаружение сервиса, но все равно это весьма удобное нововведение (для полной поддержки DNS Service Discovery попробуйте гем dnssd). require «resolv»

resolver = Resolv: MDNS.new resolver.getaddress («example.local») #=> # Связка с библиотекой resolv-replace дает возможность использовать имена mDNS с большинством сетевых библиотек в 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»)) #=> # Resolv также получил возможность запрашивать DNS LOC записи. 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 #=> # resource.longitude #=> # И наконец, последнее изменение в Resolv, теперь можно получать полные сообщения DNS с помощью метода Resolv: DNS#fetch_resource. require «resolv»

dns = Resolv: DNS.new dns.fetch_resource («example.com», Resolv: DNS: Resource: IN: A) do |reply, reply_name| reply #=> #, Resolv: DNS: Resource: IN: A]], @answer=[[#, 79148, #, @ttl=79148>]], @authority=[], @additional=[]> reply_name #=> # end Сообщения об ошибках в классе Socket В сообщения об ошибках были добавлены адреса сокетов. require «socket»

TCPSocket.new («localhost», 8080) #=> # Ускоренный Hash#shift Производительность Hash#shift была значительно увеличена, что в сочетании с упорядоченной вставкой, появившейся в Ruby 1.9, дает возможность реализовать LRU-кэш. 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 Улучшение производительности классов Queue, SizedQueue и ConditionVariable Классы Queue, SizedQueue и ConditionVariable были ускорены засчет реализации их на C (ранее были реализованы на Ruby).Перехват внутреннего исключения в Timeout Теперь стало невозможным перехватывать исключения, используемые внутри класса Timeout для прерывания выполнения блока. Это деталь внутренней реализации, в то время как внешнее исключение Timeout: Error осталось неизменным и может быть перехвачено. require «timeout»

begin Timeout.timeout (1) do begin sleep 2 rescue Exception # no longer swallows the timeout exception end end rescue StandardError => e e #=> # end Множества В класс Set были добавлены методы #intersect? и #disjoint?. Метод #intersect? возвращает true, если объект и аргумент имеют хотя бы один общий элемент и false в противном случае, #disjoint? работает наоборот. require «set»

a = Set[1,2,3] b = Set[3,4,5] c = Set[4,5,6]

a.intersect?(b) #=> true b.intersect?© #=> true a.intersect?© #=> false

a.disjoint?(b) #=> false b.disjoint?© #=> false a.disjoint?© #=> true

Другим важным изменением в Set является то, что метод #to_set будет возвращать сам объект, а не созданную копию. require «set»

set = Set[«foo», «bar», «baz»] set.object_id #=> 70286489985620 set.to_set.object_id #=> 70286489985620 Упрощение обработки ответов WEBrick Теперь тело HTTP ответа от WEBrick может быть присвоено любому объекту с методами #read и #readpartial. Ранее это могли быть только объекты IO или String. Пример ниже реализует класс, выводящий полученный ответ каждую секунду в течение 10 секунд. 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 Метод #step класса Numeric теперь вместо позиционных аргументов может принимать именованные аргументы by: и to:. Аргумент to: является необязательным, если он не задан, последовательность будет бесконечной. При использовании позиционных аргментов этого можно достичь, указав nil в качестве первого аргумента. 0.step (by: 5, to: 20) do |i| puts i end выведет 0 5 10 15 20 0.step (by: 3) do |i| puts i end

0.step (nil, 3) do |i| puts i end в обоих случаях выведут 0 3 6 9 12 … and so on IO Метод IO#seek теперь наряду с константами IO: SEEK_CUR, IO: SEEK_END и IO: SEEK_SET принимает объекты Symbol: CUR, : END и : SETВ качестве второго аргемнта теперь можно передавать IO: SEEK_DATA и IO: SEEK_HOLE (или : DATA и : HOLE). Когда они заданы, первый аргумент используется как минимальный размер данных/пустого места для перехода.

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) Эта может поддерживаться не на всех платформах, что можно проверить с помощью IO.const_defined?(: SEEK_DATA) и IO.const_defined?(: SEEK_HOLE).Использование IO _nonblock без возбуждения исключений Методы IO#read_nonblock и IO#write_nonblock могут принимать именованный аргумент exception:. Если он задан в false (по умолчанию true), методы будут возвращать при ошибке соответствующий объект Symbol вместо возбуждения исключений. 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 игнорирует внутреннюю кодировку, если внешняя ASCII-8BIT Если вы задаете внутреннюю и внешнюю кодировки по умолчанию, Ruby будет преобразовывать из внешней кодировки во внутреннюю. Исключением является случай, когда внешняя кодировка ASCII-8BIT, в этом случае преобразования не происходит.Это же исключение должно быть сделано, если кодировки передаются методу IO в качестве аргумента, но этого не было и преобразование производилось. Баг был исправлен.

File.read («example.txt», encoding: «ascii-8bit: utf-8»).encoding #=> # #include и #prepend теперь открыты Методы #include и #prepend теперь открыты, это касается классов Module и Class. 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» В третьей части будут новые методы в классах Module и Object, изменения в сетевых классах и другие обновления в стандартной библиотеке.Первая часть

© Habrahabr.ru