Статьи

Рубин для новичков: недостающие методы

Ruby — один из самых популярных языков, используемых в сети. Здесь мы проводим сессию на Nettuts +, которая познакомит вас с Ruby, а также с отличными фреймворками и инструментами, которые сопровождают разработку на Ruby. В этом эпизоде ​​мы рассмотрим слишком крутой, чтобы быть правдивым, способ, которым объекты Ruby работают с методами, которые не существуют.



Допустим, вы работаете с объектом Ruby. И давайте также скажем, что вы не совсем знакомы с этим объектом. И давайте также скажем, что вы вызываете метод, который не существует на объекте.

1
2
3
o = Object.new
o.some_method
# NoMethodError: undefined method `some_method’ for #<Object:0x00000100939828>

Это менее чем желательно, поэтому у Ruby есть замечательный способ позволить нам избавиться от этого. Проверь это:

1
2
3
4
5
6
7
8
9
class OurClass
  def method_missing (method_name)
    puts «there’s no method called ‘#{method_name}'»
  end
end
 
o = OurClass.new
o.some_method
# => there’s no method called ‘some_method’

Мы можем создать метод под названием method_missing в нашем классе. Если объект, для которого мы вызываем метод, не имеет метода (и не наследует метод от другого класса или модуля), Ruby даст нам еще один шанс сделать что-то полезное: если у класса есть метод method_missing мы method_missing информацию о методе cal методу method_missing и позволим ему разобраться в беспорядке.

Ну, это здорово; мы больше не получаем сообщение об ошибке.


Но остановитесь и подумайте об этом на секунду. Прежде всего: нет, мы больше не получаем сообщение об ошибке, но мы не получаем что-то полезное. Трудно сказать, что было бы полезно в этом случае, потому что имя метода ничего не подсказывает. Во-вторых, это довольно мощный инструмент, потому что он позволяет вам практически передавать любой метод объекту и получать разумный результат.

Давайте сделаем то, что имеет больше смысла; начнем с этого:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
class TutsSite
  attr_accessor :name, :tutorials
   
  def initialize name = «», tuts = []
    @name = name
    @tutorials = tuts
  end
   
  def get_tuts_about_javascript
    @tutorials.select do |tut|
      tut[:tags].include?
    end
  end
   
  def get_tuts_by_jeffrey_way
    @tutorials.select do |tut|
      tut[:author] == «Jeffrey Way»
    end
  end
end

Здесь вы видите небольшой класс для учебного сайта. При создании нового объекта веб-сайта мы передаем ему имя и массив учебников. Мы ожидаем, что учебники будут хэши в следующей форме:

