Разработка приложений на Ruby on Rails

Rails — приправленный Ruby

Разбить на страницы
Показывать лекцию целиком

Основанная на примерах из Главы 3, эта глава рассматривает некоторые элементы Ruby важные для Rails. Ruby многогранен, но, к счастью, нам нужна относительно малая его часть, чтобы быть продуктивным Rails-разработчиком. Более того, эта малая часть отличается от той, которая вам понадобится в Ruby для обычных задач, поэтому, если вашей целью является создание динамических веб-приложений, я рекомендую изучать Rails первым, собирая биты Ruby на этом пути. Чтобы стать экспертом Rails, вы должны понимать Ruby более глубоко, и эта книга дает вам хорошую основу для развития. Как отмечалось в Разделе 1.1.1, после окончания Rails Tutorial я советую почитать книги о чистом Ruby, такие как Beginning Ruby, The Well-Grounded Rubyist, or The Ruby Way.

В этой главе рассматривается много материала, и это нормально — не понять его весь с первого раза. Я буду часто возвращаться к нему в последующих главах.

Причины

Как мы видели в предыдущей главе, можно развить скелет приложения Rails, и даже начать тестирование, практически без знания основ языка Ruby. Мы сделали это, опираясь на код тестов предоставленный учебником, разбирая каждое сообщение об ошибке до тех пор пока не получили прохождение набора тестов. Тем не менее, такая ситуация не может длиться вечно, и мы откроем эту главу парой дополнений к сайту, которые поставят нас лицом к лицу с нашим ограниченным знанием Ruby.

Когда мы в последний раз видели наше новое приложение, мы только что обновили наши, в основном статические, страницы использовав Rails шаблон для устранения дублирования в наших представлениях, как показано в Листинге 4.1 (который является слегка переформатированной версией Листинга 3.26).

<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
    <%= stylesheet_link_tag "application", media: "all",
                                           "data-turbolinks-track" => true %>
    <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
    <%= csrf_meta_tags %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Давайте сфокусируемся на одной строке в Листинге 4.1:

<%= stylesheet_link_tag "application", media: "all",
                                       "data-turbolinks-track" => true %>

Этот код использует встроенную Rails функцию stylesheet_link_tag (о которой вы можете узнать более подробно из Rails API) для включения application.css для всех медиа типов (включая мониторы компьютеров и принтеры). Для опытного Rails разработчика эта строка выглядит просто, но в ней есть по крайней мере четыре Руби идеи которые могут сбить с толку: встроенные Rails-методы, вызов метода без скобок, символы и хэши. Мы раскроем все эти идеи в этой главе.

Помимо того, что Rails поставляются с огромным количеством встроенных функций для использования в представлениях, они также позволяют создавать новые. Такие функции называются хелперы; для того, чтобы посмотреть как создается собственный хелпер, давайте для начала рассмотрим строку с тайтлом из Листинг 4.1:

Ruby on Rails Tutorial Sample App | <%= yield(:title) %>

Этот код опирается на определение заголовка страницы (используя provide) в каждом представлении:

<% provide(:title, 'Home') %>
<h1>Sample App</h1>
<p>
  This is the home page for the
  <a href="http://railstutorial.org/">Ruby on Rails Tutorial</a>
  sample application.
</p>

Но что если мы не предоставим заголовок? Это хорошее соглашение - иметь базовый заголовок, который мы используем на каждой странице, с дополнительным переменным заголовком (тайтлом), если мы хотим быть более конкретными. Мы уже почти достигли этого с нашей текущей схемой, с одним маленьким недостатком: как вы можете видеть, если вы удалите вызов provide в одном из представлений, без специфичного для страницы заголовка полный заголовок будет выглядеть так:

Ruby on Rails Tutorial Sample App |

Другими словами, есть подходящий базовый заголовок, но есть также прицепленная вертикальная черта | в конце заголовка.

Для решения проблемы отсутствующего заголовка страницы, мы определим кастомный хелпер с названием Если хелпер специфичен для конкретного контроллера, вам следует помещать его в соответствующий файл; например, хелперы для контроллера StaticPages обычно хранятся в app/helpers/static_pages_helper.rb. В нашем случае, мы ожидаем что хелпер full_title будет использоваться на всех страницах сайта и у Rails есть специальный файл хелпера для таких случаев: app/helpers/application_helper.rb

module ApplicationHelper

  # Returns the full title on a per-page basis.
  def full_title(page_title)
    base_title = "Ruby on Rails Tutorial Sample App"
    if page_title.empty?
      base_title
    else
      "#{base_title} | #{page_title}"
    end
  end
end

Теперь, когда у нас есть хелпер, мы можем упростить наш шаблон заменив

<title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>

на

<title><%= full_title(yield(:title)) %></title>

как видно в Листинге 4.3.

<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= stylesheet_link_tag "application", media: "all",
                                           "data-turbolinks-track" => true %>
    <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
    <%= csrf_meta_tags %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Для того чтобы заставить наш хелпер работать, мы можем удалить ненужное слово "Home" из Home страницы, позволив ей вернуться к базовому заголовку. Мы сделаем это вначале обновив наш тест с кодом из Листинга 4.4, который обновляет предыдущий тест тайтла и добавляет один тест на отсутствие кастомной строки ’Home’ в заголовке. (Примечание: Если вы выполнили упражнение соотоветствующее Листингу 3.31, вам следует сохранить выражение let определяющее base_title в первом блоке describe.)

require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the content 'Sample App'" do
      visit '/static_pages/home'
      expect(page).to have_content('Sample App')
    end

    it "should have the base title" do
      visit '/static_pages/home'
      expect(page).to have_title("Ruby on Rails Tutorial Sample App")
    end

    it "should not have a custom page title" do
      visit '/static_pages/home'
      expect(page).not_to have_title('| Home')
    end
  end
  .
  .
  .
end

Посмотрим, сможете ли вы понять почему мы добавили новый тест вместо того чтобы просто преобразовать существующий. (Подсказка: ответ кроется в Разделе 3.3.1.)

Давайте запустим набор тестов чтобы проверить что один тест провальный:

$ bundle exec rspec spec/requests/static_pages_spec.rb

Чтобы получить прохождение набора тестов мы удаляем строку provide из представления страницы как это показано в Листинге 4.5.

<h1>Sample App</h1>
<p>
  This is the home page for the
  <a href="http://railstutorial.org/">Ruby on Rails Tutorial</a>
  sample application.
</p>

В этой точке тесты должны пройти:

$ bundle exec rspec spec/requests/static_pages_spec.rb

Как и со строкой для включения стилей приложения, код в Листинге 4.2 может выглядеть просто для глаз опытного Rails разработчика, но он полон новых Руби идей которые могут сбить с толку: модули, комментарии, назначение локальных переменных, булевые, контроль потока, интерполяция строк и возвращение значений. Все эти идеи тоже будут раскрыты в этой главе.

Строки и методы

Нашим основным инструментом для изучения Ruby будет Rails консоль, которая является утилитой командной строки для работы с Rails приложениями, мы впервые видели ее в Разделе 2.3.3. Сама консоль построена на интерактивном Ruby (irb), и таким образом, имеет доступ ко всей мощности языка Ruby. (Как мы увидим в Разделе 4.4.4, консоль также имеет доступ и к Rails окружению.) Запустите консоль в командной строке следующим образом:

$ rails console
Loading development environment
>>

По умолчанию консоль запускается в окружении разработки (development environment), которое является одним из трех отдельных окружений определенных в Rails (другие — тестирование и производство (test и production)). Это различие не будет иметь важного значения в этой главе, мы узнаем больше об окружениях в Разделе 7.1.1.

Консоль это замечательный инструмент обучения, и вы можете чувствовать себя свободно при ее использовании — не волнуйтесь, вы (вероятно) ничего не сломаете. При использовании консоли, нажмите Ctrl-C, если вы застряли, или Ctrl-D для выхода из консоли в целом. На протяжении оставшейся части этой главы, вы, возможно, найдете полезным консультироваться с Ruby API. Она упакована (возможно, даже слишком упакована) информацией, например, чтобы узнать больше о строках Ruby вы можете посмотреть в Ruby API вступление для String класса.

Комментарии

Ruby комментарии начинаются со знака фунт # (также называемого"знаком хэша" или, более поэтично, "октоторпом") и распространяются до конца строки. Ruby (и, следовательно, Rails) игнорирует комментарии, но они полезны для читателей (а зачастую и для самого автора!). В коде

 # Returns the full title on a per-page basis.
  def full_title(page_title)
    .
    .
    .
  end

первая строка является комментарием с указанием цели последующего определения функции.

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

$ rails console
>> 17 + 42   # Сложение целых чисел
=> 59

Если вы, двигаясь по этому разделу, будете набирать или копипастить команды в вашу консоль, вы можете, конечно, опустить комментарии, если хотите; консоль будет игнорировать их в любом случае.

Строки

Строки это, вероятно, наиболее важная структура данных для веб-приложений, так как веб-страницы, в конечном счете, состоят из строк символов отправленных с сервера на браузер. Давайте начнем изучение строк с консолью, в этот раз запустив ее командой rails c, что является сокращением для rails console:

$ rails c
>> ""         # Пустая строка
=> ""
>> "foo"      # Не пустая строка
=> "foo"

Это string literals (буквальная (литеральная) строка) (также, забавно называемая текстовая строка), созданная с использованием двойной кавычки ". Консоль печатает результат вычисления каждой строки, который, в случае буквальной (литеральной) строки, и есть сама строка.

Мы можем также объединить строки + оператором:

>> "foo" + "bar"    # Конкатенация строк
=> "foobar"

Здесь результатом оценки выражения Для того чтобы узнать больше о происхождении "foo" и "bar"—и, в частности, возможной не-связанности "foobar" с "FUBAR"—см. Jargon File entry on "foo".

Другой способ создания строк — через интерполяцию с помощью специального синтаксиса Программисты, знакомые с Perl или PHP могут сравнить это с автоматической интерполяцией переменных со знаком доллара в выражениях вроде "foo $bar"

>> first_name = "Michael"    # Присвоение переменной
=> "Michael"
>> "#{first_name} Hartl"     # Интерполяция строки
=> "Michael Hartl"

Здесь мы присвоили значение "Michael" переменной first_name а затем интерполировали ее в строку "#{first_name} Hartl". Мы также можем присвоить имя обеим строкам:

>> first_name = "Michael"
=> "Michael"
>> last_name = "Hartl"
=> "Hartl"
>> first_name + " " + last_name    # Конкатенация с пробелом
=> "Michael Hartl"
>> "#{first_name} #{last_name}"    # Эквивалентная интерполяция
=> "Michael Hartl"

Отметим, что последние два выражения являются эквивалентными, но я предпочитаю интерполированную версию; добавление одного пробела " " кажется мне немного неуклюжим.

Вывод на экран

Для того чтобы вывести на экран (print, напечатать) строку обычно используют Ruby функцию puts (произносится "put ess", от "put string"):

>> puts "foo"     # вывести на экран строку
foo
=> nil

Метод puts работает с побочным эффектом: выражение puts "foo" выводит строку на экран, а затем возвращает буквально ничего: nil это особое обозначение Ruby для "вообще ничего". (В дальнейшем, я буду иногда опускать => nil часть для простоты.)

Использование puts автоматически добавляет символ новой строки \n к выводу; связанный print метод — нет:

# выводит на экран строку (тоже что и puts, но без символа новой строки):
>> print "foo"
foo=> nil
>> print "foo\n"  # То же что и puts "foo"
foo
=> nil

Строки в одинарных кавычках

Все примеры до сих пор использовали строки в двойных кавычках, но Ruby также поддерживает строки в одиночных кавычках. Для многих целей оба типа строк практически идентичны:

>> 'foo'          # Строка в одиночных кавычках
=> "foo"
>> 'foo' + 'bar'
=> "foobar

Хотя есть важное отличие; Ruby не будет интерполировать строки в одиночных кавычках:

>> '#{foo} bar'     # Строки в одиночных кавычках не позволяют делать интерполяцию
=> "\#{foo} bar"

Обратите внимание, как консоль возвращает значения с использованием строк в двойных кавычках, которые требуют обратной косой черты, чтобы маскировать специальные символы, такие как #.

Если строки в двойных кавычках могут делать все то же, что и одиночно закавыченные, и могут интерполировать, какой смысл в одиночных кавычках? Они часто бывают полезны, потому что они действительно буквальные, и хранят в точности такие символы, как вы вводите. Например, "обратный слэш" (бэкслэш) — символ специальный в большинстве систем, например, буквальной новой строке \n. Если вы хотите чтобы переменная содержала буквально обратный слэш, в одиночных кавычках это сделать проще:

>> '\n'       # Буквальная комбинация 'бэкслэш n'
=> "\\n"

Как и с символом # в нашем предыдущем примере, Ruby необходимо маскировать обратный слэш; посредством дополнительного бэкслэша, внутри строки в двойных кавычках, буквальный бэкслэш представлен двумя бэкслэшами. Для небольшого примера, как этот, это небольшое спасение, но если есть много элементов, которые нужно маскировать, это может реально помочь:

>> 'Newlines (\n) and tabs (\t) both use the backslash character \.'
=> "Newlines (\\n) and tabs (\\t) both use the backslash character \\."

Объекты и передача сообщений

