Ruby — один из самых популярных языков, используемых в сети. Мы начали новую серию скринкастов здесь, на Nettuts +, которая познакомит вас с Ruby, а также с отличными фреймворками и инструментами, которые сопровождают разработку на Ruby. На этом уроке мы подробнее рассмотрим операторов в Ruby и почему они отличаются от всего, что вы когда-либо видели.
операторы
Вы знакомы с операторами.
| 
 1 
2 
3 
 | 
 1 + 2 # 3 
  person[:name] = «Joe» 
 | 
Операторы — это такие вещи, как знак плюс (один из арифметических операторов) или знак равенства (оператор присваивания). Эти вещи не сильно отличаются от тех, которые вы используете в JavaScript, PHP или любом другом языке. Но, как и большинство Ruby, здесь происходит гораздо больше, чем кажется на первый взгляд.
Вот секрет: операторы в Ruby на самом деле являются вызовами методов. Попробуй это:
| 
 1 
 | 
 1.+(2) # 3 
 | 
 Здесь мы вызываем оператор + для объекта 1 , передавая объект 2 в качестве параметра.  Возвращаем объект 3 .  Мы можем сделать это и со строками: 
| 
 1 
2 
3 
4 
5 
 | 
 name = «Joe» 
  name.+(» Smith») # «Joe Smith», but `name` is still «Joe» 
  name += » Smith» # name is now «Joe Smith» 
 | 
  Как видите, мы можем выполнить конкатенацию строк с помощью метода + .  В качестве бонуса, ruby определяет оператор + = на основе оператора + (примечание: вы не можете использовать + = в качестве метода). 
  Как вы понимаете, это дает нам невероятную силу.  Мы можем настроить смысл добавления, вычитания и назначения объектов в наших пользовательских классах.  Мы видели, как это работает со свойствами объектов в нашем уроке о классах (мы определили property и метод property= метод в классе и получили ожидаемый синтаксический сахар для их использования).  То, на что мы здесь смотрим, делает еще один шаг вперед. 
Создание наших собственных методов оператора
  Давайте попробуем создать один из этих методов сами.  Для этого примера давайте создадим объект-холодильник, к которому мы можем добавлять вещи с помощью оператора + и извлекать вещи с помощью оператора - . 
Вот начало нашего класса:
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
 | 
 class Fridge 
    def initialize (beverages=[], foods=[]) 
        @beverages = beverages 
        @foods = foods 
    end 
      def + (item) 
      end 
      def — (item) 
      end 
end 
 | 
  Наша функция initialize довольно проста: мы берем два параметра (которые возвращаются к пустым массивам, если ничего не дано) и присваиваем их переменным экземпляра.  Теперь давайте создадим эти две функции: 
| 
 1 
2 
3 
4 
5 
6 
7 
 | 
 def + (item) 
    if item.is_a? 
        @beverages.push item 
    else 
        @foods.push item 
    end 
end 
 | 
  Это довольно просто.  Каждый объект имеет is_a?  метод, который принимает один параметр: класс.  Если объект является экземпляром этого класса, он вернет true;  в противном случае он вернет false.  Итак, это говорит о том, что если элемент, который мы добавляем в холодильник, это Beverage , мы добавим его в массив @beverages .  В противном случае мы добавим его в массив @food . 
Это хорошо; как насчет того, чтобы вынуть вещи из холодильника? (Примечание: этот метод отличается от того, который показан на видео; это показывает, что эти операторные методы дают нам большую гибкость; на самом деле это просто обычные методы, с которыми вы можете делать что угодно. Кроме того, я думаю, что это лучшая версия метода, однако, она более сложная.)
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
 | 
 def — (item) 
    ret = @beverages.find do |beverage| 
        beverage.name.downcase == item.downcase 
    end 
      return @beverages.delete ret unless ret.nil? 
      ret = @foods.find do |food| 
        food.name.downcase == item.downcase 
    end 
      @foods.delete ret 
end 
 | 
  Вот что происходит, когда мы используем оператор минус.  Параметр, который он принимает, является строкой с названием искомого элемента (кстати, мы скоро создадим классы Beverage и Food ).  Мы начнем с использования метода find который есть у массивов.  Есть несколько способов использовать этот метод;  мы передаем это блок;  этот блок говорит, что мы пытаемся найти элемент в массиве, у которого есть свойство name которое совпадает со строкой, которую мы передали;  обратите внимание, что мы конвертируем обе строки в нижний регистр, чтобы быть в безопасности. 
  Если в массиве найдется элемент, который будет сохранен в ret ;  в противном случае ret будет равен nil .  Далее мы вернем результат @beverage.delete ret , который удаляет элемент из массива и возвращает его.  Обратите внимание, что мы используем модификатор оператора в конце этой строки: мы делаем это, если только ret равен nil . 
  Вы можете удивиться, почему мы используем ключевое слово return здесь, так как оно не требуется в Ruby.  Если бы мы не использовали его здесь, функция еще не вернулась бы, так как в ней больше кода.  Использование return здесь позволяет нам возвращать значение из места, которое функция обычно не возвращает. 
  Если мы не вернемся, это означает, что элемент не был найден в @beverages .  Поэтому предположим, что это в @foods .  Мы сделаем то же самое, чтобы найти элемент в @foods и затем вернуть его. 
  Перед тестированием нам понадобятся наши классы Food и Beverages : 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
 | 
 class Beverage 
    attr_accessor :name 
      def initialize name 
        @name = name 
        @time = Time.now 
    end 
