Статьи

Делаем Руби Кря — почему мы любим печатать утку

Одной из самых похвальных особенностей Ruby является его поддержка техники, известной как типирование утки . Несмотря на юмористическое название, Duck Typing позволяет разработчикам Ruby писать сжатый, чистый и читаемый код с минимальными усилиями — все то, что привлекло нас в Ruby.

«Что бы сделали Утка Доджерс?»

Основная предпосылка типизации утки проста. Если сущность выглядит, ходит и крякает как утка, во всех смыслах и целях справедливо предположить, что она имеет дело с членом вида anas platyrhynchos. В практических терминах Ruby это означает, что можно попробовать вызвать любой метод для любого объекта, независимо от его класса.

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

# Fields:
# * package_count: number of packages in manifest (integer)
# * mass:          total mass of all packages, in kilograms (BigDecimal)
class ShippingManifest
  attr_accessor :package_count, :mass
end

Каждый ShippingManifest Ваша задача — взять набор манифестов (предоставляется в Array

«Вы говорите, что монстр Лох-Несс живет в вашем джакузи?»

Проблема выглядит достаточно просто, верно? Возможно, мы могли бы разбить его на девять или десять строк кода:

 def summarise_manifests
  # Grab the latest set of Manifests (returns an Array)
  manifest_set = ShippingManifest.all

  # Initialize our counters
  total_package_count = 0
  total_mass          = BigDecimal("0")

  manifest_set.each do |m|
    total_package_count += m.package_count
    total_mass += m.mass
  end

  return [total_package_count, total_mass]
end

Легко, но многословно. Можно сократить этот метод до одной строки кода:

 def summarise_manifests
  ShippingManifest.all.sum
end

Как? Утка печатает!

«Ключ от скелета, а? Где ты это взял?

Наше однострочное решение использует одну из самых мощных функций Ruby: модуль Enumerable EnumerableArrayHash.

В частности, мы используем метод, определенный в ActiveSupportsum , который доступен в каждом проекте Rails. Как следует из названия, он используется для расчета суммы набора объектов. Это делается с помощью inject , функции, которая использует блок, чтобы свести набор объектов к одному значению. В случае sum

Это имеет смысл, когда мы говорим о числах: 1 + 1 = 2 Также легко увидеть, как его можно использовать для объединения строк:

 ['foo', 'bar', 'baz'].sum # => 'foobarbaz'

Но как Ruby знает, как добавить два экземпляра нашего класса ShippingManifest

Легко. Мы говорим это как.

«Это только часть того, кто я есть. Я на самом деле довольно сложный.

«Все является объектом» — одна из основных мантр Руби. Следствием этого является то, что (почти) каждый оператор на самом деле является вызовом метода для экземпляра объекта, что дает нам возможность определить нашу собственную реализацию для этого оператора. Это означает, что мы можем сделать это:

 class ShippingManifest < ActiveResource::Base
  # Returns a new instance of ShippingManifest containing the
  # sum of both `mass` and `package_count`
  def +(other)
    self.dup.tap do |neu|
      neu.mass          = @mass + other.mass
      neu.package_count = @package_count + other.package_count
    end
  end
end

Добавив несколько простых строк кода, мы добавили функциональность, которая сделала потенциальный ShippingManifest

«И позвольте мне еще раз напомнить вам, ребята, что вы слушаете Истину или… Аааа!»

Реализация пользовательского поведения для оператора + Существуют другие операторы, для которых реализовано пользовательское поведение: &*-<<. Даже == Нужно отсортировать набор записей? Реализуйте оператор сравнения ( <=>Enumerable.

Как всегда, некоторые примеры кода для этого поста доступны в нашей учетной записи GitHub — не стесняйтесь их раскошелиться и поэкспериментировать! Для начала попробуйте реализовать поведение для -<=>Enumerable.

Если у вас есть вопросы по любому другому аспекту Ruby, не стесняйтесь спрашивать в комментариях!