Все в Ruby, включая строки и даже nil, является объектом. Мы увидим технический смысл этого выражения в Разделе 4.4.2, но я не думаю, что кто-нибудь когда-нибудь понял объекты, прочитав определение в книге, вы должны создать свое интуитивное понимание объектов, видя множество примеров.

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

>> "foobar".length        # Передача сообщения "length" строке
=> 6

Как правило, сообщения, которые передаются объектам, это методы, которые являются функциями, определенными для этих объектов.4 Строки также реагируют на empty? метод:

>> "foobar".empty?
=> false
>> "".empty?
=> true

Обратите внимание на знак вопроса в конце empty метода. Это конвенция Ruby обозначающая, что возвращаемое значение — boolean (булево, логика): true (истина) или false (ложь). Булевые особенно полезны для управления потоком:

>> s = "foobar"
>> if s.empty?
>>   "The string is empty"
>> else
>>   "The string is nonempty"
>> end
=> "The string is nonempty"

Булевы также могут быть объединены с помощью ("и"), || ("или"), и ! ("не") операторов:

>> x = "foo"
=> "foo"
>> y = ""
=> ""
>> puts "Both strings are empty" if x.empty?  y.empty?
=> nil
>> puts "One of the strings is empty" if x.empty? || y.empty?
"One of the strings is empty"
=> nil
>> puts "x is not empty" if !x.empty?
"x is not empty"
=> nil

Поскольку все в Ruby является объектом, следовательно, nil тоже является объектом, поэтому он тоже может отвечать на методы. Одним из примеров является to_s метод, который может конвертировать практически любой объект в строку:

>> nil.to_s
=> ""

Это, конечно, кажется, пустой строкой, что мы можем проверить, цепочкой сообщений, передаваемых к nil:

>> nil.empty?
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.empty?
>> nil.to_s.empty?      # Сцепление сообщений
=> true

Здесь мы видим, что nil объект сам по себе не реагирует на empty? метод, но nil.to_s реагирует.

Вот специальный метод для проверки на nil-ность, о котором вы могли догадаться:

>> "foo".nil?
=> false
>> "".nil?
=> false
>> nil.nil?
=> true

Код

puts "x is not empty" if !x.empty?

также показывает альтернативное использование ключевого слова if Ruby позволяет писать утверждение, которое вычисляется только тогда, когда оператор, следующий за if это истина. Есть дополнительное ключевое слово unless которое работает по той же схеме:

>> string = "foobar"
>> puts "The string '#{string}' is nonempty." unless string.empty?
The string 'foobar' is nonempty.
=> nil

Стоит отметить, что nil объект уникален, тем, что это единственный объект Ruby, который является ложью в булевом контексте, кроме, непосредственно, false:

>> if nil
>>   true
>> else
>>   false        # nil является false
>> end
=> false

В частности, все другие объекты Ruby являются true, даже 0:

>> if 0
>>   true        # 0 (и все остальные кроме nil и самой false) является true
>> else
>>   false
>> end
=> true

Определение методов

Консоль позволяет определять методы точно так же, как мы это делали с home действием из Листинга 3.6 или хелпером из Листинга 4.2. (Определение методов в консоли - немного громоздкое мероприятие, и обычно вы будете использовать файл, но это удобно для демонстрационных целей.) Например, давайте определим функцию string_message которая принимает один аргумент и возвращает сообщение в зависимости от того, пустой аргумент или нет:

>> def string_message(string)
>>   if string.empty?
>>     "It's an empty string!"
>>   else
>>     "The string is nonempty."
>>   end
>> end
=> nil
>> puts string_message("")
It's an empty string!
>> puts string_message("foobar")
The string is nonempty.

Обратите внимание, что Ruby функции имеют неявное возвращение (скрытый return) то есть, они возвращают последнее оцененное утверждение — в данном случае, одну из двух строк сообщения, в зависимости от того, пуст или нет аргумент string. Ruby также имеет явный вариант возвращения (явный return); следующие функции эквивалентны приведенным выше:

>> def string_message(string)
>>   return "It's an empty string!" if string.empty?
>>   return "The string is nonempty."
>> end

(Внимательный читатель может заметить в этой точке, что второе return здесь фактически ненужно — состояние последнего выражения в функции, т.е. строки "The string is nonempty." будет возвращено независимо от ключевого слова return, но использование return в обоих местах придает ему приятную симметрию.)

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

>> def string_message(the_function_argument)
>>   if the_function_argument.empty?
>>     "It's an empty string!"
>>   else
>>     "The string is nonempty."
>>   end
>> end
=> nil
>> puts string_message("")
It's an empty string!
>> puts string_message("foobar")
The string is nonempty.

Возвращение к title хелперу

Теперь мы в состоянии понять Ну, все же осталась одна вещь, которую мы не понимаем — как Rails связывает все это вместе: направляет URL к действиям, делает full_title хелпер доступным в представлениях и т.д.. Это интересная тема, и я поощряю вас исследовать ее далее, но точное знание того, как Rails работает, не является необходимым для использования Rails. (Для более глубокого понимания я рекомендую The Rails 4 Way Оби Фернандеса.)

module ApplicationHelper

  # Возвращает полный заголовок зависящий от страницы # Документирующий комментарий
  def full_title(page_title)                          # Определение метода
    base_title = "Ruby on Rails Tutorial Sample App"  # Назначение переменной
    if page_title.empty?                              # Булевый тест
      base_title                                      # Явное возвращение
    else
      "#{base_title} | #{page_title}"                 # Интерполяция строки
    end
  end
end

Все эти элементы — определение функции, присваивание переменной, логические тесты, управление потоком, и интерполяция строки — собраны вместе, чтобы сделать компактный вспомогательный метод для использования в шаблоне нашего сайта. Последний элемент это module ApplicationHelper: модули позволяют нам складывать вместе связанные методы, затем они (модули) могут быть подмешаны в Ruby-классы с помощью include. Если вы пишете на обычном Ruby, вы обычно пишете модули и сами их явно включаете, но в случае с хелпер-модулем Rails делает включение за нас. Результатом является то, что full_title метод автомагически доступен во всех наших представлениях.

Другие структуры данных

Хотя веб-приложения, в конечном счете это строки, фактически, для изготовления этих строк требуется также использование других структур данных. В этом разделе мы узнаем о некоторых структурах данных Ruby, важных для написания Rails приложений.

Массивы и диапазоны

Массив это всего лишь список элементов в определенном порядке. Мы еще не обсуждали массивы в Rails Tutorial, но их понимание дает хорошую основу для понимания хэшей (Раздел 4.3.3) и для аспектов Rails моделирования данных (таких как has_many ассоциации, которые мы видели в Разделе 2.3.3 и больше раскроем в Разделе 10.1.3).

Мы потратили много времени на понимание строк, и есть естественный способ перейти от строк к массивам, используя split метод:

>>  "foo bar     baz".split     # Разбиение строки на трех-элементный массив.
=> ["foo", "bar", "baz"]

Результатом этой операции является массив из трех строк. По умолчанию, split делит строку на массив путем разделения по пробелу, но вы можете разделить практически по чему угодно:

>> "fooxbarxbazx".split('x')
=> ["foo", "bar", "baz"]

Как это принято в большинстве языков программирования, Ruby массивы — нулевого сдвига, что означает, что первый элемент массива имеет индекс 0, второй имеет индекс 1, и так далее:

>> a = [42, 8, 17]
=> [42, 8, 17]
>> a[0]               # Ruby использует квадратные скобки для доступа к массиву.
=> 42
>> a[1]
=> 8
>> a[2]
=> 17
>> a[-1]              # Индексы могут быть даже отрицательными!
=> 17

Мы видим здесь что Ruby использует квадратные скобки для доступа к элементам массива. В дополнение к этой скобковой записи, Ruby предлагает синонимы для некоторых часто используемых элементов:Second метод, используемый здесь, не является в настоящий момент частью Ruby непосредственно, а скорее добавляется Rails. Это работает в данном случае, потому что консоль Rails автоматически включает Rails расширения в Ruby

>> a                  # Просто напоминание о том что такое 'a'
=> [42, 8, 17]
>> a.first
=> 42
>> a.second
=> 8
>> a.last
=> 17
>> a.last == a[-1]    # Сравнение с помощью ==
=> true

Последняя строка вводит оператор проверки на равенство ==, который Ruby разделяет со многими другими языками, наряду со связанным != ("не равно"), и т.д.:

>> x = a.length       # Как и строки, массивы отвечают на метод 'length'.
=> 3
>> x == 3
=> true
>> x == 1
=> false
>> x != 1
=> true
>> x >= 1
=> true
>> x < 1
=> false

В дополнение к length (первая строка в приведенном выше примере), массивы отвечают на множество других методов:

>> a
=> [42, 8, 17]
>> a.sort
=> [8, 17, 42]
>> a.reverse
=> [17, 8, 42]
>> a.shuffle
=> [17, 42, 8]
>> a
=> [42, 8, 17]

обратите внимание на то, что ни один из вышеприведенных методов не меняет саму a. Для того, чтобы изменить массив, используется соответствующие "бэнг" методы (названные так потому что восклицательный знак обычно произносится как "бэнг" в этом контексте):

>> a
=> [42, 8, 17]
>> a.sort!
=> [8, 17, 42]
>> a
=> [8, 17, 42]

