Ruby on Rails Tutorial
Изучение Rails на Примерах
Майкл Хартл
Содержание
- Предисловие к русскому изданию
- Глава 1 От нуля к развертыванию
- Глава 2 demo app
- Глава 3 В основном статические страницы
- Глава 4 Rails-приправленный Ruby
- Глава 5 Заполнение шаблона
- Глава 6 Моделирование пользователей
- Chapter 7 Регистрация
- Глава 8 Войти, выйти
- Глава 9 Обновление, демонстрация и удаление пользователей
- Глава 10 Микросообщения пользователей
- Глава 11 Слежение за сообщениями пользователей
Предисловие
Моя компания (CD Baby) была одной из первых громко перешедших на Ruby on Rails, а затем еще громче вернувшейся обратно на PHP (Google расскажет вам об этой драме). Эту книгу, написанную Майклом Хартлом так высоко рекомендовали, что я должен был попробовать её, и Ruby on Rails Tutorial это всё, что я использовал, чтобы вернуться к Rails.
Хотя я уже прошел через много книг по Rails, это одна из немногих, что, наконец, зацепила меня. Было много написано книг типа «Путь Rails» — после которых я чувствовал себя неестественно, но после этой книги я наконец почувствовал себя естественно. Это также единственная книга по Rails, которая соблюдает методику «разработка через тестирование» на всем своем протяжении, этот подход строго рекомендуется специалистами, но он никогда не был так чётко продемонстрирован ранее. Наконец, Git, GitHub и Heroku присутствуют в демо-примерах, автор действительно дает вам почувствовать, что он хотел сделать реальный проект. Учебный код примеров не изолирован.
Линейное повествование — отличный формат. Лично я прошел Rails Tutorial в течении трёх долгих дней, делая все примеры и задачи в конце каждой главы. Делайте всё от начала до конца, не прыгая, и вы получите максимальную пользу.
Наслаждайтесь!
Derek Sivers (sivers.org)
Основатель CD Baby
Благодарности
Ruby On Rails Учебник во многом обязан моей предыдущей книге по Rails, RailsSpace и, следовательно, моему соавтору Aurelius Prochazka. Я хотел бы поблагодарить Aure как за работу, которую он проделал над прошлой книгой, так и за поддержку этой. Я также хотел бы поблагодарить Debra Williams Cauley, редактора обеих книг RailsSpace и Rails Tutorial; до тех пор, пока она не прекратит брать меня на бейсбол, я буду продолжать писать книги для нее.
Я хотел бы поблагодарить огромное количество Рубистов учивших и вдохновлявших меня на протяжении многих лет: David Heinemeier Hansson, Yehuda Katz, Carl Lerche, Jeremy Kemper, Xavier Noria, Ryan Bates, Geoffrey Grosenbach, Peter Cooper, Matt Aimonetti, Gregg Pollack, Wayne E. Seguin, Amy Hoy, Dave Chelimsky, Pat Maddox, Tom Preston-Werner, Chris Wanstrath, Chad Fowler, Josh Susser, Obie Fernandez, Ian McFarland, Steven Bristol, Pratik Naik, Sarah Mei, Sarah Allen, Wolfram Arnold, Alex Chaffee, Giles Bowkett, Evan Dorn, Long Nguyen, James Lindenbaum, Adam Wiggins, Tikhon Bernstam, Ron Evans, Wyatt Greene, Miles Forrest, хороших людей из Pivotal Labs, команду Heroku, thoughtbot ребят, и команду GitHub. Наконец, многих, многих читателей - слишком много чтобы перечислять их здесь - внёсших большое количество предложений по улучшению и сообщивших об ошибках во время написания этой книги, и я с благодарностью признаю их помощь в написании ее настолько хорошей, насколько это было возможно.
Об авторе
Майкл Хартл – автор Ruby on Rails Tutorial, лидирующего введения в веб разработку на Ruby on Rails. Его предыдущий опыт включает в себя написание и разработку RailsSpace - чрезвычайно устаревшего учебника по Rails и разработку Insoshi - некогда популярной, а ныне устаревшей платформы для социальных сетей написанной на Ruby on Rails. В 2011, Майкл получил Ruby Hero Award за его вклад в Ruby сообщество. Он закончил Harvard College, имеет степень Кандидата Физических Наук присвоенную в Caltech и является выпускником предпринимательских курсов Y Combinator.
Копирайт и лицензия
Ruby on Rails Tutorial: Learn Web Development with Rails. Copyright © 2012 by Michael Hartl. Весь исходный код в Ruby on Rails Tutorial доступен под MIT License и Beerware License.
Лицензия MIT
Copyright (c) 2013 Michael Hartl
Данная лицензия разрешает лицам, получившим копию данного программного
обеспечения и сопутствующей документации (в дальнейшем именуемыми
«Программное Обеспечение»), безвозмездно использовать Программное
Обеспечение без ограничений, включая неограниченное право на использование,
копирование, изменение, добавление, публикацию, распространение,
сублицензирование и/или продажу копий Программного Обеспечения, также
как и лицам, которым предоставляется данное Программное Обеспечение,
при соблюдении следующих условий:
Указанное выше уведомление об авторском праве и данные условия должны быть
включены во все копии или значимые части данного Программного Обеспечения.
ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО
ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ
ГАРАНТИЯМИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И
ОТСУТСТВИЯ НАРУШЕНИЙ ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ
НЕСУТ ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ ИЛИ ДРУГИХ
ТРЕБОВАНИЙ ПО ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ ИНОМУ, ВОЗНИКШИМ ИЗ,
ИМЕЮЩИМ ПРИЧИНОЙ ИЛИ СВЯЗАННЫМ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ
ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.
/*
* ----------------------------------------------------------------------------
* "ПИВНАЯ ЛИЦЕНЗИЯ" (Ревизия 42):
* Весь код написан Майклом Хартлом. До тех пор пока вы осознаете это,
* вы можете делать с ним все что захотите. Если мы когда нибудь
* встретимся, и если это того стоило, вы можете купить мне
* пиво в ответ.
* ----------------------------------------------------------------------------
*/
Глава 4 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.
В этой главе рассматривается много материала, и это нормально — не понять его весь с первого раза. Я буду часто возвращаться к нему в последующих главах.
4.1 Причины
Как мы видели в предыдущей главе, можно развить скелет приложения Rails, и даже начать тестирование, практически без знания основ языка Ruby. Мы сделали это, опираясь на код тестов предоставленный учебником, разбирая каждое сообщение об ошибке до тех пор пока не получили прохождение набора тестов. Тем не менее, такая ситуация не может длиться вечно, и мы откроем эту главу парой дополнений к сайту, которые поставят нас лицом к лицу с нашим ограниченным знанием Ruby.
Когда мы в последний раз видели наше новое приложение, мы только что обновили наши, в основном статические, страницы использовав Rails шаблон для устранения дублирования в наших представлениях, как показано в Листинге 4.1 (который является слегка переформатированной версией Листинга 3.26).
app/views/layouts/application.html.erb
<!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="https://railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
Но что если мы не предоставим заголовок? Это хорошее соглашение - иметь базовый заголовок, который мы используем на каждой странице, с дополнительным переменным заголовком (тайтлом), если мы хотим быть более конкретными. Мы уже почти достигли этого с нашей текущей схемой, с одним маленьким недостатком: как вы можете видеть, если вы удалите вызов provide
в одном из представлений, без специфичного для страницы заголовка полный заголовок будет выглядеть так:
Ruby on Rails Tutorial Sample App |
Другими словами, есть подходящий базовый заголовок, но есть также прицепленная вертикальная черта |
в конце заголовка.
Для решения проблемы отсутствующего заголовка страницы, мы определим кастомный хелпер с названием full_title
. Хелпер full_title
будет возвращать базовый заголовок, “Ruby on Rails Tutorial Sample App”, в случае если заголовок страницы не определен и добавлять вертикальную черту перед заголовком страницы в противном случае (Листинг 4.2).1
full_title
. 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.
app/views/layouts/application.html.erb
<!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
.)
spec/requests/static_pages_spec.rb
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.
app/views/static_pages/home.html.erb
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
В этой точке тесты должны пройти:
$ bundle exec rspec spec/requests/static_pages_spec.rb
Как и со строкой для включения стилей приложения, код в Листинге 4.2 может выглядеть просто для глаз опытного Rails разработчика, но он полон новых Руби идей которые могут сбить с толку: модули, комментарии, назначение локальных переменных, булевые, контроль потока, интерполяция строк и возвращение значений. Все эти идеи тоже будут раскрыты в этой главе.
4.2 Строки и методы
Нашим основным инструментом для изучения 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 API. Она упакована (возможно, даже слишком упакована) информацией, например, чтобы узнать больше о строках Ruby вы можете посмотреть в Ruby API вступление для String
класса.
4.2.1 Комментарии
Ruby комментарии начинаются со знака фунт #
(также называемого“знаком хэша” или, более поэтично, “октоторпом”) и распространяются до конца строки. Ruby (и, следовательно, Rails) игнорирует комментарии, но они полезны для читателей (а зачастую и для самого автора!). В коде
# Returns the full title on a per-page basis.
def full_title(page_title)
.
.
.
end
первая строка является комментарием с указанием цели последующего определения функции.
Обычно, вам не нужно включать комментарии в консольные сессии, но в учебных целях, я в дальнейшем буду включать некоторые комментарии, например:
$ rails console
>> 17 + 42 # Сложение целых чисел
=> 59
Если вы, двигаясь по этому разделу, будете набирать или копипастить команды в вашу консоль, вы можете, конечно, опустить комментарии, если хотите; консоль будет игнорировать их в любом случае.
4.2.2 Строки
Строки это, вероятно, наиболее важная структура данных для веб-приложений, так как веб-страницы, в конечном счете, состоят из строк символов отправленных с сервера на браузер. Давайте начнем изучение строк с консолью, в этот раз запустив ее командой rails c
, что является сокращением для rails console
:
$ rails c
>> "" # Пустая строка
=> ""
>> "foo" # Не пустая строка
=> "foo"
Это string literals (буквальная (литеральная) строка) (также, забавно называемая текстовая строка), созданная с использованием двойной кавычки "
. Консоль печатает результат вычисления каждой строки, который, в случае буквальной (литеральной) строки, и есть сама строка.
Мы можем также объединить строки +
оператором:
>> "foo" + "bar" # Конкатенация строк
=> "foobar"
Здесь результатом оценки выражения "foo"
plus "bar"
является строка "foobar"
.2
Другой способ создания строк — через интерполяцию с помощью специального синтаксиса #{}
:3
>> 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 \\."
4.2.3 Объекты и передача сообщений
Все в 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
4.2.4 Определение методов
Консоль позволяет определять методы точно так же, как мы это делали с home
действием из Листинга 3.6 или full_title
хелпером из Листинга 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.
4.2.5 Возвращение к title хелперу
Теперь мы в состоянии понять full_title
хелпер из Листинга 4.2:5
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
метод автомагически доступен во всех наших представлениях.
4.3 Другие структуры данных
Хотя веб-приложения, в конечном счете это строки, фактически, для изготовления этих строк требуется также использование других структур данных. В этом разделе мы узнаем о некоторых структурах данных Ruby, важных для написания Rails приложений.
4.3.1 Массивы и диапазоны
Массив это всего лишь список элементов в определенном порядке. Мы еще не обсуждали массивы в 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 предлагает синонимы для некоторых часто используемых элементов:6
>> 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"]
4.3.2 Блоки
И массивы и диапазоны отвечают на множество методов, которые принимают блоки, которые одновременно являются и самыми мощными и одними из самых непонятных элементов Руби:
>> (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
просто чтобы подчеркнуть, что имя переменной может быть любым.
Если не владеете основами программирования в должной степени, нет короткой дороги к пониманию блоков; просто их нужно много увидеть и, в конечном итоге, вы привыкнете к ним.7 К счастью, люди довольно хороши на обобщения на основе конкретных примеров; вот еще несколько блоков, в том числе пара с использованием 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"
4.3.3 Хэши и символы
Хэши, по существу, это массивы которые не ограничены целочисленными индексами. (В самом деле, некоторые языки, особенно 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"}
Хэши обозначаются фигурными скобками, содержащими пары ключ-значение; фигурные скобки без пары ключ-значение, т. е. {}
— это пустой хэш. Важно отметить, что фигурные скобки для хэшей не имеют ничего общего с фигурными скобками для блоков. (Да, это может привести к путанице.) Хотя хэши и напоминают массивы, одним важным отличием является то, что хэши не гарантируют сохранность их элементов в определенном порядке.8 Если порядок важен, используйте массив.
Вместо того, чтобы определять хэши поэлементно, используя квадратные скобки, проще использовать их буквальное представление с ключами и значениями разделенными =>
, который называют “hashrocket”:
>> user = { "first_name" => "Michael", "last_name" => "Hartl" }
=> {"last_name"=>"Hartl", "first_name"=>"Michael"}
Здесь я использовал обычную конвенцию Ruby, поместив дополнительные пробелы на двух концах хэша — конвенцию, игнорируемую при выводе на консоль. (Не спрашивайте меня, почему пробелы попали в конвенцию; вероятно, раньше какому-то влиятельному Программисту Ruby понравился внешний вид лишних пробелов, и они застряли в конвенции)
До сих пор мы использовали строки в качестве хэш-ключей, но в Rails гораздо чаще вместо них используются символы. Символы выглядят как строки, но с префиксом двоеточие, а не в кавычках. Например, :name
это символ. Вы можете думать о символах, в основном, как о строках без дополнительного багажа:9
>> "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 => "[email protected]" }
=> {:name=>"Michael Hartl", :email=>"[email protected]"}
>> user[:name] # Доступ к значению соответствующему :name.
=> "Michael Hartl"
>> user[:password] # Доступ к значению неопределенного ключа.
=> nil
Здесь мы видим из последнего примера, что хэш-значение для неопределенного ключа просто nil
.
Поскольку использование в хэшах символов в качестве ключей является общепринятой практикой, Ruby 1.9 поддерживает новый синтаксис специально для этого случая:
>> h1 = { :name => "Michael Hartl", :email => "[email protected]" }
=> {:name=>"Michael Hartl", :email=>"[email protected]"}
>> h2 = { name: "Michael Hartl", email: "[email protected]" }
=> {:name=>"Michael Hartl", :email=>"[email protected]"}
>> h1 == h2
=> true
Второй синтаксис заменяет комбинацию символ/hashrocket на имя за которым следует двоеточие и ключ:
{ name: "Michael Hartl", email: "[email protected]" }
Эта конструкция более близка к нотации хэшей используемой в других языках (таких как JavaScript) и наслаждается возрастающей популярностью в Rails сообществе. Поскольку оба варианта синтаксиса все еще часто используются, совершенно необходимо уметь распознавать их. К сожалению это может привести к путанице, поскольку :name
является валидным сам по себе (как отдельно взятый символ) но name:
сам по себе не имеет значения. Суть в том, что :name =>
и name:
являются фактически одинаковыми только внутри литеральных хэшей, таким образом
{ :name => "Michael Hartl" }
и
{ name: "Michael Hartl" }
эквивалентны, но в противном случае вы должны использовать :name
(с двоеточием впереди) для обозначения символа.
Хеш значениями может быть практически все, даже другие хэши, как видно в Листинге 4.6.
>> params = {} # Определение хэша с именем 'params' (сокращение для 'parameters').
=> {}
>> params[:user] = { name: "Michael Hartl", email: "[email protected]" }
=> {:name=>"Michael Hartl", :email=>"[email protected]"}
>> params
=> {:user=>{:name=>"Michael Hartl", :email=>"[email protected]"}}
>> params[:user][:email]
=> "[email protected]"
Этот вид хэшей-в-хэшах, или вложенных хэшей, интенсивно используется Рельсами, как мы увидим в Разделе 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
4.3.4 Вновь 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 не видет разницы между знаками новой строки и прочими пробелами (в данном контексте).10 Причиной по которой я разбил код на части является моя привычка удерживать длину строк исходного кода в рамках 80 знаков для разборчивости.11
Таким образом, мы видим теперь, что строка
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 файл перейдя на //localhost:3000/assets/application.css, вы увидите что он (за исключением нескольких коментариев) пуст. Мы это изменим в Главе 5.
4.4 Ruby классы
Мы говорили, что все в Ruby является объектами, и в этом разделе мы, наконец, определим несколько собственных классов. Ruby, как и многие другие объектно-ориентированные языки, использует классы чтобы организовать методы; эти классы, затем экземплируются для создания объектов. Если вы новичок в объектно-ориентированном программировании, это может звучать как бред, так что давайте рассмотрим несколько конкретных примеров.
4.4.1 Конструкторы
Мы видели много примеров использования классов для создания экземпляра объекта, но нам еще предстоит сделать это в явном виде. Например, мы экземплировали строку с помощью двойных кавычек, которые являются буквальным конструктором для строк:
>> s = "foobar" # Буквальный конструктор для строк использующий двойные кавычки
=> "foobar"
>> s.class
=> String
Мы видим здесь, что строки реагируют на метод class
, и просто возвращают класс к которому они принадлежат.
Вместо использования буквального конструктора, можно использовать аналогичный именованный конструктор, что подразумевает вызов new
метода на имени класса:12
>> 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
, называется методом экземпляра.
4.4.2 Наследование классов
При изучении классов, полезно выяснять иерархию классов, используя 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 является объектом”.
Создание собственного класса незаменимо для более глубокого понимания классов. Давайте сделаем 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
.)
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.8, обратите внимание, что проверка того, что слово является палиндромом включает в себя вызов слова внутри Word
класса. Ruby позволяет нам сделать это, используя ключевое слово self
: в Word
классе, self
является самим объектом, что означает, что мы можем использовать
self == self.reverse
чтобы проверить, является ли слово палиндромом.13
4.4.3 Изменение встроенных классов
Хотя наследование это мощная идея, в случае палиндромов может быть даже более естественно добавить palindrome?
метод самому классу String
, так чтобы (среди прочего) мы могли вызвать palindrome?
на буквальную строку, что мы в настоящее время не можем сделать:
>> "level".palindrome?
NoMethodError: undefined method `palindrome?' for "level":String
Несколько удивительно, что Ruby позволяет сделать это; Ruby классы могут быть открыты и изменены простыми смертными, такими как мы, самостоятельно добавляющими методы к ним:14
>> 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.
4.4.4 Класс контроллер
Все эти разговоры о классах и наследованиях, возможно, вызвали вспышку узнавания, потому что мы видели их и раньше, в контроллере StaticPages (Листинг 3.16):
class StaticPagesController < ApplicationController
def home
end
def help
end
def about
end
end
Теперь вы в состоянии оценить, по крайней мере смутно, что этот код означает: StaticPagesController
это класс, который наследует от ApplicationController
и он оснащен home
, help
и about
методами. Так как каждый сеанс Rails консоли загружает локальную среду Rails, мы можем даже создать контроллер в явном виде и изучить иерархию его классов:15
>> 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.
Мы можем даже вызвать действия контроллера в консоли, которые являются просто методами:
>> controller.home
=> nil
Здесь возвращаемое значение это nil
поскольку home
действие пустое.
Но постойте: действия не имеют возвращаемых значений, по крайней мере не действия со знаком вопроса (#булевые). Целью home
действия, как мы видели в Главе 3, является визуализация веб-страницы. И я бы точно не забыл, если бы когда-либо вызывал StaticPagesController.new
где-нибудь. Что происходит?
Происходит вот что: Rails написан на Ruby, но Rails это не Ruby. Некоторые Rails классы используются как обычные объекты Ruby, но некоторые из них просто льют воду на Рельсовую "волшебную мельницу". Rails это sui generis и должен быть изучен и понят отдельно от Ruby. Именно поэтому, если ваш основной интерес программирования заключается в написании веб-приложений, я рекомендую изучать вначале Rails, затем изучать Ruby, затем вернуться обратно на рельсы.
4.4.5 Класс User
Мы закончим наше путешествие по Ruby комплектацией собственного класса, User
класса, который ожидает User модель, которая появится в Главе 6.
До сих пор мы вводили определения классов на консоли, но это быстро стало утомительным, вместо этого создайте файл example_user.rb
в корневом каталоге приложения и заполните его содержимым Листинга 4.9.
example_user.rb
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 = "[email protected]" # и не-nil email адреса
=> "[email protected]"
>> example.formatted_email
=> "Example User <[email protected]>"
Здесь ’.’
— 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: "[email protected]")
=> #<User:0x225167c @email="[email protected]", @name="Michael Hartl">
>> user.formatted_email
=> "Michael Hartl <[email protected]>"
Мы увидим, начиная с Главы 7 что инициализация объектов с использованием хэш аргумента является общепринятой в Rails приложениях.
4.5 Заключение
На этом мы завершаем обзор языка Ruby. В Главе 5 мы начнем применять полученные знания при разработке примера приложения.
Файл example_user.rb
из Раздела 4.4.5 нам больше не пригодится, поэтому я рекомендую удалить его:
$ rm example_user.rb
Затем зафиксируйте остальные изменения в основном репозитории проекта:
$ git add .
$ git commit -m "Add a full_title helper"
4.5 Упражнения
- Заменяя знаки вопроса в Листинге 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
. - Пройдите Ruby Koans16 для того чтобы достичь просветления в Ruby.
>> def string_shuffle(s)
>> s.split('').?.?
>> end
=> nil
>> string_shuffle("foobar")
shuffle
метода, принадлежащего String
классу. >> class String
>> def shuffle
>> self.split('').?.?
>> end
>> end
=> nil
>> "foobar".shuffle
- Если хелпер специфичен для конкретного контроллера, вам следует помещать его в соответствующий файл; например, хелперы для контроллера StaticPages обычно хранятся в
app/helpers/static_pages_helper.rb
. В нашем случае, мы ожидаем что хелперfull_title
будет использоваться на всех страницах сайта и у Rails есть специальный файл хелпера для таких случаев:app/helpers/application_helper.rb
. ↑ - Для того чтобы узнать больше о происхождении “foo” и “bar”—и, в частности, возможной не-связанности “foobar” с “FUBAR”—см. Jargon File entry on “foo”. ↑
- Программисты, знакомые с Perl или PHP могут сравнить это с автоматической интерполяцией переменных со знаком доллара в выражениях вроде
"foo $bar"
. ↑ - Заранее извиняюсь за случайные переключения между функцией и методом на протяжении этой главы; в Ruby они представляют одно и то же: все методы это функции, а все функции это методы, поскольку все является объектом. ↑
- Ну, все же осталась одна вещь, которую мы не понимаем — как Rails связывает все это вместе: направляет URL к действиям, делает
full_title
хелпер доступным в представлениях и т.д.. Это интересная тема, и я поощряю вас исследовать ее далее, но точное знание того, как Rails работает, не является необходимым для использования Rails. (Для более глубокого понимания я рекомендую The Rails 4 Way Оби Фернандеса.) ↑ Second
метод, используемый здесь, не является в настоящий момент частью Ruby непосредственно, а скорее добавляется Rails. Это работает в данном случае, потому что консоль Rails автоматически включает Rails расширения в Ruby. ↑- С другой стороны, искушенные программисты могут извлечь пользу из понимания, что блоки - это замыкания, объединяющие анонимные функции с внешними, по отношению к этим функциям, данными. ↑
- Ruby 1.9 фактически гарантирует, что хеши сохраняют свои элементы в том же самом порядке в каком они вводились, но было бы неблагоразумно когда-либо рассчитывать на определенный порядок. ↑
- В результате наличия меньшего количества багажа символы легче сравнить друг с другом; строки должны быть сравнены посимвольно, в то время как символы могут быть сравнены все одним разом. Это делает их идеальными для использования в качестве хеш ключей. ↑
- Знак новой строки это то что идет в конце строки, тем самым начиная новую строку. В коде он представлен знаком \n. ↑
- Подсчитывание столбцов вручную может свести вас с ума, поэтому многие текстовые редакторы имеют визуальные средства которые могут помочь вам с этим. Например, если вы внимательно посмотрите на Рис. 1.1, то вы увидите маленькую вертикальную черту справа которая помогает удерживать длину строк исходного кода в пределах 80 знаков. (На самом деле она установлена на 78 столбцов, что дает вам небольшой запас на ошибку.) Если вы используете TextMate, вы можете найти эту фичу в View > Wrap Column > 78. В Sublime Text, вы можете использовать View > Ruler > 78 или View > Ruler > 80. ↑
- Эти результаты могут изменятся, в зависимости от версии Ruby, которую вы используете. Этот пример предполагает, что вы используете Ruby 1.9.3. ↑
- Для того чтобы узнать больше о классах Ruby и ключевом слове
self
, см. RailsTips пост “Class and Instance Variables in Ruby”. ↑ - Для знакомых с JavaScript, эта функциональность сопоставима с использованием встроенного class prototype object для расширения класса. (Спасибо читателю Erik Eldridge за указание на это.) ↑
- Вы не должны знать, что делает каждый класс в этой иерархии. Я не знаю, что они все делают и я программирую на Ruby on Rails с 2005. Это означает или что или (a) я чрезвычайно некомпетентен или (b) можно быть квалифицированным Rails разработчиком не зная всех его внутренностей. Для нашей с вами пользы я уповаю на последнее. ↑
- http://rubykoans.com/ ↑