Глава 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, или The Ruby Way.В этой главе рассматриваются много материала, и это нормально — не понять его весь с первого раза. Я буду часто возвращаться к нему в последующих главах.
4.1 Причины
Как мы видели в предыдущей главе, можно развить скелет приложения Rails, и даже начать тестирование, практически без знания основ языка Ruby. Мы сделали это, опираясь на сгенерированный контроллер и код тестов, следуя примерам (кода) которые мы там увидели. Тем не менее, такая ситуация не может длиться вечно, и мы откроем эту главу парой дополнений к сайту, которые поставят нас лицом к лицу с нашим ограниченным знанием Ruby.
4.1.1 Тitle хелпер
Когда мы в последний раз видели наше новое приложение, мы только что
обновили наши, в основном статические, страницы использовав Rails
шаблон для устранения дублирования в наших представлениях (Листинг 4.1).
Листинг 4.1. Шаблон сайта Пример приложения.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Ruby on Rails Tutorial Sample App | <%= @title %></title>
<%= csrf_meta_tag %>
</head>
<body>
<%= yield %>
</body>
</html>
Ruby on Rails Tutorial Sample App | <%= @title %>
@title в действии (actions), такое какclass PagesController < ApplicationController
def home
@title = "Home"
end
.
.
.
@title переменную? Это хорошее соглашение - иметь базовый заголовок,
который мы используем на каждой странице, с дополнительным переменным
заголовком (тайтлом), если мы хотим быть более конкретными. Мы уже почти достигли этого с нашей текущей схемой, с одним маленьким недостатком: как вы можете видеть, если вы удалите назначение @title в одном из действий, в отсутствие @title переменной название будет выглядеть следующим образом:Ruby on Rails Tutorial Sample App |
| в конце заголовка.Одним принятым способом справиться с этим случаем, является определение helper (помощник, хелпер), который является функцией предназначенной для использования в представлениях. Давайте определим
title помощник, который возвращает базовый заголовок, “Ruby on Rails Tutorial Sample App”, если @title переменная не определена и добавляет вертикальную черту перед переменным заголовком, если @title определена (Листинг 4.2).1
Листинг 4.2. Определение
title помощника (хелпера).app/helpers/application_helper.rb
module ApplicationHelper
# Return a title on a per-page basis.
def title
base_title = "Ruby on Rails Tutorial Sample App"
if @title.nil?
base_title
else
"#{base_title} | #{@title}"
end
end
end
Теперь у нас есть помощник (хелпер), и мы можем использовать его для упрощения нашего макета, заменив
<title>Ruby on Rails Tutorial Sample App | <%= @title %></title>
<title><%= title %></title>
@title к методу помощника title (без знака @). Используя Autotest или rspec spec/, вы можете убедиться, что тесты из Главы 3 все еще проходят.
Листинг 4.3. Макет сайта Пример приложения.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<%= csrf_meta_tag %>
</head>
<body>
<%= yield %>
</body>
</html>
4.1.2 Каскадные Таблицы Стилей (CSS)
Это второе дополнение к нашему сайту, оно покажется простым, но добавит несколько новых концепций Ruby: в том числе таблицы стилей в шаблон нашего сайта. Хотя эта книга о веб-разработке, а не о веб-дизайне, мы будем использовать каскадные таблицы стилей (CSS), чтобы придать примеру приложения некоторый минимальный стиль, и мы будем использовать фрэймворк Blueprint CSS в качестве основы для этого стиля.Для начала, скачайте последнюю версию Blueprint CSS. (Для простоты я буду считать, что вы скачали Blueprint в папку
Downloads,
но вы можете использовать любую другую.) Используя командную строку
либо графический инструмент, скопируйте папку Blueprint CSS blueprint в public/stylesheets
- это специальный каталог, в котором Rails держит таблицы стилей. На
моем Mac, команды выглядели так, но ваши команды могут отличаться:$ cp -r ~/Downloads/joshuaclayton-blueprint-css-<version number>/blueprint \
> public/stylesheets/
cp является командой копирования Unix, и -r — флаг рекурсивного копирования (необходимый для копирования директорий). (Как уже упоминалось вкратце в Разделе 3.2.1.1, тильда ~ означает “home directory” в Unix.) Примечание: вы не должны вставлять символ > в терминал. Если вы вставите первую строку с обратным слэшем (“ \”) и нажмете Enter, вы увидите >,
указывающий на продолжение строки. Вам следует вставить вторую строку и
снова нажать Enter, чтобы выполнить команду. Отметим также, что вы
должны будете заполнить номер версии вручную, т. к. он меняется при
обновлении Blueprint. (# я просто скопировал название
папки, включающее и номер версии тоже, и заменил им
«joshuaclayton-blueprint-css-<номер версии>») Наконец, убедитесь, что вы не вводите$ cp -r ~/Downloads/joshuaclayton-blueprint-css-<version number>/blueprint/ \
> public/stylesheets/
…/blueprint/. Это вставит содержимое папки Blueprint в каталог public/stylesheets вместо перемещения всей папки.После того как вы заполучили таблицы стилей в соответствующий каталог, Rails предоставляет помощника для включения их на наших страницах с помощью Embedded (встроенного) Ruby (Листинг 4.4).
Листинг 4.4. Добавление таблиц стилей в макет примера приложения.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<%= csrf_meta_tag %>
<%= stylesheet_link_tag 'blueprint/screen', :media => 'screen' %>
<%= stylesheet_link_tag 'blueprint/print', :media => 'print' %>
</head>
<body>
<%= yield %>
</body>
</html>
<%= stylesheet_link_tag 'blueprint/screen', :media => 'screen' %>
<%= stylesheet_link_tag 'blueprint/print', :media => 'print' %>
stylesheet_link_tag, о котором вы можете прочитать больше на Rails API.2 Первая stylesheet_link_tag строка включает таблицу стилей blueprint/screen.css для экранов (например, компьютерных мониторов), а вторая включает в себя blueprint/print.css для печати. (Помощник автоматически добавляет .css расширение к именам файлов, если оно отсутствует, поэтому я опустил его для краткости). Как и с title
помощником (хелпером), для опытного Rails разработчика эти строки
выглядят просто, но в них есть по крайней мере четыре новых Ruby идеи:
встроенные в Rails методы, вызов метода с отсутствующими скобками,
символы и хэши. В этой главе мы рассмотрим эти новые идеи. (Мы увидим,
HTML произведенный включением этих таблиц стилей в Листинге 4.6 Раздела 4.3.4.)Кстати, с новыми таблицами стилей сайт не сильно изменился, но это только начало (Рис. 4.1). Мы будем строить на этой основе, начиная с Главы 5.3
Рисунок 4.1: Home страница с новыми Blueprint таблицами стилей. (полный размер)
4.2 Строки и методы
Нашим основным инструментом для изучения Ruby будет Rails консоль, которая является утилитой командной строки для работы с Rails приложениями. Сама консоль построена на интерактивном Ruby (irb), и таким образом, имеет доступ ко всей мощности Ruby. (Как мы увидим в Разделе 4.4.4, консоль также имеет доступ к среде Rails.) Запуск консоли в командной строке происходит следующим образом:4$ rails console
Loading development environment (Rails 3.0.9)
>>
Консоль это замечательный инструмент обучения, и вы можете чувствовать себя свободно при ее использовании — не волнуйтесь, вы (вероятно) ничего не сломаете. При использовании консоли, нажмите Ctrl-C, если вы застряли, или Ctrl-D для выхода из консоли в целом.
На протяжении оставшейся части этой главы, вы, возможно, найдете полезным консультироваться с Ruby API.5 Она упакована (возможно, даже слишком упакована) информацией, например, чтобы узнать больше о строках Ruby вы можете посмотреть на Ruby API вступление для
String класса.4.2.1 Комментарии
Ruby комментарии начинаются со знака фунт#
и распространяются до конца строки. Ruby (и, следовательно, Rails)
игнорирует коментарии, но они полезны для читателей (в том числе, часто,
для самого автора!). В коде # Return a title on a per-page basis.
def title
.
.
.
Обычно, вам не нужно включать комментарии в консольные сессии, но в учебных целях, я в дальнейшем буду включать некоторые коментарии, например:
$ rails console
>> 17 + 42 # Integer addition
=> 59
4.2.2 Строки
Строки это, вероятно, наиболее важная структура данных для веб-приложений, так как веб-страницы, в конечном счете, состоят из строк символов отправленных с сервера в браузер. Давайте начнем изучение строк с консолью, в этот раз запустив ее командойrails c, что является сокращением для rails console:$ rails c
>> "" # An empty string
=> ""
>> "foo" # A nonempty string
=> "foo"
". Консоль печатает результат вычисления каждой строки, который, в случае буквальной (литеральной) строки, и есть сама строка.Мы можем также объединить строки
+ оператором:>> "foo" + "bar" # String concatenation
=> "foobar"
"foo" плюс "bar" это строка "foobar".6Другой способ создания строк — через интерполяцию с помощью специального синтаксиса
#{}:7>> first_name = "Michael" # Variable assignment
=> "Michael"
>> "#{first_name} Hartl" # String interpolation
=> "Michael Hartl"
"Michael" переменной first_name а затем интерполировали ее в строку "#{first_name} Hartl". Мы также можем присвоить имя обеим строкам:>> first_name = "Michael"
=> "Michael"
>> last_name = "Hartl"
=> "Hartl"
>> first_name + " " + last_name # Concatenation, with a space in between
=> "Michael Hartl"
>> "#{first_name} #{last_name}" # The equivalent interpolation
=> "Michael Hartl"
" " кажется мне немного неуклюжим.Печать
Для того, чтобы напечатать строку, наиболее часто используется Ruby функцияputs (произносится как “put ess”, от “put string”):>> puts "foo" # put string
foo
=> nil
Puts метод работает с побочным эффектом: выражение puts "foo" выводит строку на экран, а затем возвращает буквально ничего: nil это особое обозначение Ruby для “вообще ничего”. (В дальнейшем, я буду иногда опускать => nil часть для простоты.)Использование
puts автоматически добавляет символ новой строки \n к выводу; связанный print метод — нет:>> print "foo" # print string (same as puts, but without the newline)
foo=> nil
>> print "foo\n" # Same as puts "foo"
foo
=> nil
Строки в одиночных кавычках
Все примеры до сих пор использовали строки в двойных кавычках, но Ruby также поддерживает строки в одиночных кавычках. Для многих целей, оба типа строк идентично эффективны:>> 'foo' # A single-quoted string
=> "foo"
>> 'foo' + 'bar'
=> "foobar"
>> '#{foo} bar' # Single-quoted strings don't allow interpolation
=> "\#{foo} bar"
#.Если строки в двойных кавычках могут делать все то же, что и одиночно закавыченные, и могут интерполировать, какой смысл в одиночных кавычках? Они часто бывают полезны, потому что они действительно буквальные, и хранят в точности такие символы, как вы вводите. Например, “обратный слэш” (бэкслэш) — символ специальный в большинстве систем, например, буквальной новой строке \n. Если вы хотите чтобы переменная содержала буквально обратный слэш, в одиночных кавычках это сделать проще:
>> '\n' # A literal 'backslash n' combination
=> "\\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 # Passing the "length" message to a string
=> 6
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 тоже является объектом, поэтому он тоже может отвечать на методы. Одним из примеров является 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? # Message chaining
=> true
nil объект сам по себе не реагирует на empty? метод, но nil.to_s реагирует.Вот специальный метод для проверки на
nil-ность, о котором вы могли догадаться:>> "foo".nil?
=> false
>> "".nil?
=> false
>> nil.nil?
=> true
title помощник тестирует, является ли @title — nil, используя nil? метод. Это намек на то, что есть что-то особенное в переменных экземпляра (переменных с @ знаком), что может быть лучше понятым, противопоставлением их обычным переменным. Например, предположим, что мы вводим title и @title переменные в консоли не определив их вначале:>> title # Oops! We haven't defined a title variable.
NameError: undefined local variable or method `title'
>> @title # An instance variable in the console
=> nil
>> puts "There is no such instance variable." if @title.nil?
There is no such instance variable.
=> nil
>> "#{@title}" # Interpolating @title when it's nil
=> ""
nil если она не определена. Это также объясняет, почему кодRuby on Rails Tutorial Sample App | <%= @title %>
Ruby on Rails Tutorial Sample App |
@title является nil: Embedded Ruby вставляет строку, соответствующую данной переменной, а строкой, соответствующей nil является "".Последний пример также показывает альтернативное использование ключевого слова
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 is false
>> end
=> false
>> if 0
>> true # 0 (and everything other than nil and false itself) is true
>> else
>> false
>> end
=> true
4.2.4 Определение метода
Консоль позволяет определять методы точно так же, как мы это делали сhome действием из Листинга 3.6 или 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.
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 в обоих местах придает ему приятную симметрию.
4.2.5 Возвращение к title хелперу
Теперь мы в состоянии понять title помощника из Листинга 4.2:9module ApplicationHelper
# Return a title on a per-page basis. # Documentation comment
def title # Method definition
base_title = "Ruby on Rails Tutorial Sample App" # Variable assignment
if @title.nil? # Boolean test for nil
base_title # Implicit return
else
"#{base_title} | #{@title}" # String interpolation
end
end
end
module ApplicationHelper: код в модулях Ruby может быть подмешан
в Ruby классы. При написании обычных Ruby, вы часто пишете модули и
сами их явно включаете, но в данном случае Rails делает включение за
нас, автоматически. Результатом является то, что title метод автомагически доступен во всех наших представлениях.4.3 Другие структуры данных
Хотя веб-приложения, в конечном счете это строки, фактически, для изготовления этих строк требуется также использование других структур данных. В этом разделе мы узнаем о некоторых структурах данных Ruby, важных для написания приложений Rails.4.3.1 Массивы и диапазоны
Массив это всего лишь список элементов в определенном порядке. Мы еще не обсуждали массивы в Rails Tutorial, но их понимание дает хорошую основу для понимания хэшей (Раздел 4.3.3) и для аспектов Rails моделирования данных (таких какhas_many ассоциации, которые мы видели в Разделе 2.3.3 и больше раскроем в Разделе 11.1.2).Мы потратили много времени на понимание строк, и есть естественный способ перейти от строк к массивам, используя
split метод:>> "foo bar baz".split # Split a string into a three-element array
=> ["foo", "bar", "baz"]
split делит строку на массив путем разделения по пробелу, но вы можете разделить практически по чему угодно:>> "fooxbarxbazx".split('x')
=> ["foo", "bar", "baz"]
>> a = [42, 8, 17]
=> [42, 8, 17]
>> a[0] # Ruby uses square brackets for array access.
=> 42
>> a[1]
=> 8
>> a[2]
=> 17
>> a[-1] # Indices can even be negative!
=> 17
>> a # Just a reminder of what 'a' is
=> [42, 8, 17]
>> a.first
=> 42
>> a.second
=> 8
>> a.last
=> 17
>> a.last == a[-1] # Comparison using ==
=> true
==, который Ruby разделяет со многими другими языками, как и связанные != (“не равно”), и т.д.:>> x = a.length # Like strings, arrays respond to the 'length' method.
=> 3
>> x == 3
=> true
>> x == 1
=> false
>> x != 1
=> true
>> x >= 1
=> true
>> x < 1
=> false
length (первая строка в приведенном выше примере), массивы отвечают на множество других методов:>> a.sort
=> [8, 17, 42]
>> a.reverse
=> [17, 8, 42]
>> a.shuffle
=> [17, 42, 8]
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"]
Прежде, мы видели что
split преобразовывает строку в массив. Мы также можем пойти другим путем с join методом:>> a
=> [42, 8, 17, 7, "foo", "bar"]
>> a.join # Join on nothing
=> "428177foobar"
>> a.join(', ') # Join on comma-space
=> "42, 8, 17, 7, foo, bar"
to_a метода:>> 0..9
=> 0..9
>> 0..9.to_a # Oops, call to_a on 9
ArgumentError: bad value for range
>> (0..9).to_a # Use parentheses to call to_a on the range
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
0..9 и является допустимым диапазоном, второе выражение показывает, что нам нужно добавить скобки для вызова метода на нем.Диапазоны полезны для вытаскивания элементов из массива:
>> a = %w[foo bar baz quux] # Use %w to make a string array.
=> ["foo", "bar", "baz", "quux"]
>> a[0..2]
=> ["foo", "bar", "baz"]
>> ('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
do..end синтаксис для длинных однострочных и многострочных блоков:>> (1..5).each do |number|
?> puts 2 * number
>> puts '--'
>> end
2
--
4
--
6
--
8
--
10
--
=> 1..5
number вместо i просто чтобы подчеркнуть, что имя переменной может быть любым.Если вы существенно не владеете основами программирования, нет короткой дороги к пониманию блоков; просто их нужно много увидеть, и, в конечном итоге, вы привыкнете к ним.11 К счастью, люди довольно хороши на обобщения на основе конкретных примеров; вот еще несколько примеров, в том числе пара с использованием
map метода:>> 3.times { puts "Betelgeuse!" } # 3.times takes a block with no variables.
"Betelgeuse!"
"Betelgeuse!"
"Betelgeuse!"
=> 3
>> (1..5).map { |i| i**2 } # The ** notation is for 'power'.
=> [1, 4, 9, 16, 25]
>> %w[a b c] # Recall that %w makes string arrays.
=> ["a", "b", "c"]
>> %w[a b c].map { |char| char.upcase }
=> ["A", "B", "C"]
map метод возвращает результат применения данного блока для каждого элемента в массиве или диапазоне.Кстати, теперь мы в состоянии понять строку Ruby, которую я вбросил в Разделе 1.4.4 для генерации случайных субдоменов:
('a'..'z').to_a.shuffle[0..7].join
>> ('a'..'z').to_a # An alphabet array
=> ["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 # Shuffle it.
=> ["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] # Pull out the first eight elements.
=> ["f", "w", "i", "a", "h", "p", "c", "x"]
>> ('a'..'z').to_a.shuffle[0..7].join # Join them together to make one string.
=> "mznpybuj"
4.3.3 Хэши и символы
Хэши, по существу, это объединение массивов: вы можете думать о хэшах в основном, как о массивах, но не ограничиваясь целочисленными индексами. (В самом деле, некоторые языки, особенно Perl, иногда называют хэши associative arrays (ассоциативными массивами) по этой причине.) Вместо этого, хэш-индексами, ключами, могут быть практически любые объекты. Например, мы можем использовать строки в качестве ключей:>> user = {} # {} is an empty hash.
=> {}
>> user["first_name"] = "Michael" # Key "first_name", value "Michael"
=> "Michael"
>> user["last_name"] = "Hartl" # Key "last_name", value "Hartl"
=> "Hartl"
>> user["first_name"] # Element access is like arrays.
=> "Michael"
>> user # A literal representation of the hash
=> {"last_name"=>"Hartl", "first_name"=>"Michael"}
{} — это
пустой хэш. Важно отметить, что фигурные скобки для хэшей не имеют
ничего общего с фигурными скобками для блоков. (Да, это может привести к
путанице.) Хотя хэши и напоминают массивы, одним важным отличием
является то, что хэши не гарантируют сохранность их элементов в
определенном порядке.12 Если порядок важен, используйте массив.Вместо того, чтобы определять хэши попунктно, используя квадратные скобки, проще использовать их буквальное представление:
>> user = { "first_name" => "Michael", "last_name" => "Hartl" }
=> {"last_name"=>"Hartl", "first_name"=>"Michael"}
До сих пор мы использовали строки в качестве хэш-ключей, но в Rails гораздо чаще вместо них используются символы. Символы выглядят как строки, но с префиксом двоеточие, а не в кавычках. Например,
:name это символ. Вы можете думать о символах, в основном, как о строках без дополнительного багажа:13>> "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
В терминах символов как хэш-ключей, мы можем определить
user хэш следующим образом:>> user = { :name => "Michael Hartl", :email => "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> user[:name] # Access the value corresponding to :name.
=> "Michael Hartl"
>> user[:password] # Access the value of an undefined key.
=> nil
nil.Хеш значениями может быть практически все, даже другие хэши, как видно в Листинге 4.5.
Листинг 4.5. Вложенные хэши.
>> params = {} # Define a hash called 'params' (short for '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"
Также как массивы и диапазоны, хэши реагируют на
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 # Put an array as a string.
1
2
3
4
5
>> puts (1..5).to_a.inspect # Put a literal array.
[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 # Same as 'puts :name.inspect'
:name
4.3.4 Вновь CSS
Пришло время снова посетить строки из Листинга 4.4 используемые в макете для включения каскадных таблиц стилей:<%= stylesheet_link_tag 'blueprint/screen', :media => 'screen' %>
<%= stylesheet_link_tag 'blueprint/print', :media => 'print' %>
stylesheet_link_tag 'blueprint/screen', :media => 'screen'
# Parentheses on function calls are optional.
stylesheet_link_tag('blueprint/screen', :media => 'screen')
stylesheet_link_tag 'blueprint/screen', :media => 'screen'
:media аргумент уверенно выглядит как хэш, но где фигурные скобки? Когда хэш — последний аргумент в вызове функции, фигурные скобки не являются обязательными; эти две строки эквивалентны:# Curly braces on final hash arguments are optional.
stylesheet_link_tag 'blueprint/screen', { :media => 'screen' }
stylesheet_link_tag 'blueprint/screen', :media => 'screen'
<%= stylesheet_link_tag 'blueprint/screen', :media => 'screen' %>
<%= stylesheet_link_tag 'blueprint/print', :media => 'print' %>
stylesheet_link_tag функцию с двумя аргументами: строкой, указывающей путь к таблице стилей, и хэшем, с указанием типа носителя ( ’screen’ для экрана компьютера и ’print’ для печатной версии). Из-за <%= %>
скобок, результаты вставляются в шаблон ERb-ой, и если вы посмотрите
исходный код страницы в браузере, вы должны увидеть HTML, необходимый
для включении таблиц стилей (Листинг 4.6).14
Листинг 4.6. Исходный код HTML произведенный включением CSS.
<link href="/stylesheets/blueprint/screen.css" media="screen" rel="stylesheet"
type="text/css" />
<link href="/stylesheets/blueprint/print.css" media="print" rel="stylesheet"
type="text/css" />
4.4 Ruby классы
Мы говорили, что все в Ruby является объектами, и в этом разделе мы, наконец, определим несколько собственных классов. Ruby, как и многие другие объектно-ориентированные языки, использует классы чтобы организовать методы; эти классы, затем экземплируются для создания объектов. Если вы новичок в объектно-ориентированном программировании, это может звучать как бред, так что давайте посмотрим на некоторые конкретные примеры.4.4.1 Конструкторы
Мы видели много примеров использования классов для создания экземпляра объекта, но нам еще предстоит сделать это в явном виде. Например, мы экземплировали строку с помощью двойных кавычек, которые являются буквальным конструктором для строк:>> s = "foobar" # A literal constructor for strings using double quotes
=> "foobar"
>> s.class
=> String
class, и просто возвращают класс к которому они принадлежат.Вместо использования буквального конструктора, можно использовать аналогичный именованный конструктор, что подразумевает вызов
new метода на имени класса:15>> s = String.new("foobar") # A named constructor for a string
=> "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] # Try to access the value for the nonexistent key :foo.
=> nil
>> h = Hash.new(0) # Arrange for nonexistent keys to return 0 instead of nil.
=> {}
>> h[:foo]
=> 0
4.4.2 Наследование классов
При изучении классов, полезно выяснять иерархию классов, используяsuperclass метод:>> s = String.new("foobar")
=> "foobar"
>> s.class # Find the class of s.
=> String
>> s.class.superclass # Find the superclass of String.
=> Object
>> s.class.superclass.superclass # Ruby 1.9 uses a new BasicObject base class
=> BasicObject
>> s.class.superclass.superclass.superclass
=> nil
String является Object , а суперкласс Object это BasicObject, но BasicObject
не имеет суперкласса. Эта модель относится к каждому объекту Ruby:
можно проследить иерархию классов достаточно далеко, и каждый класс в
Ruby в конечном счете наследуется от BasicObject, который не имеет своего суперкласса. Это техническое значение выражения “все в Ruby является объектом”.
Рис. 4.2: Иерархия наследования
String класса.Word класс с palindrome? методом, который возвращает true, если слово можно читать и справа налево и слева направо, сохраняя смысл:>> class Word
>> def palindrome?(string)
>> string == string.reverse
>> end
>> end
=> nil
>> w = Word.new # Make a new Word object.
=> #<Word:0x22d0b20>
>> w.palindrome?("foobar")
=> false
>> w.palindrome?("level")
=> true
Word класс от String, как это показано в Листинге 4.7. (Вы должны выйти из консоли и ввести его заново, чтобы убрать старое определение Word.)
Листинг 4.7. Определение
Word класса в консоли. >> class Word < String # Word inherits from String.
>> # Return true if the string is its own reverse.
>> def palindrome?
>> self == self.reverse # self is the string itself.
>> end
>> end
=> nil
Word < String это Ruby-синтаксис для наследования (кратко обсуждается в Разделе 3.1.2), который гарантирует, что, в дополнение к новому palindrome? методу, words (слова) также имеют все те же методы, что и строки:>> s = Word.new("level") # Make a new Word, initialized with "level".
=> "level"
>> s.palindrome? # Words have the palindrome? method.
=> true
>> s.length # Words also inherit all the normal string methods.
=> 5
Word класс наследует от String , мы можем использовать консоль, чтобы увидеть иерархию классов в явном виде:>> s.class
=> Word
>> s.class.superclass
=> String
>> s.class.superclass.superclass
=> Object
Рисунок 4.3: Иерархия наследования (не встроенного)
Word класса из Листинга 4.7.Word класса. Ruby позволяет нам сделать это, используя ключевое слово self: в Word классе, self является самим объектом, что означает, что мы можем использоватьself == self.reverse
4.4.3 Изменение встроенных классов
Хотя наследование это мощная идея, в случае палиндромов может быть даже более естественно добавитьpalindrome? метод самому классу String, так что бы (среди прочего) мы могли вызвать palindrome? на буквальную строку, что мы в настоящее время не можем сделать:>> "level".palindrome?
NoMethodError: undefined method `palindrome?' for "level":String
>> class String
>> # Return true if the string is its own reverse.
>> def palindrome?
>> self == self.reverse
>> end
>> end
=> nil
>> "deified".palindrome?
=> true
"deified" является палиндромом.)Изменение встроенных классов является мощной техникой, но с большой властью приходит большая ответственность и это считается дурным тоном добавлять методы к встроенным классам, не имея действительно хорошей причины для этого. Rails имеет несколько хороших причин (чтобы не делать этого); например, в веб-приложениях вы часто не хотите, чтобы переменные были пустыми; например, имя пользователя должно быть чем то другим, нежели ы и прочие пробелы—и Rails добавляет
blank?
метод к Ruby. Rails консоль автоматически включает Rails расширения, и
мы можем увидеть это, например, здесь (это не будет работать в простом irb (Интерактивном Руби)):>> "".blank?
=> true
>> " ".empty?
=> false
>> " ".blank?
=> true
>> nil.blank?
=> true
nil является чистым, так как nil не является строкой, это намек, на то, что Rails фактически добавляет blank? к базовому классу String,
которым (как мы видели в начале этого раздела) является сам Object. Мы
увидим некоторые другие примеры Rails дополнений в Ruby классы в Разделе 9.3.2.4.4.4 Класс контроллер
Все эти разговоры о классах и наследованиях, возможно, вызвали вспышку узнавания, потому что мы видели их и раньше, в контроллере Pages (Листинг 3.24):class PagesController < ApplicationController
def home
@title = "Home"
end
def contact
@title = "Contact"
end
def about
@title = "About"
end
end
PagesController это класс, который наследует от ApplicationController и он оснащен home, contact и about методами, каждый из которых определяет переменную экземпляра @title.
Так как каждый сеанс Rails консоли загружает локальную среду Rails, мы
можем даже создать контроллер в явном виде и изучить иерархию его
классов:18>> controller = PagesController.new
=> #<PagesController:0x22855d0>
>> controller.class
=> PagesController
>> 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.4: Иерархия наследования для контроллера Pages.
>> controller.home
=> "Home"
"Home" происходит от назначения @title = "Home" в home действии.Но постойте: действия не имеют возвращаемых значений, по крайней мере не действия со знаком вопроса (#булевые). Целью
home действия, как мы видели в Главе 3, является визуализация веб-страницы. И я бы точно не забыл, если бы когда-либо вызывал PagesController.new где-нибудь. Что происходит?Происходит вот что: Rails написан на Ruby, но Rails это не Ruby. Некоторые Rails классы используются как обычные объекты Ruby, но некоторые из них просто льют воду на Рельсовую "волшебную мельницу". Rails это sui generis, и должен быть изучен и понят отдельно от Ruby. Именно поэтому, если ваш основной интерес программирования заключается в написании веб-приложений, я рекомендую изучать вначале Rails, затем изучать Ruby, затем вернуться обратно на рельсы.
4.4.5 Класс user
Мы закончим наше путешествие по Ruby комплектацией собственного класса,User класса, который ожидает User модель которая появится в Главе 6.До сих пор мы вводили определения классов на консоли, но это быстро стало утомительным, вместо этого создайте файл
example_user.rb в корневом каталоге Rails и заполните его содержимым Листинга 4.8. (Напомним, из Раздела 1.1.3
что корневой каталог Rails это корень вашего каталога приложения;
например, корневой каталог Rails для моего примера приложения это /Users/mhartl/rails_projects/sample_app.)
Листинг 4.8. Код для примера пользователя.
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
@name и @email переменные экземпляра.Первый метод,
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
>> require './example_user' # This is how you load the example_user code.
=> ["User"]
>> example = User.new
=> #<User:0x224ceec @email=nil, @name=nil>
>> example.name # nil since attributes[:name] is nil
=> nil
>> example.name = "Example User" # Assign a non-nil name
=> "Example User"
>> example.email = "user@example.com" # and a non-nil email address
=> "user@example.com"
>> example.formatted_email
=> "Example User <user@example.com>"
’.’ — Unix обозначение для “текущего каталога”, и ’./example_user’
говорит Ruby искать файл примера пользователя относительно этого
расположения. Последующий код создает пустого пользователя в качестве
примера и затем заполняет имя и адрес электронной почты, присвоением
непосредственно к соответствующим атрибутам (присвоение стало возможными
благодаря attr_accessor строке в Листинге 4.8). Когда мы пишемexample.name = "Example User"
@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>"
4.5 Упражнения
- Используя Листинг 4.9 как руководство, скомбинировать
split,shuffle, иjoinчтобы написать функцию, которая тасует буквы в данной строке. - Используя Листинг 4.10 как руководство, добавить
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.
Листинг 4.9. Скелет для функции string shuffle.
>> def string_shuffle(s)
>> s.split('').?.?
>> end
=> nil
>> string_shuffle("foobar")
Листинг 4.10. Скелет для
shuffle метода, принадлежащего String классу. >> class String
>> def shuffle
>> self.split('').?.?
>> end
>> end
=> nil
>> "foobar".shuffle
- Если помощник определен для конкретного контроллера,
следует поместить его в соответствующий файл помощника; например,
помощники для контроллера Pages обычно входят в
app/helpers/pages_helper.rb. В нашем случае мы ожидаем, что помощник заголовка будет использоваться на страницах всего сайта, и у Rails есть специальный файл помощника для этого случая:app/helpers/application_helper.rb. ↑ - Я не даю ссылки на API, потому что у них есть тенденция быстро устаревать. Давайте Google будет вашей путеводной звездой. Кстати, “API” означает “Application Programming Interface (Интерфейс прикладного программирования)”. ↑
- Если вы нетерпеливы, не стесняйтесь проверить Blueprint CSS wiki. ↑
- Вспомните, что консольное приглашение, вероятно, будет чем-то вроде
ruby-1.9.2-head >, но примеры используют>>так как версии Ruby изменяются. ↑ - Как и с Rails API, ссылки Ruby API выходят из моды, хотя и не так быстро. Google по-прежнему всегда готов вам помочь ↑
- Чтобы узнать больше о происхождении “foo” и “bar”—и, в частности возможном не отношении “foobar” к “FUBAR”—см. “foo” в Jargon File. . ↑
- Програмисты, знакомые с Perl или PHP, должны сравнить
это с автоматической интерполяцией переменных знака доллара в таких
выражениях, как
"foo $bar". ↑ - Заранее извиняюсь за случайные переключения между
функциейиметодомповсюду в этой главе; в Ruby они — одно и то же: все методы — функции, и все функции — методы, потому что все — объект. ↑ - Ну, все же осталась одна вещь, которую мы не понимаем — как Rails связывает все это вместе: направление URL к действиям, делание
titleпомощника доступным в представлениях, и т.д. Это интересная тема, и я поощряю вас исследовать ее далее, но точное знание того, как Rails работает, не является необходимым для использования Rails. (Для более глубокого понимания я рекомендую The Rails 3 Way Оби Фернандеса.) ↑ Secondметод, используемый здесь, не является в настоящий момент частью Ruby непосредственно, а скорее добавляется Rails. Это работает в данном случае, потому что консоль Rails автоматически включает Rails расширения в Ruby. ↑- С другой стороны, искушенные программисты могут извлечь пользу из понимания, что блоки - это замыкания, объединяющие анонимные функции с внешними, по отношению к этим функциям, данными. ↑
- Ruby 1.9 фактически гарантирует, что хеши сохраняют свои элементы в том же самом порядке в каком они вводились, но было бы неблагоразумно когда-либо рассчитывать на определенный порядок. ↑
- В результате наличия меньшего количества багажа символы легче сравнить друг с другом; строки должны быть сравнены посимвольно, в то время как символы могут быть сравнены все одним разом. Это делает их идеальными для использования в качестве хеш ключей. ↑
- Можно увидеть немного пугающие числа, такие как
?1257465942, после имен файла CSS. Они вставляются Rails, чтобы гарантировать, что браузеры перезагружают CSS, когда они изменяются на сервере. ↑ - Эти результаты могут изменятся, в зависимости от версии Ruby, которую вы используете. Этот пример предполагает, что вы используете Ruby 1.9.2. ↑
- Для того чтобы узнать больше о классах Ruby и ключевом слове
self, см. RailsTips пост “Class and Instance Variables in Ruby”. ↑ - Для знакомых с JavaScript, эта функциональность сопоставима с использованием встроенного class prototype object для расширения класса. (Спасибо читателю Erik Eldridge за указание на это.) ↑
- Вы не должны знать, что делает каждый класс в этой иерархии. Я не знаю, что они все делают, и я программирую в Ruby on Rails с 2005. Это означает или что (a), я чрезвычайно некомпетентен или (b), можно быть квалифицированным Rails разработчиком, не зная все его внутренности. Для нашей с вами пользы я уповаю на последнее. ↑
Комментариев нет:
Отправить комментарий