Вы также можете добавлять данные в массивы с помощью push (# отправить, толкнуть) метода или эквивалентного ему оператора, <<:

>> a.push(6)                  # Отправка 6 в массив
=> [42, 8, 17, 6]
>> a << 7                     # Отправка 7 в массив
=> [42, 8, 17, 6, 7]
>> a << "foo" << "bar"        # Сцепление отправляемых в массив данных
=> [42, 8, 17, 6, 7, "foo", "bar"]

Последний пример показывает что вы можете сцеплять добавления вместе, а также, что, в отличие от массивов во многих других языках, Ruby массивы могут содержать смесь различных типов (в данном случае, целые числа и строки).

Прежде мы видели что split преобразовывает строку в массив. Мы также можем пойти другим путем с join методом:

>> a
=> [42, 8, 17, 7, "foo", "bar"]
>> a.join                       # Объединение без ничего.
=> "428177foobar"
>> a.join(', ')                 # Объединение через запятую.
=> "42, 8, 17, 7, foo, bar"

Тесно связаны с массивами диапазоны, которые проще всего понять посредством преобразования их в массивы, с помощью to_a метода:

>> 0..9
=> 0..9
>> 0..9.to_a              # Упс, вызвали to_a на 9.
NoMethodError: undefined method `to_a' for 9:Fixnum
>> (0..9).to_a            # Использование круглых скобок для вызова to_a на диапазоне.
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Хотя 0..9 и является допустимым диапазоном, второе выражение показывает, что нам нужно добавить скобки для вызова метода на нем.

Диапазоны полезны для вытаскивания элементов массива:

>> a = %w[foo bar baz quux]         # Применение %w для создания массива строк.
=> ["foo", "bar", "baz", "quux"]
>> a[0..2]
=> ["foo", "bar", "baz"]

Особенно полезным трюком является использование индекса -1 в конце диапазона для выборки каждого элемента от заданной точки до конца массива без необходимости явно использовать длину массива:

>> a = (0..9).to_a
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>> a[2..(a.length-1)]               # Явное использование длины массива.
=> [2, 3, 4, 5, 6, 7, 8, 9]
>> a[2..-1]                         # Использование трюка с индексом -1.
=> [2, 3, 4, 5, 6, 7, 8, 9]

Диапазоны также работают с буквами:

>> ('a'..'e').to_a
=> ["a", "b", "c", "d", "e"]

Блоки

И массивы и диапазоны отвечают на множество методов, которые принимают блоки, которые одновременно являются и самыми мощными и одними из самых непонятных элементов Руби:

>> (1..5).each { |i| puts 2 * i }
2
4
6
8
10
=> 1..5

Этот код вызывает each метод на диапазоне (1..5) и передает ему блок { |i| puts 2 * i }. Вертикальные линии вокруг имени переменной в |i| являются Ruby синтаксисом для блоковых переменных и это позволяет методу узнать, что делать с блоком; в данном случае диапазонный each метод может обрабатывать блок с одной локальной переменной, которую мы называли i и он просто выполняет блок для каждого значения в диапазоне.

Фигурные скобки это один из способов обозначить блок, но есть также второй способ:

>> (1..5).each do |i|
?>   puts 2 * i
>> end
2
4
6
8
10
=> 1..5

Блоки часто могут быть более чем из одной строки. В Rails Tutorial мы будем следовать общей конвенции использования фигурных скобок только для коротких однострочных блоков и использовать do..end синтаксис для длинных однострочных и многострочных блоков:

>> (1..5).each do |number|
?>   puts 2 * number
>>   puts '--'
>> end
2
--
4
--
6
--
8
--
10
--
=> 1..5

Здесь я использовал number вместо i просто чтобы подчеркнуть, что имя переменной может быть любым.

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

>> 3.times { puts "Betelgeuse!" }   # 3.times принимает блок без переменных.
"Betelgeuse!"
"Betelgeuse!"
"Betelgeuse!"
=> 3
>> (1..5).map { |i| i**2 }          # Нотация ** обозначает 'степень'.
=> [1, 4, 9, 16, 25]
>> %w[a b c]                        # Вспомните что %w создает массив строк.
=> ["a", "b", "c"]
>> %w[a b c].map { |char| char.upcase }
=> ["A", "B", "C"]
>> %w[A B C].map { |char| char.downcase }
=> ["a", "b", "c"]

Как вы можете видеть, map метод возвращает результат применения данного блока для каждого элемента в массиве или диапазоне.

Кстати, теперь мы в состоянии понять строку Ruby, которую я вбросил в Разделе 1.4.4 для генерации случайных субдоменов:

('a'..'z').to_a.shuffle[0..7].join

Давайте построим ее шаг за шагом:

>> ('a'..'z').to_a                     # Массив букв в алфавитном порядке
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
>> ('a'..'z').to_a.shuffle             # Перемешиваем его.
=> ["c", "g", "l", "k", "h", "z", "s", "i", "n", "d", "y", "u", "t", "j", "q",
"b", "r", "o", "f", "e", "w", "v", "m", "a", "x", "p"]
>> ('a'..'z').to_a.shuffle[0..7]       # Вытаскиваем первые восемь элементов.
=> ["f", "w", "i", "a", "h", "p", "c", "x"]
# Объединяем их вместе чтобы сделать одну строку.
>> ('a'..'z').to_a.shuffle[0..7].join
=> "mznpybuj"

Хэши и символы

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

>> user = {}                          # {} это пустой хэш.
=> {}
>> user["first_name"] = "Michael"     # Ключ "first_name", значение "Michael"
=> "Michael"
>> user["last_name"] = "Hartl"        # Ключ "last_name", значение "Hartl"
=> "Hartl"
>> user["first_name"]                 # Доступ к элементам как в массивах.
=> "Michael"
>> user                               # Буквальное представление хэша
=> {"last_name"=>"Hartl", "first_name"=>"Michael"}

Хэши обозначаются фигурными скобками, содержащими пары ключ-значение; фигурные скобки без пары ключ-значение, т. е. {} — это пустой хэш. Важно отметить, что фигурные скобки для хэшей не имеют ничего общего с фигурными скобками для блоков. (Да, это может привести к путанице.) Хотя хэши и напоминают массивы, одним важным отличием является то, что хэши не гарантируют сохранность их элементов в определенном порядке.Ruby 1.9 фактически гарантирует, что хеши сохраняют свои элементы в том же самом порядке в каком они вводились, но было бы неблагоразумно когда-либо рассчитывать на определенный порядок. Если порядок важен, используйте массив.

Вместо того, чтобы определять хэши поэлементно, используя квадратные скобки, проще использовать их буквальное представление с ключами и значениями разделенными =>, который называют "hashrocket":

>> user = { "first_name" => "Michael", "last_name" => "Hartl" }
=> {"last_name"=>"Hartl", "first_name"=>"Michael"}

Здесь я использовал обычную конвенцию Ruby, поместив дополнительные пробелы на двух концах хэша — конвенцию, игнорируемую при выводе на консоль. (Не спрашивайте меня, почему пробелы попали в конвенцию; вероятно, раньше какому-то влиятельному Программисту Ruby понравился внешний вид лишних пробелов, и они застряли в конвенции)

До сих пор мы использовали строки в качестве хэш-ключей, но в Rails гораздо чаще вместо них используются символы. Символы выглядят как строки, но с префиксом двоеточие, а не в кавычках. Например, :name это символ. Вы можете думать о символах, в основном, как о строках без дополнительного багажа:В результате наличия меньшего количества багажа символы легче сравнить друг с другом; строки должны быть сравнены посимвольно, в то время как символы могут быть сравнены все одним разом. Это делает их идеальными для использования в качестве хеш ключей.

>> "name".split('')
=> ["n", "a", "m", "e"]
>> :name.split('')
NoMethodError: undefined method `split' for :name:Symbol
>> "foobar".reverse
=> "raboof"
>> :foobar.reverse
NoMethodError: undefined method `reverse' for :foobar:Symbol

Символы это специальный тип данных Ruby, очень мало используемый в других языках, так что они могут показаться странными на первый взгляд, но Rails довольно часто их использует, так что вы быстро к ним привыкнете.

В терминах символов как хэш-ключей, мы можем определить user хэш следующим образом:

>> user = { :name => "Michael Hartl", :email => "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> user[:name]              # Доступ к значению соответствующему :name.
=> "Michael Hartl"
>> user[:password]          # Доступ к значению неопределенного ключа.
=> nil

Здесь мы видим из последнего примера, что хэш-значение для неопределенного ключа просто nil.

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

>> h1 = { :name => "Michael Hartl", :email => "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> h2 = { name: "Michael Hartl", email: "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> h1 == h2
=> true

Второй синтаксис заменяет комбинацию символ/hashrocket на имя за которым следует двоеточие и ключ:

{ name: "Michael Hartl", email: "michael@example.com" }

Эта конструкция более близка к нотации хэшей используемой в других языках (таких как JavaScript) и наслаждается возрастающей популярностью в Rails сообществе. Поскольку оба варианта синтаксиса все еще часто используются, совершенно необходимо уметь распознавать их. К сожалению это может привести к путанице, поскольку :name является валидным сам по себе (как отдельно взятый символ) но name: сам по себе не имеет значения. Суть в том, что :name => и name: являются фактически одинаковыми только внутри литеральных хэшей, таким образом

{ :name => "Michael Hartl" }

и

{ name: "Michael Hartl" }

эквивалентны, но в противном случае вы должны использовать :name (с двоеточием впереди) для обозначения символа.

Хеш значениями может быть практически все, даже другие хэши, как видно в Листинге 4.6.

>> params = {} # Определение хэша с именем 'params' (сокращение для 'parameters').
=> {}
>> params[:user] = { name: "Michael Hartl", email: "mhartl@example.com" }
=> {:name=>"Michael Hartl", :email=>"mhartl@example.com"}
>> params
=> {:user=>{:name=>"Michael Hartl", :email=>"mhartl@example.com"}}
>>  params[:user][:email]
=> "mhartl@example.com

Этот вид хэшей-в-хэшах, или вложенных хэшей, интенсивно используется Рельсами, как мы увидим в Разделе 7.3.

Также как массивы и диапазоны, хэши реагируют на each метод. Рассмотрим, например, хэш с именем flash с ключами для двух условий :success и :error:

>> flash = { success: "It worked!", error: "It failed." }
=> {:success=>"It worked!", :error=>"It failed."}
>> flash.each do |key, value|
?>   puts "Key #{key.inspect} has value #{value.inspect}"
>> end
Key :success has value "It worked!"
Key :error has value "It failed."

Отметим, что в то время как each метод для массивов принимает блок только с одной переменной, each для хэшей принимает два — ключ и значение. Таким образом, each метод для хэшей итерирует по одной хэш паре ключ-значение за раз.

Последний пример использует полезный inspect метод, который возвращает строку с буквальным представлением объекта на котором он был вызван:

>> puts (1..5).to_a            # Вывести массив как строку.
1
2
3
4
5
>> puts (1..5).to_a.inspect    # Вывести буквальный массив.
[1, 2, 3, 4, 5]
>> puts :name, :name.inspect
name
:name
>> puts "It worked!", "It worked!".inspect
It worked!
"It worked!"

Кстати, использование inspect для печати объекта достаточно обычное явление, для этого даже есть специальное сокращение - p функция:

>> p :name             # Тоже что и 'puts :name.inspect'
:name

Вновь CSS

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

<%= stylesheet_link_tag "application", media: "all",
                                       "data-turbolinks-track" => true %>

Теперь мы почти в состоянии это понять. Как уже упоминалось вкратце в Разделе 4.1, Rails определяет специальную функцию для включения таблиц стилей и

stylesheet_link_tag "application", media: "all",
                                   "data-turbolinks-track" => true

это вызов этой функции. Но есть несколько вопросов. Во-первых, где скобки? В Ruby, они не являются обязательными; эти два выражения эквивалентны:

# Круглые скобки необязательны при вызове функции.
stylesheet_link_tag("application", media: "all",
                                   "data-turbolinks-track" => true)
stylesheet_link_tag "application", media: "all",
                                   "data-turbolinks-track" => true

Во-вторых, media аргумент определенно выглядит как хэш, но где фигурные скобки? Когда хэш — последний аргумент в вызове функции, фигурные скобки не являются обязательными; эти два выражения эквивалентны:

# Фигурные скобки необязательны если хэш является последним аргументом функции.
stylesheet_link_tag "application", { media: "all",
                                     "data-turbolinks-track" => true }
stylesheet_link_tag "application", media: "all",
                                   "data-turbolinks-track" => true

Далее. Почему data-turbolinks-track пара ключ/значение использует старый синтаксис хэшей? Это связано с тем что использование нового синтаксиса для написания

data-turbolinks-track: true

приведет к попытке создания символа :data-turbolinks-track, который (оказывается) невалиден из-за дефисов. Это вынуждает нас использовать старый синтаксис приводящий к

"data-turbolinks-track" => true

Наконец, почему Ruby корректно интерпретирует строки

stylesheet_link_tag "application", media: "all",
                                   "data-turbolinks-track" => true

даже с разрывом строки между конечными элементами? Ответ заключается в том что Ruby не видит разницы между знаками новой строки и прочими пробелами (в данном контексте).Знак новой строки это то что идет в конце строки, тем самым начиная новую строку. В коде он представлен знаком \n.Причиной по которой я разбил код на части является моя привычка удерживать длину строк исходного кода в рамках 80 знаков для разборчивости.Подсчитывание столбцов вручную может свести вас с ума, поэтому многие текстовые редакторы имеют визуальные средства которые могут помочь вам с этим. Например, если вы внимательно посмотрите на Рис. 1.1, то вы увидите маленькую вертикальную черту справа которая помогает удерживать длину строк исходного кода в пределах 80 знаков. (На самом деле она установлена на 78 столбцов, что дает вам небольшой запас на ошибку.) Если вы используете TextMate, вы можете найти эту фичу в View > Wrap Column > 78. В Sublime Text, вы можете использовать View > Ruler > 78 или View > Ruler > 80.

Таким образом, мы видим теперь, что строка

stylesheet_link_tag "application", media: "all",
                                   "data-turbolinks-track" => true

вызывает stylesheet_link_tag функцию с двумя аргументами: строкой, указывающей путь к таблице стилей и хэшем, с двумя элементами, указывающими медиа тип и говорящими Rails о необходимости использовать фичу turbolinks (появилась в Rails 4). (Turbolinks будут более детально описаны в следующем издании учебника.) Из-за <%= %> скобок, результаты вставляются в шаблон ERb-ом и если вы посмотрите исходный код страницы в браузере, вы должны увидеть HTML, необходимый для включении таблиц стилей (Листинг 4.7). (Вы можете увидеть некоторые дополнительные вещи вроде ?body=1, после названий файлов CSS. Они вставляются Rails для того чтобы браузеры перегружали CSS после их изменения на сервере.)

<link data-turbolinks-track="true" href="/assets/application.css" media="all"
rel="stylesheet" />

Если вы посмотрите на сам CSS файл перейдя на http://localhost:3000/assets/application.css, вы увидите что он (за исключением нескольких комментариев) пуст. Мы это изменим в Главе 5.

Ruby классы

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

Конструкторы

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

>> s = "foobar"   # Буквальный конструктор для строк использующий двойные кавычки
=> "foobar"
>> s.class
=> String

Мы видим здесь, что строки реагируют на метод class, и просто возвращают класс к которому они принадлежат

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

>> s = String.new("foobar")   # Именованный конструктор для строки
=> "foobar"
>> s.class
=> String
>> s == "foobar"
=> true

Это эквивалентно буквальному конструктору, но это более четко говорит о том, что мы делаем.

Массивы в этом контексте работают так же как строки:

>> a = Array.new([1, 3, 2])
=> [1, 3, 2]

Хэши работают по-другому. Если конструктор массива Array.new принимает начальное значение для массива, Hash.new принимает дефолтное значение для хэша, которое является значением хэша с несуществующим ключом:

>> h = Hash.new
=> {}
>> h[:foo]          # Попытка обратиться к значению несуществующего ключа :foo.
=> nil
>> h = Hash.new(0)  # Теперь несуществующие ключи будут возвращать 0 вместо nil.
=> {}
>> h[:foo]
=> 0

Когда метод вызывается на самом классе, как это происходит в случае с new, он называется методом класса. Результатом вызова new на классе является объект этого класса, также называемый экземпляром класса. Метод вызываемый на экземпляре, такой как length, называется методом экземпляра.

Наследование классов

При изучении классов, полезно выяснять иерархию классов, используя superclass метод:

>> s = String.new("foobar")
=> "foobar"
>> s.class                        # Поиск класса к которому принадлежит s.
=> String
>> s.class.superclass             # Поиск суперкласса String.
=> Object
# Ruby 1.9 использует новый базовый класс BasicObject:
>> s.class.superclass.superclass
=> BasicObject
>> s.class.superclass.superclass.superclass
=> nil

Диаграмма этой иерархии наследования представлена на рис 4.1. Мы видим здесь, что суперклассом String является Object , а суперкласс Object это BasicObject, но BasicObject не имеет суперкласса. Эта модель относится к каждому объекту Ruby: можно проследить иерархию классов достаточно далеко, и каждый класс в Ruby в конечном счете наследуется от BasicObject, который не имеет своего суперкласса. Это техническое значение выражения "все в Ruby является объектом".

(рис 4.1) Иерархия наследования String класса.

Создание собственного класса незаменимо для более глубокого понимания классов. Давайте сделаем Word класс с palindrome? методом, который возвращает true, если слово можно читать и справа налево и слева направо, сохраняя смысл:

>> class Word
>>   def palindrome?(string)
>>     string == string.reverse
>>   end
>> end
=> nil

Мы можем использовать его следующим образом:

>> w = Word.new              # Создание нового Word объекта.
=> #<Word:0x22d0b20>
>> w.palindrome?("foobar")
=> false
>> w.palindrome?("level")
=> true

Если этот пример поражает вас, как немного надуманный, хорошо; так и было задумано. Довольно странно создавать новый класс только для того чтобы создать метод принимающий строку в качестве аргумента. Поскольку слово это строка, более естественным решением будет унаследовать наш Word класс от String, как это показано в Листинге 4.8. (Вы должны выйти из консоли и ввести его заново, чтобы убрать старое определение Word.)

>> class Word < String             # Word наследует от String.
>>   # Возвращает true если строка соответствует своему перевертышу.
>>   def palindrome?
>>     self == self.reverse        # self это сама строка.
>>   end
>> end
=> nil

Здесь Word < String это Ruby-синтаксис для наследования (кратко обсуждается в Разделе 3.1), который гарантирует, что, в дополнение к новому palindrome? методу, words (слова) также имеют все те же методы, что и строки:

>> s = Word.new("level") # Создать новый объект Word, инициализирует его с "level".
=> "level"
>> s.palindrome?         # Слова имеют palindrome? метод.
=> true
>> s.length              # Слова также наследуют все нормальные методы строк.
=> 5

Так как Word класс наследует от String , мы можем использовать консоль, чтобы увидеть иерархию классов в явном виде:

>> s.class
=> Word
>> s.class.superclass
=> String
>> s.class.superclass.superclass
=> Object

Эта иерархия показана на рис 4.2.

(рис 4.2) Иерархия наследования (не встроенного) Word класса из Листинга 4.8.

В Листинге 4.8, обратите внимание, что проверка того, что слово является палиндромом включает в себя вызов слова внутри Word класса. Ruby позволяет нам сделать это, используя ключевое слово self: в Word классе, self является самим объектом, что означает, что мы можем использовать

self == self.reverse

чтобы проверить, является ли слово палиндромом.Для того чтобы узнать больше о классах Ruby и ключевом слове self, см. RailsTips пост "Class and Instance Variables in Ruby".

Изменение встроенных классов

Хотя наследование это мощная идея, в случае палиндромов может быть даже более естественно добавить palindrome? метод самому классу String, так чтобы (среди прочего) мы могли вызвать palindrome? на буквальную строку, что мы в настоящее время не можем сделать:

>> "level".palindrome?
NoMethodError: undefined method `palindrome?' for "level":String

Несколько удивительно, что Ruby позволяет сделать это; Ruby классы могут быть открыты и изменены простыми смертными, такими как мы, самостоятельно добавляющими методы к ним:Для знакомых с JavaScript, эта функциональность сопоставима с использованием встроенного class prototype object для расширения класса. (Спасибо читателю Erik Eldridge за указание на это.)

>> class String
>>   # Возвращает true если строка соответствует своему перевертышу.
>>   def palindrome?
>>     self == self.reverse
>>   end
>> end
=> nil
>> "deified".palindrome?
=> true

(Я и не знаю что круче: то, что Ruby позволяет добавлять методы во встроенные классы или то, что слово "deified" является палиндромом.)

Изменение встроенных классов является мощной техникой, но с большой властью приходит большая ответственность и считается дурным тоном добавлять методы к встроенным классам, не имея действительно хорошей причины для этого. Rails имеет несколько хороших причин (чтобы не делать этого); например, в веб-приложениях вы часто не хотите, чтобы переменные были пустыми; например, имя пользователя не должно быть пустым или состоять из одних пробелов—и Rails добавляет blank? метод к Ruby. Rails консоль автоматически включает Rails расширения, и мы можем увидеть это, например, здесь (это не будет работать в простом irb (Интерактивном Руби)):

>> "".blank?
=> true
>> "      ".empty?
=> false
>> "      ".blank?
=> true
>> nil.blank?
=> true

Мы видим, что строка пробелов не является пустой, но она чистая (blank). Отметим также, что nil является чистым, так как nil не является строкой, это намек, на то, что Rails фактически добавляет blank? к базовому классу String, которым (как мы видели в начале этого раздела) является сам Object. Мы увидим некоторые другие примеры Rails дополнений в Ruby классы в Разделе 8.2.1.

Класс контроллер

Все эти разговоры о классах и наследованиях, возможно, вызвали вспышку узнавания, потому что мы видели их и раньше, в контроллере StaticPages (Листинг 3.16):

class StaticPagesController < ApplicationController

  def home
  end

  def help
  end

  def about
  end
end

Теперь вы в состоянии оценить, по крайней мере смутно, что этот код означает: Вы не должны знать, что делает каждый класс в этой иерархии. Я не знаю, что они все делают и я программирую на Ruby on Rails с 2005. Это означает или что или (a) я чрезвычайно некомпетентен или (b) можно быть квалифицированным Rails разработчиком не зная всех его внутренностей. Для нашей с вами пользы я уповаю на последнее.

>> controller = StaticPagesController.new
=> #<StaticPagesController:0x22855d0>
>> controller.class
=> StaticPagesController
>> controller.class.superclass
=> ApplicationController
>> controller.class.superclass.superclass
=> ActionController::Base
>> controller.class.superclass.superclass.superclass
=> ActionController::Metal
>> controller.class.superclass.superclass.superclass.superclass
=> AbstractController::Base
>> controller.class.superclass.superclass.superclass.superclass.superclass
=> Object

Диаграмма этой иерархии представлена на рис 4.3.

(рис 4.3) Иерархия наследования для контроллера StaticPages.

Мы можем даже вызвать действия контроллера в консоли, которые являются просто методами:

>> controller.home
=> nil

Здесь возвращаемое значение это nil поскольку home действие пустое.

Но постойте: действия не имеют возвращаемых значений, по крайней мере не действия со знаком вопроса (#булевые). Целью home действия, как мы видели в Главе 3, является визуализация веб-страницы. И я бы точно не забыл, если бы когда-либо вызывал StaticPagesController.new где-нибудь. Что происходит?

Происходит вот что: Rails написан на Ruby, но Rails это не Ruby. Некоторые Rails классы используются как обычные объекты Ruby, но некоторые из них просто льют воду на Рельсовую "волшебную мельницу". Rails это sui generis и должен быть изучен и понят отдельно от Ruby. Именно поэтому, если ваш основной интерес программирования заключается в написании веб-приложений, я рекомендую изучать вначале Rails, затем изучать Ruby, затем вернуться обратно на рельсы.

Класс User

Мы закончим наше путешествие по Ruby комплектацией собственного класса, User класса, который ожидает User модель, которая появится в Главе 6.

До сих пор мы вводили определения классов на консоли, но это быстро стало утомительным, вместо этого создайте файл example_user.rb в корневом каталоге приложения и заполните его содержимым Листинга 4.9.

class User
  attr_accessor :name, :email

  def initialize(attributes = {})
    @name  = attributes[:name]
    @email = attributes[:email]
  end

  def formatted_email
    "#{@name} <#{@email}>"
  end
end

Здесь довольно много чего происходит, так что давайте разбираться шаг за шагом. Первая строка,

 attr_accessor :name, :email

создает атрибуты доступа соответствующие имени пользователя и адресу электронной почты. Это создает "получатель" ("getter") и "назначатель" ("setter") методы, которые позволяют нам получать (get) и назначать (set) @name и @email переменные экземпляра, которые мы вкратце упоминали в Разделе 2.2.2. В Rails, принципиальная важность переменных экземпляра заключается в том что они автоматически доступны в представлениях, но в общем случае они используются для переменных которые должны быть доступны в Ruby классе повсеместно. (Мы вскоре расскажем об этом более подробно.) Переменные экземпляра всегда начинаются со знака @, и являются nil если они не определены.

Первый метод, initialize, специальный в Ruby: этот метод вызывается, когда мы выполняем User.new. Конкретно этот initialize принимает один аргумент, attributes:

  def initialize(attributes = {})
    @name  = attributes[:name]
    @email = attributes[:email]
  end

Здесь attributes переменная имеет значение по умолчанию равное пустому хэшу, так что мы можем определить пользователя без имени или адреса электронной почты (напомним, из Раздела 4.3.3 , что хэши возвращают nil на несуществующие ключи, поэтому attributes[:name] будет nil, если нет :name ключа, и так же для attributes[:email]).

Наконец, наш класс определяет метод, называемый formatted_email который использует значения присваиваемые @name и @email переменным для создания отформатированной версии адреса электронной почты пользователя, с помощью интерполяции строки (Раздел 4.2.2):

  def formatted_email
    "#{@name} <#{@email}>"
  end

Переменные @name и @email автоматически доступны в методе formatted_email из-за того что они являются переменными экземпляра (на что указывает знак @).

Давайте запустим консоль, и запросим (require) код примера пользователя, и покрутим наш User класс.

>> require './example_user'           # Так вы загружаете код example_user.
=> ["User"]
>> example = User.new
=> #<User:0x224ceec @email=nil, @name=nil>
>> example.name                   # nil поскольку attributes[:name] является nil
=> nil
>> example.name = "Example User"      # Назначение не-nil имени
=> "Example User"
>> example.email = "user@example.com" # и не-nil email адреса
=> "user@example.com"
>> example.formatted_email
=> "Example User <user@example.com>"

Здесь ’.’ — Unix обозначение для "текущего каталога" и ’./example_user’ говорит Ruby искать файл примера пользователя относительно этого расположения. Последующий код создает пустого пользователя в качестве примера и затем заполняет имя и адрес электронной почты, присвоением непосредственно к соответствующим атрибутам (присвоение стало возможными благодаря attr_accessor строке в Листинге 4.9). Когда мы пишем

example.name = "Example User"

Ruby создает @name переменную для "Example User" (и аналогично дляemail атрибута), которую мы затем используем в formatted_email методе.

Ссылаясь на Раздел 4.3.4 мы можем опустить фигурные скобки для последнего хеш аргумента, мы можем создать другого пользователя, передавая хэш initialize методу для создания пользователя с заранее определенными атрибутами:

>> user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
=> #<User:0x225167c @email="mhartl@example.com", @name="Michael Hartl">
>> user.formatted_email
=> "Michael Hartl <mhartl@example.com>"

Мы увидим, начиная с Главы 7 что инициализация объектов с использованием хэш аргумента является общепринятой в Rails приложениях.

Заключение

На этом мы завершаем обзор языка Ruby. В Главе 5 мы начнем применять полученные знания при разработке примера приложения.

Файл example_user.rb из Раздела 4.4.5 нам больше не пригодится, поэтому я рекомендую удалить его:

$ rm example_user.rb

Затем зафиксируйте остальные изменения в основном репозитории проекта:

$ git add .
$ git commit -m "Add a full_title helper"

Упражнения

  • Заменяя знаки вопроса в Листинге 4.10 на соответствующие методы, скомбинируйте split, shuffle и join для того чтобы написать функцию которая тасует буквы в данной строке.
  • Используя Листинг 4.11 как руководство, добавить shuffle метод к String классу.
  • Создайте три хэша, назовите их person1, person2, и person3, С именем (first name) и фамилией (last name) под ключами :first и :last. Затем создайте params хэш с тем, чтобы params[:father] являлся person1, params[:mother] являлся person2, и params[:child] являлся person3 (father, mother, child это отец, мать, и ребенок соответственно). Убедитесь, что, например, params[:father][:first] имеет правильное значение.
  • Найдите онлайн версию Ruby API и почитайте о Hash методе merge.
  • Пройдите http://rubykoans.com/http://rubykoans.com/ для того чтобы достичь просветления в Ruby.
  • >> def string_shuffle(s)
    >>   s.split('').?.?
    >> end
    => nil
    >> string_shuffle("foobar")
    >> class String
    >>   def shuffle
    >>     self.split('').?.?
    >>   end
    >> end
    => nil
    >> "foobar".shuffle
    Страницы:

    Основанная на примерах из Главы 3, эта глава рассматривает некоторые элементы Ruby важные для Rails. Ruby многогранен, но, к счастью, нам нужна относительно малая его часть, чтобы быть продуктивным Rails-разработчиком. Более того, эта малая часть отличается от той, которая вам понадобится в Ruby для обычных задач, поэтому, если вашей целью является создание динамических веб-приложений, я рекомендую изучать Rails первым, собирая биты Ruby на этом пути. Чтобы стать экспертом Rails, вы должны понимать Ruby более глубоко, и эта книга дает вам хорошую основу для развития. Как отмечалось в Разделе 1.1.1, после окончания Rails Tutorial я советую почитать книги о чистом Ruby, такие как Beginning Ruby, The Well-Grounded Rubyist, or The Ruby Way.

    В этой главе рассматривается много материала, и это нормально — не понять его весь с первого раза. Я буду часто возвращаться к нему в последующих главах.

    Причины

    Как мы видели в предыдущей главе, можно развить скелет приложения Rails, и даже начать тестирование, практически без знания основ языка Ruby. Мы сделали это, опираясь на код тестов предоставленный учебником, разбирая каждое сообщение об ошибке до тех пор пока не получили прохождение набора тестов. Тем не менее, такая ситуация не может длиться вечно, и мы откроем эту главу парой дополнений к сайту, которые поставят нас лицом к лицу с нашим ограниченным знанием Ruby.

    Когда мы в последний раз видели наше новое приложение, мы только что обновили наши, в основном статические, страницы использовав Rails шаблон для устранения дублирования в наших представлениях, как показано в Листинге 4.1 (который является слегка переформатированной версией Листинга 3.26).

    <!DOCTYPE html>
    <html>
      <head>
        <title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
        <%= stylesheet_link_tag "application", media: "all",
                                               "data-turbolinks-track" => true %>
        <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
        <%= csrf_meta_tags %>
      </head>
      <body>
        <%= yield %>
      </body>
    </html>

    Давайте сфокусируемся на одной строке в Листинге 4.1:

    <%= stylesheet_link_tag "application", media: "all",
                                           "data-turbolinks-track" => true %>

    Этот код использует встроенную Rails функцию stylesheet_link_tag (о которой вы можете узнать более подробно из Rails API) для включения application.css для всех медиа типов (включая мониторы компьютеров и принтеры). Для опытного Rails разработчика эта строка выглядит просто, но в ней есть по крайней мере четыре Руби идеи которые могут сбить с толку: встроенные Rails-методы, вызов метода без скобок, символы и хэши. Мы раскроем все эти идеи в этой главе.

    Помимо того, что Rails поставляются с огромным количеством встроенных функций для использования в представлениях, они также позволяют создавать новые. Такие функции называются хелперы; для того, чтобы посмотреть как создается собственный хелпер, давайте для начала рассмотрим строку с тайтлом из Листинг 4.1:

    Ruby on Rails Tutorial Sample App | <%= yield(:title) %>

    Этот код опирается на определение заголовка страницы (используя provide) в каждом представлении:

    <% provide(:title, 'Home') %>
    <h1>Sample App</h1>
    <p>
      This is the home page for the
      <a href="http://railstutorial.org/">Ruby on Rails Tutorial</a>
      sample application.
    </p>

    Но что если мы не предоставим заголовок? Это хорошее соглашение - иметь базовый заголовок, который мы используем на каждой странице, с дополнительным переменным заголовком (тайтлом), если мы хотим быть более конкретными. Мы уже почти достигли этого с нашей текущей схемой, с одним маленьким недостатком: как вы можете видеть, если вы удалите вызов provide в одном из представлений, без специфичного для страницы заголовка полный заголовок будет выглядеть так:

    Ruby on Rails Tutorial Sample App |

    Другими словами, есть подходящий базовый заголовок, но есть также прицепленная вертикальная черта | в конце заголовка.

    Для решения проблемы отсутствующего заголовка страницы, мы определим кастомный хелпер с названием Если хелпер специфичен для конкретного контроллера, вам следует помещать его в соответствующий файл; например, хелперы для контроллера StaticPages обычно хранятся в app/helpers/static_pages_helper.rb. В нашем случае, мы ожидаем что хелпер full_title будет использоваться на всех страницах сайта и у Rails есть специальный файл хелпера для таких случаев: app/helpers/application_helper.rb

    module ApplicationHelper
    
      # Returns the full title on a per-page basis.
      def full_title(page_title)
        base_title = "Ruby on Rails Tutorial Sample App"
        if page_title.empty?
          base_title
        else
          "#{base_title} | #{page_title}"
        end
      end
    end

    Теперь, когда у нас есть хелпер, мы можем упростить наш шаблон заменив

    <title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>

    на

    <title><%= full_title(yield(:title)) %></title>

    как видно в Листинге 4.3.

    <!DOCTYPE html>
    <html>
      <head>
        <title><%= full_title(yield(:title)) %></title>
        <%= stylesheet_link_tag "application", media: "all",
                                               "data-turbolinks-track" => true %>
        <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
        <%= csrf_meta_tags %>
      </head>
      <body>
        <%= yield %>
      </body>
    </html>

    Для того чтобы заставить наш хелпер работать, мы можем удалить ненужное слово "Home" из Home страницы, позволив ей вернуться к базовому заголовку. Мы сделаем это вначале обновив наш тест с кодом из Листинга 4.4, который обновляет предыдущий тест тайтла и добавляет один тест на отсутствие кастомной строки ’Home’ в заголовке. (Примечание: Если вы выполнили упражнение соотоветствующее Листингу 3.31, вам следует сохранить выражение let определяющее base_title в первом блоке describe.)

    require 'spec_helper'
    
    describe "Static pages" do
    
      describe "Home page" do
    
        it "should have the content 'Sample App'" do
          visit '/static_pages/home'
          expect(page).to have_content('Sample App')
        end
    
        it "should have the base title" do
          visit '/static_pages/home'
          expect(page).to have_title("Ruby on Rails Tutorial Sample App")
        end
    
        it "should not have a custom page title" do
          visit '/static_pages/home'
          expect(page).not_to have_title('| Home')
        end
      end
      .
      .
      .
    end

    Посмотрим, сможете ли вы понять почему мы добавили новый тест вместо того чтобы просто преобразовать существующий. (Подсказка: ответ кроется в Разделе 3.3.1.)

    Давайте запустим набор тестов чтобы проверить что один тест провальный:

    $ bundle exec rspec spec/requests/static_pages_spec.rb

    Чтобы получить прохождение набора тестов мы удаляем строку provide из представления страницы как это показано в Листинге 4.5.

    <h1>Sample App</h1>
    <p>
      This is the home page for the
      <a href="http://railstutorial.org/">Ruby on Rails Tutorial</a>
      sample application.
    </p>

    В этой точке тесты должны пройти:

    $ bundle exec rspec spec/requests/static_pages_spec.rb

    Как и со строкой для включения стилей приложения, код в Листинге 4.2 может выглядеть просто для глаз опытного Rails разработчика, но он полон новых Руби идей которые могут сбить с толку: модули, комментарии, назначение локальных переменных, булевые, контроль потока, интерполяция строк и возвращение значений. Все эти идеи тоже будут раскрыты в этой главе.

    Строки и методы

    Нашим основным инструментом для изучения Ruby будет Rails консоль, которая является утилитой командной строки для работы с Rails приложениями, мы впервые видели ее в Разделе 2.3.3. Сама консоль построена на интерактивном Ruby (irb), и таким образом, имеет доступ ко всей мощности языка Ruby. (Как мы увидим в Разделе 4.4.4, консоль также имеет доступ и к Rails окружению.) Запустите консоль в командной строке следующим образом:

    $ rails console
    Loading development environment
    >>

    По умолчанию консоль запускается в окружении разработки (development environment), которое является одним из трех отдельных окружений определенных в Rails (другие — тестирование и производство (test и production)). Это различие не будет иметь важного значения в этой главе, мы узнаем больше об окружениях в Разделе 7.1.1.

    Консоль это замечательный инструмент обучения, и вы можете чувствовать себя свободно при ее использовании — не волнуйтесь, вы (вероятно) ничего не сломаете. При использовании консоли, нажмите Ctrl-C, если вы застряли, или Ctrl-D для выхода из консоли в целом. На протяжении оставшейся части этой главы, вы, возможно, найдете полезным консультироваться с Ruby API. Она упакована (возможно, даже слишком упакована) информацией, например, чтобы узнать больше о строках Ruby вы можете посмотреть в Ruby API вступление для String класса.

    Комментарии

    Ruby комментарии начинаются со знака фунт # (также называемого"знаком хэша" или, более поэтично, "октоторпом") и распространяются до конца строки. Ruby (и, следовательно, Rails) игнорирует комментарии, но они полезны для читателей (а зачастую и для самого автора!). В коде

     # Returns the full title on a per-page basis.
      def full_title(page_title)
        .
        .
        .
      end

    первая строка является комментарием с указанием цели последующего определения функции.

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

    $ rails console
    >> 17 + 42   # Сложение целых чисел
    => 59

    Если вы, двигаясь по этому разделу, будете набирать или копипастить команды в вашу консоль, вы можете, конечно, опустить комментарии, если хотите; консоль будет игнорировать их в любом случае.

    Строки

    Строки это, вероятно, наиболее важная структура данных для веб-приложений, так как веб-страницы, в конечном счете, состоят из строк символов отправленных с сервера на браузер. Давайте начнем изучение строк с консолью, в этот раз запустив ее командой rails c, что является сокращением для rails console:

    $ rails c
    >> ""         # Пустая строка
    => ""
    >> "foo"      # Не пустая строка
    => "foo"

    Это string literals (буквальная (литеральная) строка) (также, забавно называемая текстовая строка), созданная с использованием двойной кавычки ". Консоль печатает результат вычисления каждой строки, который, в случае буквальной (литеральной) строки, и есть сама строка.

    Мы можем также объединить строки + оператором:

    >> "foo" + "bar"    # Конкатенация строк
    => "foobar"

    Здесь результатом оценки выражения Для того чтобы узнать больше о происхождении "foo" и "bar"—и, в частности, возможной не-связанности "foobar" с "FUBAR"—см. Jargon File entry on "foo".

    Другой способ создания строк — через интерполяцию с помощью специального синтаксиса Программисты, знакомые с Perl или PHP могут сравнить это с автоматической интерполяцией переменных со знаком доллара в выражениях вроде "foo $bar"

    >> first_name = "Michael"    # Присвоение переменной
    => "Michael"
    >> "#{first_name} Hartl"     # Интерполяция строки
    => "Michael Hartl"

    Здесь мы присвоили значение "Michael" переменной first_name а затем интерполировали ее в строку "#{first_name} Hartl". Мы также можем присвоить имя обеим строкам:

    >> first_name = "Michael"
    => "Michael"
    >> last_name = "Hartl"
    => "Hartl"
    >> first_name + " " + last_name    # Конкатенация с пробелом
    => "Michael Hartl"
    >> "#{first_name} #{last_name}"    # Эквивалентная интерполяция
    => "Michael Hartl"

    Отметим, что последние два выражения являются эквивалентными, но я предпочитаю интерполированную версию; добавление одного пробела " " кажется мне немного неуклюжим.

    Вывод на экран

    Для того чтобы вывести на экран (print, напечатать) строку обычно используют Ruby функцию puts (произносится "put ess", от "put string"):

    >> puts "foo"     # вывести на экран строку
    foo
    => nil

    Метод puts работает с побочным эффектом: выражение puts "foo" выводит строку на экран, а затем возвращает буквально ничего: nil это особое обозначение Ruby для "вообще ничего". (В дальнейшем, я буду иногда опускать => nil часть для простоты.)

    Использование puts автоматически добавляет символ новой строки \n к выводу; связанный print метод — нет:

    # выводит на экран строку (тоже что и puts, но без символа новой строки):
    >> print "foo"
    foo=> nil
    >> print "foo\n"  # То же что и puts "foo"
    foo
    => nil

    Строки в одинарных кавычках

    Все примеры до сих пор использовали строки в двойных кавычках, но Ruby также поддерживает строки в одиночных кавычках. Для многих целей оба типа строк практически идентичны:

    >> 'foo'          # Строка в одиночных кавычках
    => "foo"
    >> 'foo' + 'bar'
    => "foobar

    Хотя есть важное отличие; Ruby не будет интерполировать строки в одиночных кавычках:

    >> '#{foo} bar'     # Строки в одиночных кавычках не позволяют делать интерполяцию
    => "\#{foo} bar"

    Обратите внимание, как консоль возвращает значения с использованием строк в двойных кавычках, которые требуют обратной косой черты, чтобы маскировать специальные символы, такие как #.

    Если строки в двойных кавычках могут делать все то же, что и одиночно закавыченные, и могут интерполировать, какой смысл в одиночных кавычках? Они часто бывают полезны, потому что они действительно буквальные, и хранят в точности такие символы, как вы вводите. Например, "обратный слэш" (бэкслэш) — символ специальный в большинстве систем, например, буквальной новой строке \n. Если вы хотите чтобы переменная содержала буквально обратный слэш, в одиночных кавычках это сделать проще:

    >> '\n'       # Буквальная комбинация 'бэкслэш n'
    => "\\n"

    Как и с символом # в нашем предыдущем примере, Ruby необходимо маскировать обратный слэш; посредством дополнительного бэкслэша, внутри строки в двойных кавычках, буквальный бэкслэш представлен двумя бэкслэшами. Для небольшого примера, как этот, это небольшое спасение, но если есть много элементов, которые нужно маскировать, это может реально помочь:

    >> 'Newlines (\n) and tabs (\t) both use the backslash character \.'
    => "Newlines (\\n) and tabs (\\t) both use the backslash character \\."

    Объекты и передача сообщений

    Все в Ruby, включая строки и даже nil, является объектом. Мы увидим технический смысл этого выражения в Разделе 4.4.2, но я не думаю, что кто-нибудь когда-нибудь понял объекты, прочитав определение в книге, вы должны создать свое интуитивное понимание объектов, видя множество примеров.

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

    >> "foobar".length        # Передача сообщения "length" строке
    => 6

    Как правило, сообщения, которые передаются объектам, это методы, которые являются функциями, определенными для этих объектов.4 Строки также реагируют на empty? метод:

    >> "foobar".empty?
    => false
    >> "".empty?
    => true

    Обратите внимание на знак вопроса в конце empty метода. Это конвенция Ruby обозначающая, что возвращаемое значение — boolean (булево, логика): true (истина) или false (ложь). Булевые особенно полезны для управления потоком:

    >> s = "foobar"
    >> if s.empty?
    >>   "The string is empty"
    >> else
    >>   "The string is nonempty"
    >> end
    => "The string is nonempty"

    Булевы также могут быть объединены с помощью ("и"), || ("или"), и ! ("не") операторов:

    >> x = "foo"
    => "foo"
    >> y = ""
    => ""
    >> puts "Both strings are empty" if x.empty?  y.empty?
    => nil
    >> puts "One of the strings is empty" if x.empty? || y.empty?
    "One of the strings is empty"
    => nil
    >> puts "x is not empty" if !x.empty?
    "x is not empty"
    => nil

    Поскольку все в Ruby является объектом, следовательно, nil тоже является объектом, поэтому он тоже может отвечать на методы. Одним из примеров является to_s метод, который может конвертировать практически любой объект в строку:

    >> nil.to_s
    => ""

    Это, конечно, кажется, пустой строкой, что мы можем проверить, цепочкой сообщений, передаваемых к nil:

    >> nil.empty?
    NoMethodError: You have a nil object when you didn't expect it!
    You might have expected an instance of Array.
    The error occurred while evaluating nil.empty?
    >> nil.to_s.empty?      # Сцепление сообщений
    => true

    Здесь мы видим, что nil объект сам по себе не реагирует на empty? метод, но nil.to_s реагирует.

    Вот специальный метод для проверки на nil-ность, о котором вы могли догадаться:

    >> "foo".nil?
    => false
    >> "".nil?
    => false
    >> nil.nil?
    => true

    Код

    puts "x is not empty" if !x.empty?

    также показывает альтернативное использование ключевого слова if Ruby позволяет писать утверждение, которое вычисляется только тогда, когда оператор, следующий за if это истина. Есть дополнительное ключевое слово unless которое работает по той же схеме:

    >> string = "foobar"
    >> puts "The string '#{string}' is nonempty." unless string.empty?
    The string 'foobar' is nonempty.
    => nil

    Стоит отметить, что nil объект уникален, тем, что это единственный объект Ruby, который является ложью в булевом контексте, кроме, непосредственно, false:

    >> if nil
    >>   true
    >> else
    >>   false        # nil является false
    >> end
    => false

    В частности, все другие объекты Ruby являются true, даже 0:

    >> if 0
    >>   true        # 0 (и все остальные кроме nil и самой false) является true
    >> else
    >>   false
    >> end
    => true

    Определение методов

    Консоль позволяет определять методы точно так же, как мы это делали с home действием из Листинга 3.6 или хелпером из Листинга 4.2. (Определение методов в консоли - немного громоздкое мероприятие, и обычно вы будете использовать файл, но это удобно для демонстрационных целей.) Например, давайте определим функцию string_message которая принимает один аргумент и возвращает сообщение в зависимости от того, пустой аргумент или нет:

    >> def string_message(string)
    >>   if string.empty?
    >>     "It's an empty string!"
    >>   else
    >>     "The string is nonempty."
    >>   end
    >> end
    => nil
    >> puts string_message("")
    It's an empty string!
    >> puts string_message("foobar")
    The string is nonempty.

    Обратите внимание, что Ruby функции имеют неявное возвращение (скрытый return) то есть, они возвращают последнее оцененное утверждение — в данном случае, одну из двух строк сообщения, в зависимости от того, пуст или нет аргумент string. Ruby также имеет явный вариант возвращения (явный return); следующие функции эквивалентны приведенным выше:

    >> def string_message(string)
    >>   return "It's an empty string!" if string.empty?
    >>   return "The string is nonempty."
    >> end

    (Внимательный читатель может заметить в этой точке, что второе return здесь фактически ненужно — состояние последнего выражения в функции, т.е. строки "The string is nonempty." будет возвращено независимо от ключевого слова return, но использование return в обоих местах придает ему приятную симметрию.)

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

    >> def string_message(the_function_argument)
    >>   if the_function_argument.empty?
    >>     "It's an empty string!"
    >>   else
    >>     "The string is nonempty."
    >>   end
    >> end
    => nil
    >> puts string_message("")
    It's an empty string!
    >> puts string_message("foobar")
    The string is nonempty.

    Возвращение к title хелперу

    Теперь мы в состоянии понять Ну, все же осталась одна вещь, которую мы не понимаем — как Rails связывает все это вместе: направляет URL к действиям, делает full_title хелпер доступным в представлениях и т.д.. Это интересная тема, и я поощряю вас исследовать ее далее, но точное знание того, как Rails работает, не является необходимым для использования Rails. (Для более глубокого понимания я рекомендую The Rails 4 Way Оби Фернандеса.)

    module ApplicationHelper
    
      # Возвращает полный заголовок зависящий от страницы # Документирующий комментарий
      def full_title(page_title)                          # Определение метода
        base_title = "Ruby on Rails Tutorial Sample App"  # Назначение переменной
        if page_title.empty?                              # Булевый тест
          base_title                                      # Явное возвращение
        else
          "#{base_title} | #{page_title}"                 # Интерполяция строки
        end
      end
    end

    Все эти элементы — определение функции, присваивание переменной, логические тесты, управление потоком, и интерполяция строки — собраны вместе, чтобы сделать компактный вспомогательный метод для использования в шаблоне нашего сайта. Последний элемент это module ApplicationHelper: модули позволяют нам складывать вместе связанные методы, затем они (модули) могут быть подмешаны в Ruby-классы с помощью include. Если вы пишете на обычном Ruby, вы обычно пишете модули и сами их явно включаете, но в случае с хелпер-модулем Rails делает включение за нас. Результатом является то, что full_title метод автомагически доступен во всех наших представлениях.

    Другие структуры данных

    Хотя веб-приложения, в конечном счете это строки, фактически, для изготовления этих строк требуется также использование других структур данных. В этом разделе мы узнаем о некоторых структурах данных Ruby, важных для написания Rails приложений.

    Массивы и диапазоны

    Массив это всего лишь список элементов в определенном порядке. Мы еще не обсуждали массивы в Rails Tutorial, но их понимание дает хорошую основу для понимания хэшей (Раздел 4.3.3) и для аспектов Rails моделирования данных (таких как has_many ассоциации, которые мы видели в Разделе 2.3.3 и больше раскроем в Разделе 10.1.3).

    Мы потратили много времени на понимание строк, и есть естественный способ перейти от строк к массивам, используя split метод:

    >>  "foo bar     baz".split     # Разбиение строки на трех-элементный массив.
    => ["foo", "bar", "baz"]

    Результатом этой операции является массив из трех строк. По умолчанию, split делит строку на массив путем разделения по пробелу, но вы можете разделить практически по чему угодно:

    >> "fooxbarxbazx".split('x')
    => ["foo", "bar", "baz"]

    Как это принято в большинстве языков программирования, Ruby массивы — нулевого сдвига, что означает, что первый элемент массива имеет индекс 0, второй имеет индекс 1, и так далее:

    >> a = [42, 8, 17]
    => [42, 8, 17]
    >> a[0]               # Ruby использует квадратные скобки для доступа к массиву.
    => 42
    >> a[1]
    => 8
    >> a[2]
    => 17
    >> a[-1]              # Индексы могут быть даже отрицательными!
    => 17

    Мы видим здесь что Ruby использует квадратные скобки для доступа к элементам массива. В дополнение к этой скобковой записи, Ruby предлагает синонимы для некоторых часто используемых элементов:Second метод, используемый здесь, не является в настоящий момент частью Ruby непосредственно, а скорее добавляется Rails. Это работает в данном случае, потому что консоль Rails автоматически включает Rails расширения в Ruby

    >> a                  # Просто напоминание о том что такое 'a'
    => [42, 8, 17]
    >> a.first
    => 42
    >> a.second
    => 8
    >> a.last
    => 17
    >> a.last == a[-1]    # Сравнение с помощью ==
    => true

    Последняя строка вводит оператор проверки на равенство ==, который Ruby разделяет со многими другими языками, наряду со связанным != ("не равно"), и т.д.:

    >> x = a.length       # Как и строки, массивы отвечают на метод 'length'.
    => 3
    >> x == 3
    => true
    >> x == 1
    => false
    >> x != 1
    => true
    >> x >= 1
    => true
    >> x < 1
    => false

    В дополнение к length (первая строка в приведенном выше примере), массивы отвечают на множество других методов:

    >> a
    => [42, 8, 17]
    >> a.sort
    => [8, 17, 42]
    >> a.reverse
    => [17, 8, 42]
    >> a.shuffle
    => [17, 42, 8]
    >> a
    => [42, 8, 17]

    обратите внимание на то, что ни один из вышеприведенных методов не меняет саму a. Для того, чтобы изменить массив, используется соответствующие "бэнг" методы (названные так потому что восклицательный знак обычно произносится как "бэнг" в этом контексте):

    >> a
    => [42, 8, 17]
    >> a.sort!
    => [8, 17, 42]
    >> a
    => [8, 17, 42]

    Вы также можете добавлять данные в массивы с помощью push (# отправить, толкнуть) метода или эквивалентного ему оператора, <<:

    >> a.push(6)                  # Отправка 6 в массив
    => [42, 8, 17, 6]
    >> a << 7                     # Отправка 7 в массив
    => [42, 8, 17, 6, 7]
    >> a << "foo" << "bar"        # Сцепление отправляемых в массив данных
    => [42, 8, 17, 6, 7, "foo", "bar"]

    Последний пример показывает что вы можете сцеплять добавления вместе, а также, что, в отличие от массивов во многих других языках, Ruby массивы могут содержать смесь различных типов (в данном случае, целые числа и строки).

    Прежде мы видели что split преобразовывает строку в массив. Мы также можем пойти другим путем с join методом:

    >> a
    => [42, 8, 17, 7, "foo", "bar"]
    >> a.join                       # Объединение без ничего.
    => "428177foobar"
    >> a.join(', ')                 # Объединение через запятую.
    => "42, 8, 17, 7, foo, bar"

    Тесно связаны с массивами диапазоны, которые проще всего понять посредством преобразования их в массивы, с помощью to_a метода:

    >> 0..9
    => 0..9
    >> 0..9.to_a              # Упс, вызвали to_a на 9.
    NoMethodError: undefined method `to_a' for 9:Fixnum
    >> (0..9).to_a            # Использование круглых скобок для вызова to_a на диапазоне.
    => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    Хотя 0..9 и является допустимым диапазоном, второе выражение показывает, что нам нужно добавить скобки для вызова метода на нем.

    Диапазоны полезны для вытаскивания элементов массива:

    >> a = %w[foo bar baz quux]         # Применение %w для создания массива строк.
    => ["foo", "bar", "baz", "quux"]
    >> a[0..2]
    => ["foo", "bar", "baz"]

    Особенно полезным трюком является использование индекса -1 в конце диапазона для выборки каждого элемента от заданной точки до конца массива без необходимости явно использовать длину массива:

    >> a = (0..9).to_a
    => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    >> a[2..(a.length-1)]               # Явное использование длины массива.
    => [2, 3, 4, 5, 6, 7, 8, 9]
    >> a[2..-1]                         # Использование трюка с индексом -1.
    => [2, 3, 4, 5, 6, 7, 8, 9]

    Диапазоны также работают с буквами:

    >> ('a'..'e').to_a
    => ["a", "b", "c", "d", "e"]

    Блоки

    И массивы и диапазоны отвечают на множество методов, которые принимают блоки, которые одновременно являются и самыми мощными и одними из самых непонятных элементов Руби:

    >> (1..5).each { |i| puts 2 * i }
    2
    4
    6
    8
    10
    => 1..5

    Этот код вызывает each метод на диапазоне (1..5) и передает ему блок { |i| puts 2 * i }. Вертикальные линии вокруг имени переменной в |i| являются Ruby синтаксисом для блоковых переменных и это позволяет методу узнать, что делать с блоком; в данном случае диапазонный each метод может обрабатывать блок с одной локальной переменной, которую мы называли i и он просто выполняет блок для каждого значения в диапазоне.

    Фигурные скобки это один из способов обозначить блок, но есть также второй способ:

    >> (1..5).each do |i|
    ?>   puts 2 * i
    >> end
    2
    4
    6
    8
    10
    => 1..5

    Блоки часто могут быть более чем из одной строки. В Rails Tutorial мы будем следовать общей конвенции использования фигурных скобок только для коротких однострочных блоков и использовать do..end синтаксис для длинных однострочных и многострочных блоков:

    >> (1..5).each do |number|
    ?>   puts 2 * number
    >>   puts '--'
    >> end
    2
    --
    4
    --
    6
    --
    8
    --
    10
    --
    => 1..5

    Здесь я использовал number вместо i просто чтобы подчеркнуть, что имя переменной может быть любым.

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

    >> 3.times { puts "Betelgeuse!" }   # 3.times принимает блок без переменных.
    "Betelgeuse!"
    "Betelgeuse!"
    "Betelgeuse!"
    => 3
    >> (1..5).map { |i| i**2 }          # Нотация ** обозначает 'степень'.
    => [1, 4, 9, 16, 25]
    >> %w[a b c]                        # Вспомните что %w создает массив строк.
    => ["a", "b", "c"]
    >> %w[a b c].map { |char| char.upcase }
    => ["A", "B", "C"]
    >> %w[A B C].map { |char| char.downcase }
    => ["a", "b", "c"]

    Как вы можете видеть, map метод возвращает результат применения данного блока для каждого элемента в массиве или диапазоне.

    Кстати, теперь мы в состоянии понять строку Ruby, которую я вбросил в Разделе 1.4.4 для генерации случайных субдоменов:

    ('a'..'z').to_a.shuffle[0..7].join

    Давайте построим ее шаг за шагом:

    >> ('a'..'z').to_a                     # Массив букв в алфавитном порядке
    => ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
    "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
    >> ('a'..'z').to_a.shuffle             # Перемешиваем его.
    => ["c", "g", "l", "k", "h", "z", "s", "i", "n", "d", "y", "u", "t", "j", "q",
    "b", "r", "o", "f", "e", "w", "v", "m", "a", "x", "p"]
    >> ('a'..'z').to_a.shuffle[0..7]       # Вытаскиваем первые восемь элементов.
    => ["f", "w", "i", "a", "h", "p", "c", "x"]
    # Объединяем их вместе чтобы сделать одну строку.
    >> ('a'..'z').to_a.shuffle[0..7].join
    => "mznpybuj"

    Хэши и символы

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

    >> user = {}                          # {} это пустой хэш.
    => {}
    >> user["first_name"] = "Michael"     # Ключ "first_name", значение "Michael"
    => "Michael"
    >> user["last_name"] = "Hartl"        # Ключ "last_name", значение "Hartl"
    => "Hartl"
    >> user["first_name"]                 # Доступ к элементам как в массивах.
    => "Michael"
    >> user                               # Буквальное представление хэша
    => {"last_name"=>"Hartl", "first_name"=>"Michael"}

    Хэши обозначаются фигурными скобками, содержащими пары ключ-значение; фигурные скобки без пары ключ-значение, т. е. {} — это пустой хэш. Важно отметить, что фигурные скобки для хэшей не имеют ничего общего с фигурными скобками для блоков. (Да, это может привести к путанице.) Хотя хэши и напоминают массивы, одним важным отличием является то, что хэши не гарантируют сохранность их элементов в определенном порядке.Ruby 1.9 фактически гарантирует, что хеши сохраняют свои элементы в том же самом порядке в каком они вводились, но было бы неблагоразумно когда-либо рассчитывать на определенный порядок. Если порядок важен, используйте массив.

    Вместо того, чтобы определять хэши поэлементно, используя квадратные скобки, проще использовать их буквальное представление с ключами и значениями разделенными =>, который называют "hashrocket":

    >> user = { "first_name" => "Michael", "last_name" => "Hartl" }
    => {"last_name"=>"Hartl", "first_name"=>"Michael"}

    Здесь я использовал обычную конвенцию Ruby, поместив дополнительные пробелы на двух концах хэша — конвенцию, игнорируемую при выводе на консоль. (Не спрашивайте меня, почему пробелы попали в конвенцию; вероятно, раньше какому-то влиятельному Программисту Ruby понравился внешний вид лишних пробелов, и они застряли в конвенции)

    До сих пор мы использовали строки в качестве хэш-ключей, но в Rails гораздо чаще вместо них используются символы. Символы выглядят как строки, но с префиксом двоеточие, а не в кавычках. Например, :name это символ. Вы можете думать о символах, в основном, как о строках без дополнительного багажа:В результате наличия меньшего количества багажа символы легче сравнить друг с другом; строки должны быть сравнены посимвольно, в то время как символы могут быть сравнены все одним разом. Это делает их идеальными для использования в качестве хеш ключей.

    >> "name".split('')
    => ["n", "a", "m", "e"]
    >> :name.split('')
    NoMethodError: undefined method `split' for :name:Symbol
    >> "foobar".reverse
    => "raboof"
    >> :foobar.reverse
    NoMethodError: undefined method `reverse' for :foobar:Symbol

    Символы это специальный тип данных Ruby, очень мало используемый в других языках, так что они могут показаться странными на первый взгляд, но Rails довольно часто их использует, так что вы быстро к ним привыкнете.

    В терминах символов как хэш-ключей, мы можем определить user хэш следующим образом:

    >> user = { :name => "Michael Hartl", :email => "michael@example.com" }
    => {:name=>"Michael Hartl", :email=>"michael@example.com"}
    >> user[:name]              # Доступ к значению соответствующему :name.
    => "Michael Hartl"
    >> user[:password]          # Доступ к значению неопределенного ключа.
    => nil

    Здесь мы видим из последнего примера, что хэш-значение для неопределенного ключа просто nil.

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

    >> h1 = { :name => "Michael Hartl", :email => "michael@example.com" }
    => {:name=>"Michael Hartl", :email=>"michael@example.com"}
    >> h2 = { name: "Michael Hartl", email: "michael@example.com" }
    => {:name=>"Michael Hartl", :email=>"michael@example.com"}
    >> h1 == h2
    => true

    Второй синтаксис заменяет комбинацию символ/hashrocket на имя за которым следует двоеточие и ключ:

    { name: "Michael Hartl", email: "michael@example.com" }

    Эта конструкция более близка к нотации хэшей используемой в других языках (таких как JavaScript) и наслаждается возрастающей популярностью в Rails сообществе. Поскольку оба варианта синтаксиса все еще часто используются, совершенно необходимо уметь распознавать их. К сожалению это может привести к путанице, поскольку :name является валидным сам по себе (как отдельно взятый символ) но name: сам по себе не имеет значения. Суть в том, что :name => и name: являются фактически одинаковыми только внутри литеральных хэшей, таким образом

    { :name => "Michael Hartl" }

    и

    { name: "Michael Hartl" }

    эквивалентны, но в противном случае вы должны использовать :name (с двоеточием впереди) для обозначения символа.

    Хеш значениями может быть практически все, даже другие хэши, как видно в Листинге 4.6.

    >> params = {} # Определение хэша с именем 'params' (сокращение для 'parameters').
    => {}
    >> params[:user] = { name: "Michael Hartl", email: "mhartl@example.com" }
    => {:name=>"Michael Hartl", :email=>"mhartl@example.com"}
    >> params
    => {:user=>{:name=>"Michael Hartl", :email=>"mhartl@example.com"}}
    >>  params[:user][:email]
    => "mhartl@example.com

    Этот вид хэшей-в-хэшах, или вложенных хэшей, интенсивно используется Рельсами, как мы увидим в Разделе 7.3.

    Также как массивы и диапазоны, хэши реагируют на each метод. Рассмотрим, например, хэш с именем flash с ключами для двух условий :success и :error:

    >> flash = { success: "It worked!", error: "It failed." }
    => {:success=>"It worked!", :error=>"It failed."}
    >> flash.each do |key, value|
    ?>   puts "Key #{key.inspect} has value #{value.inspect}"
    >> end
    Key :success has value "It worked!"
    Key :error has value "It failed."

    Отметим, что в то время как each метод для массивов принимает блок только с одной переменной, each для хэшей принимает два — ключ и значение. Таким образом, each метод для хэшей итерирует по одной хэш паре ключ-значение за раз.

    Последний пример использует полезный inspect метод, который возвращает строку с буквальным представлением объекта на котором он был вызван:

    >> puts (1..5).to_a            # Вывести массив как строку.
    1
    2
    3
    4
    5
    >> puts (1..5).to_a.inspect    # Вывести буквальный массив.
    [1, 2, 3, 4, 5]
    >> puts :name, :name.inspect
    name
    :name
    >> puts "It worked!", "It worked!".inspect
    It worked!
    "It worked!"

    Кстати, использование inspect для печати объекта достаточно обычное явление, для этого даже есть специальное сокращение - p функция:

    >> p :name             # Тоже что и 'puts :name.inspect'
    :name

    Вновь CSS

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

    <%= stylesheet_link_tag "application", media: "all",
                                           "data-turbolinks-track" => true %>

    Теперь мы почти в состоянии это понять. Как уже упоминалось вкратце в Разделе 4.1, Rails определяет специальную функцию для включения таблиц стилей и

    stylesheet_link_tag "application", media: "all",
                                       "data-turbolinks-track" => true

    это вызов этой функции. Но есть несколько вопросов. Во-первых, где скобки? В Ruby, они не являются обязательными; эти два выражения эквивалентны:

    # Круглые скобки необязательны при вызове функции.
    stylesheet_link_tag("application", media: "all",
                                       "data-turbolinks-track" => true)
    stylesheet_link_tag "application", media: "all",
                                       "data-turbolinks-track" => true

    Во-вторых, media аргумент определенно выглядит как хэш, но где фигурные скобки? Когда хэш — последний аргумент в вызове функции, фигурные скобки не являются обязательными; эти два выражения эквивалентны:

    # Фигурные скобки необязательны если хэш является последним аргументом функции.
    stylesheet_link_tag "application", { media: "all",
                                         "data-turbolinks-track" => true }
    stylesheet_link_tag "application", media: "all",
                                       "data-turbolinks-track" => true

    Далее. Почему data-turbolinks-track пара ключ/значение использует старый синтаксис хэшей? Это связано с тем что использование нового синтаксиса для написания

    data-turbolinks-track: true

    приведет к попытке создания символа :data-turbolinks-track, который (оказывается) невалиден из-за дефисов. Это вынуждает нас использовать старый синтаксис приводящий к

    "data-turbolinks-track" => true

    Наконец, почему Ruby корректно интерпретирует строки

    stylesheet_link_tag "application", media: "all",
                                       "data-turbolinks-track" => true

    даже с разрывом строки между конечными элементами? Ответ заключается в том что Ruby не видит разницы между знаками новой строки и прочими пробелами (в данном контексте).Знак новой строки это то что идет в конце строки, тем самым начиная новую строку. В коде он представлен знаком \n.Причиной по которой я разбил код на части является моя привычка удерживать длину строк исходного кода в рамках 80 знаков для разборчивости.Подсчитывание столбцов вручную может свести вас с ума, поэтому многие текстовые редакторы имеют визуальные средства которые могут помочь вам с этим. Например, если вы внимательно посмотрите на Рис. 1.1, то вы увидите маленькую вертикальную черту справа которая помогает удерживать длину строк исходного кода в пределах 80 знаков. (На самом деле она установлена на 78 столбцов, что дает вам небольшой запас на ошибку.) Если вы используете TextMate, вы можете найти эту фичу в View > Wrap Column > 78. В Sublime Text, вы можете использовать View > Ruler > 78 или View > Ruler > 80.

    Таким образом, мы видим теперь, что строка

    stylesheet_link_tag "application", media: "all",
                                       "data-turbolinks-track" => true

    вызывает stylesheet_link_tag функцию с двумя аргументами: строкой, указывающей путь к таблице стилей и хэшем, с двумя элементами, указывающими медиа тип и говорящими Rails о необходимости использовать фичу turbolinks (появилась в Rails 4). (Turbolinks будут более детально описаны в следующем издании учебника.) Из-за <%= %> скобок, результаты вставляются в шаблон ERb-ом и если вы посмотрите исходный код страницы в браузере, вы должны увидеть HTML, необходимый для включении таблиц стилей (Листинг 4.7). (Вы можете увидеть некоторые дополнительные вещи вроде ?body=1, после названий файлов CSS. Они вставляются Rails для того чтобы браузеры перегружали CSS после их изменения на сервере.)

    <link data-turbolinks-track="true" href="/assets/application.css" media="all"
    rel="stylesheet" />

    Если вы посмотрите на сам CSS файл перейдя на http://localhost:3000/assets/application.css, вы увидите что он (за исключением нескольких комментариев) пуст. Мы это изменим в Главе 5.

    Ruby классы

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

    Конструкторы

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

    >> s = "foobar"   # Буквальный конструктор для строк использующий двойные кавычки
    => "foobar"
    >> s.class
    => String

    Мы видим здесь, что строки реагируют на метод class, и просто возвращают класс к которому они принадлежат

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

    >> s = String.new("foobar")   # Именованный конструктор для строки
    => "foobar"
    >> s.class
    => String
    >> s == "foobar"
    => true

    Это эквивалентно буквальному конструктору, но это более четко говорит о том, что мы делаем.

    Массивы в этом контексте работают так же как строки:

    >> a = Array.new([1, 3, 2])
    => [1, 3, 2]

    Хэши работают по-другому. Если конструктор массива Array.new принимает начальное значение для массива, Hash.new принимает дефолтное значение для хэша, которое является значением хэша с несуществующим ключом:

    >> h = Hash.new
    => {}
    >> h[:foo]          # Попытка обратиться к значению несуществующего ключа :foo.
    => nil
    >> h = Hash.new(0)  # Теперь несуществующие ключи будут возвращать 0 вместо nil.
    => {}
    >> h[:foo]
    => 0

    Когда метод вызывается на самом классе, как это происходит в случае с new, он называется методом класса. Результатом вызова new на классе является объект этого класса, также называемый экземпляром класса. Метод вызываемый на экземпляре, такой как length, называется методом экземпляра.

    Наследование классов

    При изучении классов, полезно выяснять иерархию классов, используя superclass метод:

    >> s = String.new("foobar")
    => "foobar"
    >> s.class                        # Поиск класса к которому принадлежит s.
    => String
    >> s.class.superclass             # Поиск суперкласса String.
    => Object
    # Ruby 1.9 использует новый базовый класс BasicObject:
    >> s.class.superclass.superclass
    => BasicObject
    >> s.class.superclass.superclass.superclass
    => nil

    Диаграмма этой иерархии наследования представлена на рис 4.1. Мы видим здесь, что суперклассом String является Object , а суперкласс Object это BasicObject, но BasicObject не имеет суперкласса. Эта модель относится к каждому объекту Ruby: можно проследить иерархию классов достаточно далеко, и каждый класс в Ruby в конечном счете наследуется от BasicObject, который не имеет своего суперкласса. Это техническое значение выражения "все в Ruby является объектом".

    (рис 4.1) Иерархия наследования String класса.

    Создание собственного класса незаменимо для более глубокого понимания классов. Давайте сделаем Word класс с palindrome? методом, который возвращает true, если слово можно читать и справа налево и слева направо, сохраняя смысл:

    >> class Word
    >>   def palindrome?(string)
    >>     string == string.reverse
    >>   end
    >> end
    => nil

    Мы можем использовать его следующим образом:

    >> w = Word.new              # Создание нового Word объекта.
    => #<Word:0x22d0b20>
    >> w.palindrome?("foobar")
    => false
    >> w.palindrome?("level")
    => true

    Если этот пример поражает вас, как немного надуманный, хорошо; так и было задумано. Довольно странно создавать новый класс только для того чтобы создать метод принимающий строку в качестве аргумента. Поскольку слово это строка, более естественным решением будет унаследовать наш Word класс от String, как это показано в Листинге 4.8. (Вы должны выйти из консоли и ввести его заново, чтобы убрать старое определение Word.)

    >> class Word < String             # Word наследует от String.
    >>   # Возвращает true если строка соответствует своему перевертышу.
    >>   def palindrome?
    >>     self == self.reverse        # self это сама строка.
    >>   end
    >> end
    => nil

    Здесь Word < String это Ruby-синтаксис для наследования (кратко обсуждается в Разделе 3.1), который гарантирует, что, в дополнение к новому palindrome? методу, words (слова) также имеют все те же методы, что и строки:

    >> s = Word.new("level") # Создать новый объект Word, инициализирует его с "level".
    => "level"
    >> s.palindrome?         # Слова имеют palindrome? метод.
    => true
    >> s.length              # Слова также наследуют все нормальные методы строк.
    => 5

    Так как Word класс наследует от String , мы можем использовать консоль, чтобы увидеть иерархию классов в явном виде:

    >> s.class
    => Word
    >> s.class.superclass
    => String
    >> s.class.superclass.superclass
    => Object

    Эта иерархия показана на рис 4.2.

    (рис 4.2) Иерархия наследования (не встроенного) Word класса из Листинга 4.8.

    В Листинге 4.8, обратите внимание, что проверка того, что слово является палиндромом включает в себя вызов слова внутри Word класса. Ruby позволяет нам сделать это, используя ключевое слово self: в Word классе, self является самим объектом, что означает, что мы можем использовать

    self == self.reverse

    чтобы проверить, является ли слово палиндромом.Для того чтобы узнать больше о классах Ruby и ключевом слове self, см. RailsTips пост "Class and Instance Variables in Ruby".

    Изменение встроенных классов

    Хотя наследование это мощная идея, в случае палиндромов может быть даже более естественно добавить palindrome? метод самому классу String, так чтобы (среди прочего) мы могли вызвать palindrome? на буквальную строку, что мы в настоящее время не можем сделать:

    >> "level".palindrome?
    NoMethodError: undefined method `palindrome?' for "level":String

    Несколько удивительно, что Ruby позволяет сделать это; Ruby классы могут быть открыты и изменены простыми смертными, такими как мы, самостоятельно добавляющими методы к ним:Для знакомых с JavaScript, эта функциональность сопоставима с использованием встроенного class prototype object для расширения класса. (Спасибо читателю Erik Eldridge за указание на это.)

    >> class String
    >>   # Возвращает true если строка соответствует своему перевертышу.
    >>   def palindrome?
    >>     self == self.reverse
    >>   end
    >> end
    => nil
    >> "deified".palindrome?
    => true

    (Я и не знаю что круче: то, что Ruby позволяет добавлять методы во встроенные классы или то, что слово "deified" является палиндромом.)

    Изменение встроенных классов является мощной техникой, но с большой властью приходит большая ответственность и считается дурным тоном добавлять методы к встроенным классам, не имея действительно хорошей причины для этого. Rails имеет несколько хороших причин (чтобы не делать этого); например, в веб-приложениях вы часто не хотите, чтобы переменные были пустыми; например, имя пользователя не должно быть пустым или состоять из одних пробелов—и Rails добавляет blank? метод к Ruby. Rails консоль автоматически включает Rails расширения, и мы можем увидеть это, например, здесь (это не будет работать в простом irb (Интерактивном Руби)):

    >> "".blank?
    => true
    >> "      ".empty?
    => false
    >> "      ".blank?
    => true
    >> nil.blank?
    => true

    Мы видим, что строка пробелов не является пустой, но она чистая (blank). Отметим также, что nil является чистым, так как nil не является строкой, это намек, на то, что Rails фактически добавляет blank? к базовому классу String, которым (как мы видели в начале этого раздела) является сам Object. Мы увидим некоторые другие примеры Rails дополнений в Ruby классы в Разделе 8.2.1.

    Класс контроллер

    Все эти разговоры о классах и наследованиях, возможно, вызвали вспышку узнавания, потому что мы видели их и раньше, в контроллере StaticPages (Листинг 3.16):

    class StaticPagesController < ApplicationController
    
      def home
      end
    
      def help
      end
    
      def about
      end
    end

    Теперь вы в состоянии оценить, по крайней мере смутно, что этот код означает: Вы не должны знать, что делает каждый класс в этой иерархии. Я не знаю, что они все делают и я программирую на Ruby on Rails с 2005. Это означает или что или (a) я чрезвычайно некомпетентен или (b) можно быть квалифицированным Rails разработчиком не зная всех его внутренностей. Для нашей с вами пользы я уповаю на последнее.

    >> controller = StaticPagesController.new
    => #<StaticPagesController:0x22855d0>
    >> controller.class
    => StaticPagesController
    >> controller.class.superclass
    => ApplicationController
    >> controller.class.superclass.superclass
    => ActionController::Base
    >> controller.class.superclass.superclass.superclass
    => ActionController::Metal
    >> controller.class.superclass.superclass.superclass.superclass
    => AbstractController::Base
    >> controller.class.superclass.superclass.superclass.superclass.superclass
    => Object

    Диаграмма этой иерархии представлена на рис 4.3.

    (рис 4.3) Иерархия наследования для контроллера StaticPages.

    Мы можем даже вызвать действия контроллера в консоли, которые являются просто методами:

    >> controller.home
    => nil

    Здесь возвращаемое значение это nil поскольку home действие пустое.

    Но постойте: действия не имеют возвращаемых значений, по крайней мере не действия со знаком вопроса (#булевые). Целью home действия, как мы видели в Главе 3, является визуализация веб-страницы. И я бы точно не забыл, если бы когда-либо вызывал StaticPagesController.new где-нибудь. Что происходит?

    Происходит вот что: Rails написан на Ruby, но Rails это не Ruby. Некоторые Rails классы используются как обычные объекты Ruby, но некоторые из них просто льют воду на Рельсовую "волшебную мельницу". Rails это sui generis и должен быть изучен и понят отдельно от Ruby. Именно поэтому, если ваш основной интерес программирования заключается в написании веб-приложений, я рекомендую изучать вначале Rails, затем изучать Ruby, затем вернуться обратно на рельсы.

    Класс User

    Мы закончим наше путешествие по Ruby комплектацией собственного класса, User класса, который ожидает User модель, которая появится в Главе 6.

    До сих пор мы вводили определения классов на консоли, но это быстро стало утомительным, вместо этого создайте файл example_user.rb в корневом каталоге приложения и заполните его содержимым Листинга 4.9.

    class User
      attr_accessor :name, :email
    
      def initialize(attributes = {})
        @name  = attributes[:name]
        @email = attributes[:email]
      end
    
      def formatted_email
        "#{@name} <#{@email}>"
      end
    end

    Здесь довольно много чего происходит, так что давайте разбираться шаг за шагом. Первая строка,

     attr_accessor :name, :email

    создает атрибуты доступа соответствующие имени пользователя и адресу электронной почты. Это создает "получатель" ("getter") и "назначатель" ("setter") методы, которые позволяют нам получать (get) и назначать (set) @name и @email переменные экземпляра, которые мы вкратце упоминали в Разделе 2.2.2. В Rails, принципиальная важность переменных экземпляра заключается в том что они автоматически доступны в представлениях, но в общем случае они используются для переменных которые должны быть доступны в Ruby классе повсеместно. (Мы вскоре расскажем об этом более подробно.) Переменные экземпляра всегда начинаются со знака @, и являются nil если они не определены.

    Первый метод, initialize, специальный в Ruby: этот метод вызывается, когда мы выполняем User.new. Конкретно этот initialize принимает один аргумент, attributes:

      def initialize(attributes = {})
        @name  = attributes[:name]
        @email = attributes[:email]
      end

    Здесь attributes переменная имеет значение по умолчанию равное пустому хэшу, так что мы можем определить пользователя без имени или адреса электронной почты (напомним, из Раздела 4.3.3 , что хэши возвращают nil на несуществующие ключи, поэтому attributes[:name] будет nil, если нет :name ключа, и так же для attributes[:email]).

    Наконец, наш класс определяет метод, называемый formatted_email который использует значения присваиваемые @name и @email переменным для создания отформатированной версии адреса электронной почты пользователя, с помощью интерполяции строки (Раздел 4.2.2):

      def formatted_email
        "#{@name} <#{@email}>"
      end

    Переменные @name и @email автоматически доступны в методе formatted_email из-за того что они являются переменными экземпляра (на что указывает знак @).

    Давайте запустим консоль, и запросим (require) код примера пользователя, и покрутим наш User класс.

    >> require './example_user'           # Так вы загружаете код example_user.
    => ["User"]
    >> example = User.new
    => #<User:0x224ceec @email=nil, @name=nil>
    >> example.name                   # nil поскольку attributes[:name] является nil
    => nil
    >> example.name = "Example User"      # Назначение не-nil имени
    => "Example User"
    >> example.email = "user@example.com" # и не-nil email адреса
    => "user@example.com"
    >> example.formatted_email
    => "Example User <user@example.com>"

    Здесь ’.’ — Unix обозначение для "текущего каталога" и ’./example_user’ говорит Ruby искать файл примера пользователя относительно этого расположения. Последующий код создает пустого пользователя в качестве примера и затем заполняет имя и адрес электронной почты, присвоением непосредственно к соответствующим атрибутам (присвоение стало возможными благодаря attr_accessor строке в Листинге 4.9). Когда мы пишем

    example.name = "Example User"

    Ruby создает @name переменную для "Example User" (и аналогично дляemail атрибута), которую мы затем используем в formatted_email методе.

    Ссылаясь на Раздел 4.3.4 мы можем опустить фигурные скобки для последнего хеш аргумента, мы можем создать другого пользователя, передавая хэш initialize методу для создания пользователя с заранее определенными атрибутами:

    >> user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
    => #<User:0x225167c @email="mhartl@example.com", @name="Michael Hartl">
    >> user.formatted_email
    => "Michael Hartl <mhartl@example.com>"

    Мы увидим, начиная с Главы 7 что инициализация объектов с использованием хэш аргумента является общепринятой в Rails приложениях.

    Заключение

    На этом мы завершаем обзор языка Ruby. В Главе 5 мы начнем применять полученные знания при разработке примера приложения.

    Файл example_user.rb из Раздела 4.4.5 нам больше не пригодится, поэтому я рекомендую удалить его:

    $ rm example_user.rb

    Затем зафиксируйте остальные изменения в основном репозитории проекта:

    $ git add .
    $ git commit -m "Add a full_title helper"

    Упражнения

  • Заменяя знаки вопроса в Листинге 4.10 на соответствующие методы, скомбинируйте split, shuffle и join для того чтобы написать функцию которая тасует буквы в данной строке.
  • Используя Листинг 4.11 как руководство, добавить shuffle метод к String классу.
  • Создайте три хэша, назовите их person1, person2, и person3, С именем (first name) и фамилией (last name) под ключами :first и :last. Затем создайте params хэш с тем, чтобы params[:father] являлся person1, params[:mother] являлся person2, и params[:child] являлся person3 (father, mother, child это отец, мать, и ребенок соответственно). Убедитесь, что, например, params[:father][:first] имеет правильное значение.
  • Найдите онлайн версию Ruby API и почитайте о Hash методе merge.
  • Пройдите http://rubykoans.com/http://rubykoans.com/ для того чтобы достичь просветления в Ruby.
  • >> def string_shuffle(s)
    >>   s.split('').?.?
    >> end
    => nil
    >> string_shuffle("foobar")
    >> class String
    >>   def shuffle
    >>     self.split('').?.?
    >>   end
    >> end
    => nil
    >> "foobar".shuffle
    Вернуться к учебному плану