end 
class Food 
    attr_accessor :name 
      def initialize name 
        @name = name 
        @time = Time.now 
    end 
end 
 | 
  Обратите внимание, что в видео я не делал @name доступным извне объекта.  Здесь я делаю это с помощью attr_accessor :name , чтобы мы могли проверить имя этих объектов, когда они находятся в холодильнике. 
Итак, давайте проверим это в irb; начнем с того, что нам потребуется файл, содержащий код; затем, попробуйте классы; обратите внимание, что я добавил разрывы строк в выводе для облегчения чтения.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
 | 
 > require ‘./lesson_6’ 
=> true 
  > f = Fridge.new 
=> #<Fridge:0x00000100a10378 @beverages=[], @foods=[]> 
  > f + Beverage.new(«water») 
=> [#<Beverage:0x000001009fe8d0 @name=»water», @time=2011-01-15 13:20:48 -0500>] 
  > f + Food.new(«bread») 
=> [#<Food:0x000001009d3c98 @name=»bread», @time=2011-01-15 13:20:59 -0500>] 
  > f + Food.new(«eggs») 
=> [ 
    #<Food:0x000001009d3c98 @name=»bread», @time=2011-01-15 13:20:59 -0500>, 
    #<Food:0x000001009746a8 @name=»eggs», @time=2011-01-15 13:21:04 -0500> 
   ] 
  > f + Beverage.new(«orange juice») 
=> [ 
    #<Beverage:0x000001009fe8d0 @name=»water», @time=2011-01-15 13:20:48 -0500>, 
    #<Beverage:0x00000100907cd8 @name=»orange juice», @time=2011-01-15 13:21:16 -0500> 
   ] 
  > f 
=> #<Fridge:0x00000100a10378 
        @beverages=[ 
            #<Beverage:0x000001009fe8d0 @name=»water», @time=2011-01-15 13:20:48 -0500>, 
            #<Beverage:0x00000100907cd8 @name=»orange juice», @time=2011-01-15 13:21:16 -0500> ], 
        foods[ 
            #<Food:0x000001009d3c98 @name=»bread», @time=2011-01-15 13:20:59 -0500>, 
            #<Food:0x000001009746a8 @name=»eggs», @time=2011-01-15 13:21:04 -0500> ] 
> f — «bread» 
=> #<Food:0x000001009d3c98 @name=»bread», @time=2011-01-15 13:20:59 -0500> 
> f 
=> #<Fridge:0x00000100a10378 
        @beverages=[ 
            #<Beverage:0x000001009fe8d0 @name=»water», @time=2011-01-15 13:20:48 -0500>, 
            #<Beverage:0x00000100907cd8 @name=»orange juice», @time=2011-01-15 13:21:16 -0500>], 
        foods[#<Food:0x000001009746a8 @name=»eggs», @time=2011-01-15 13:21:04 -0500>] 
 | 
  По мере @beverages вы можете видеть, как что-то добавляется в массивы @beverages и @foods , а затем удаляется. 
Получить и установить операторы
Теперь давайте напишем методы для операторов get и set, используемых с хешами. Вы видели это раньше:
| 
 1 
2 
3 
 | 
 person = {} 
  person[:name] = «Joe» 
 | 
Но, поскольку эти операторы являются методами, мы можем сделать это следующим образом:
| 
 1 
2 
3 
 | 
 person.[]=(:age, 35) # to set 
  person.[](:name) # to get 
 | 
Это верно; это нормальные методы, со специальным сахаром для вашего использования.
  Давайте попробуем;  мы сделаем Club класс.  В нашем клубе есть члены с разными ролями.  Однако мы можем захотеть иметь более одного члена с данной ролью.  Таким образом, наш экземпляр Club будет отслеживать участников и их роли с помощью хэша.  Если мы попытаемся назначить второго члена на роль, вместо того, чтобы перезаписать первого, мы добавим его. 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
 | 
 class Club 
    def initialize 
        @members = {} 
    end 
      def [] (role) 
        @members[role] 
    end 
      def []= (role, member) 
      end 
end 
 | 
  Получить версию довольно просто;  мы просто пересылаем его в массив @members .  Но набор немного сложнее: 
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
 | 
 def []== (role, member) 
    if @members[role].nil? 
        @members[role] = member 
    elsif @members[role].is_a? 
        @members[role] = [ @members[role], member ] 
    else 
        @members[role].push member 
    end 
end 
 | 
Если эта роль не была установлена, мы просто установим значение этого ключа для нашего хэша участника. Если он был задан как строка, мы хотим преобразовать его в массив и поместить в этот массив исходный элемент и новый элемент. Наконец, если ни одна из этих опций не верна, это уже массив, и мы просто помещаем элемент в массив. Мы можем проверить этот класс следующим образом:
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
 | 
 c = Club.new 
  c[:chair] = «Joe» 
  c[:engineer] = «John» 
  c[:engineer] = «Sue» 
  c[:chair] # «Joe» 
  c[:engingeer] # [ «John», «Sue» ] 
 | 
Вот и вы!
Другие операторы
Конечно, это не единственные операторы, с которыми мы можем сделать это. Вот весь список:
-   Арифметические операторы: 
+ - * \ -   Операторы получения и установки: 
[] []= -   Оператор лопаты: 
<< -   Операторы сравнения: 
== < > <= >= -   Оператор равенства регистра: 
=== -   Побитовый оператор: 
| & ^| & ^ 
Спасибо за прочтение!
Если у вас есть какие-либо вопросы об этом уроке или что-либо еще, что мы обсуждали в Ruby, задавайте их в комментариях!