1
2
3
4
5
{ title: «Some title», author: «the author», tags: [«array», «of», «tags»] # Ruby 1.9
 
# OR
 
{ :title => «Some title», :author => «the author», :tags => [«array», «of», «tags»] # Ruby 1.8

Мы ожидаем символы в качестве ключей; обратите внимание, что если вы не используете Ruby 1.9, вам придется использовать нижний формат для ваших хэшей (оба работают в 1.9)

Затем у нас есть две вспомогательные функции, которые позволяют нам получить только туториал с тегом JavaScript или только туториалы Джеффри Уэя. Они полезны для фильтрации учебных пособий … но они не дают нам слишком много вариантов. Конечно, мы могли бы создать методы с именами get_tuts_with_tag и get_tuts_by_author которые принимают параметры с тегом или именем автора. Однако мы собираемся пойти другим путем: method_missing .

Как мы уже видели, method_missing получает имя метода в качестве параметра. Я не упомянул, что это символ. Также доступны параметры, которые передаются методу и блоку (если он был задан). Обратите внимание, что параметры передаются в качестве отдельных параметров в method_missing , поэтому обычным соглашением является использование оператора splat для сбора их всех в массив:

1
2
3
def method_missing name, *args, &block
 
end

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

1
2
3
4
5
6
7
nettuts.get_tuts_by_jeffrey_way
 
nettuts.get_tuts_about_html
 
nettuts.get_tuts_about_canvas_by_rob_hawkes
 
nettuts.get_tuts_by_jeremy_mcpeak_about_asp_net

Итак, давайте вернемся к этому; отбросьте те более ранние методы и замените это этим:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
def method_missing name, *args, &block
  tuts = @tutorials.dup
  name = name.to_s.downcase
  
  if (md = /^get_tuts_(by_|about_)(\w*?)((_by_|_about_)(\w*))?$/.match name)
    if md[1] == ‘by_’
      tuts.select!
      tuts.select!
    elsif md[1] == ‘about_’
      tuts.select!
      tuts.select!
    end
  else
    tuts = «This object doesn’t support the object ‘#{name}'»
  end
  tuts
end

Не волнуйтесь, мы пройдем через все это сейчас. Мы начнем с дублирования массива @tutorials ; у каждого объекта Ruby есть метод dup который его копирует; если бы мы этого не делали — а просто сказали tuts = @tutorial мы бы работали с исходным массивом, чего мы не хотим делать; мы хотим сохранить этот массив как есть. Затем мы отфильтруем учебные хеши, которые нам не нужны.

Мы также должны получить имя метода; так как он передается в method_missing как символ, мы конвертируем его в строку с to_s а затем to_s чтобы она была в нижнем регистре с downcase .

Теперь мы должны проверить, что метод соответствует формату, который мы хотим; в конце концов, возможно, что кто-то мог передать что-то еще методу. Итак, давайте разберем это имя метода. Если это соответствует, мы разработаем магию; в противном случае мы возвращаем сообщение об ошибке по умолчанию:

1
2
3
4
5
if (md = /^get_tuts_(by_|about_)(\w*?)((_by_|_about_)(\w*))?$/.match name)
   #coming
 else
   tuts = «This object doesn’t support the method ‘#{name}'»
 end

Это выглядит довольно устрашающе, но вы должны это понять: в основном, мы ищем «get_tuts_», за которым следует «by_» или «about_»; затем у нас есть имя автора или тег, за которым следует «_by_» или «_about_» и автор или тег. Если это соответствует, мы MatchData объект MatchData в md ; в противном случае мы вернем nil ; в этом случае мы установим tuts сообщения об ошибке. Мы делаем это так, чтобы в любом случае мы могли вернуть tuts .

Таким образом, регулярное выражение соответствует, мы получим объект MatchData . Если имя метода было get_tuts_by_andrew_burgess_about_html , у вас есть следующие индексы:

1
2
3
4
5
6
0. get_tuts_by_andrew_burgess_about_html
1. by_
2. andrew_burgess
3. _about_html
4. _about_
5. html

Отмечу, что если одна из необязательных групп не заполнена, ее индекс имеет значение nil .

Итак, данные, которые мы хотим, находятся в индексах 2 и 5; помните, что мы могли получить только тег, только автора или оба (в любом порядке). Итак, затем мы должны отфильтровать слова, которые не соответствуют нашим критериям. Мы можем сделать это с помощью метода select массива. Он передает каждый элемент в блок, один за другим. Если блок возвращает true , элемент сохраняется; если он возвращает false , элемент выбрасывается из массива. Давайте начнем с этого:

1
2
3
if md[1] == ‘by_’
 tuts.select!
 tuts.select!

Если md[1] «by_», мы знаем, что автор пришел первым. Поэтому внутри блока первого вызова select мы получаем имя автора tut хеша (в downcase ) и сравниваем его с md[2] . Я использую метод глобальной замены — gsub — для замены всех подчеркиваний одним пробелом. Если строки сравниваются как true, элемент сохраняется; в противном случае это не так. Во втором вызове select мы проверяем тег (хранящийся в md[5] ) в массиве tut[:tags] . Массив include? Метод вернет true если элемент находится в массиве. Обратите внимание на модификатор в конце этой строки: мы делаем это только в том случае, если четвертым индексом является строка «_about_».

Обратите внимание, что мы на самом деле используем метод select массива: мы используем select! (с ударом / восклицательным знаком). Это не возвращает новый массив только с выбранными элементами; это работает с фактическим массивом tuts в памяти.

Теперь, когда вы понимаете это, у вас не должно быть проблем со следующими строками:

1
2
3
4
elsif md[1] == ‘about_’
  tuts.select!
  tuts.select!
end

Эти строки выполняют те же действия, что и выше, но они предназначены для имен методов в обратной ситуации: первый тег, необязательный второй автор

В конце метода мы возвращаем tuts ; это либо фильтрованный массив, либо сообщение об ошибке.

Теперь давайте проверим это:

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
tuts = [
  { title: «How to transition an Image from B&W to Color with Canvas», author: «Jeffrey Way», tags: [«javascript», «canvas»] },
  { title: «Node.js Step by Step: Blogging Application», author: «Christopher Roach», tags: [«javascript», «node»] },
  { title: «The 30 CSS Selectors you Must Memorize», author: «Jeffrey Way», tags: [«css», «selectors»] },
  { title: «Responsive Web Design: A Visual Guide», author: «Andrew Gormley», tags: [«html», «responsive design»] },
  { title: «Web Development from Scratch: Basic Layout», author: «Jeffrey Way», tags: [«html»] },
  { title: «Protect a CodeIgniter Application Against CSRF», author: «Ian Murray», tags: [«php», «codeigniter»] },
  { title: «Manage Cron Jobs with PHP», author: «Nikola Malich», tags: [«php», «cron jobs»] }
]
 
nettuts = TutsSite.new «Nettuts+», tuts
 
p nettuts.get_tuts_by_ian_murray
# [{:title=>»Protect a CodeIgniter Application Against CSRF», :author=>»Ian Murray», :tags=>[«php», «codeigniter»]}]
 
p nettuts.get_tuts_about_html
# [{:title=>»Responsive Web Design: A Visual Guide», :author=>»Andrew Gormley», :tags=>[«html», «responsive design»]}, {:title=>»Web Development from Scratch: Basic Layout», :author=>»Jeffrey Way», :tags=>[«html»]}]
 
p nettuts.get_tuts_by_jeffrey_way_about_canvas
# [{:title=>»How to transition an Image from B&W to Color with Canvas», :author=>»Jeffrey Way», :tags=>[«javascript», «canvas»]}]
 
p nettuts.get_tuts_about_php_by_nikola_malich
# [{:title=>»Manage Cron Jobs with PHP», :author=>»Nikola Malich», :tags=>[«php», «cron jobs»]}]
     
p nettuts.submit_an_article
# This object doesn’t support the method ‘submit_an_article'»

Я пишу результаты этих методов, так что вы можете запустить их в файле ruby ​​в командной строке.


Я должен отметить, что, хотя это довольно круто, это не обязательно правильное использование method_missing . Это прежде всего для безопасности, чтобы спасти вас от ошибок. Однако соглашение не плохое: оно широко используется в классах ActiveRecord которые являются большой частью Ruby on Rails.


Вы, вероятно, не знали, что в JavaScript была похожая функция: это метод __noSuchMethod__ для объектов. Насколько я знаю, он поддерживается только в FireFox, но это интересная идея. Я переписал приведенный выше пример на JavaScript, и вы можете проверить его на этом JSBin .


Это обертка на сегодня! У меня в рукаве есть интересные вещи с Руби, которые скоро придут к вам. Следите за Nettuts +, и если вы хотите что-то конкретное, дайте мне знать в комментариях!