[Перевод] Ruby 2.1 в деталях (Часть 2)
RefinementsУточнения (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? #=> #
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}>»} #=> »¡
Однако значение монотонных часов имеет смысл только при сравнении с другой такой же отметкой:
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») #=> #
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 (/\[(?
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») #=> #
resolver = Resolv: MDNS.new
resolver.getaddress («example.local») #=> #
Resolv: DefaultResolver.replace_resolvers ([Resolv: Hosts.new, Resolv: MDNS.new])
Net: HTTP.get_response (URI.parse («http://example.local»)) #=> #
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 #=> #
dns = Resolv: DNS.new
dns.fetch_resource («example.com», Resolv: DNS: Resource: IN: A) do |reply, reply_name|
reply #=> #
TCPSocket.new («localhost», 8080) #=> #
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 #=> #
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 #=> #
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, изменения в сетевых классах и другие обновления в стандартной библиотеке.Первая часть