Для запуска команды командной строки через Ruby используется команда
system("commandhere")
Эта команда эквивалентна команде в PHP
exec("commandhere");
В Python аналогичная команда будет записана так
import os
os.system("commandhere")
Пример.
system("compass compile")
Другие примеры.
system 'echo hi' #=> true (prints 'hi')
system 'echo hi >&2' #=> true (prints 'hi' to stderr)
system 'exit 1' #=> nil
Ruby
Ruby on Rails уроки программирования. Обучение и документация на русском языке.
среда, 30 января 2013 г.
среда, 28 ноября 2012 г.
Работа с GitHub
Если вы работаете над проектом в GitHub, то вы можете просто ответвить проект (fork) в ваш GitHub-репозиторий или создать новый репозиторий под вашим именем. После того, как вы внесете изменения в вашем GitHub-репозитории, вы можете послать запрос на добавление (pull) изменений в GitHub-репозитории других пользователей, уведомив их о внесенных вами изменениях, после чего они смогут добавить (pull) ваши изменения в свои репозитории.
Для примера форкнем проект ClickToFlash. Перейдите в проект по адресу http://github.com/rentzsch/clicktoflash и кликните на кнопку "Fork". После этого копия данного проекта появится в вашем GitHub-репозиторий (например http://github.com/lapcat/clicktoflash). Теперь вам необходимо клонировать код из вашего репозитория на ваш локальный компьютер. Убедитесь в том, что вы клонируете SSH-версию. Если вы клонируете read-only версию, тогда вы не сможете добавлять (pull) изменения в ваш GitHub-репозиторий.
$ git clone git@github.com:lapcat/clicktoflash.git
Ваша частная локальная копия автоматически будет иметь master branch ветку, которая соответствует master branch ветке в ваше публичном репозитории на GitHub.
$ cd clicktoflash
$ git branch
* master
$ git status
# On branch master nothing to commit (working directory clean)
Ваш удаленный клонированный репозиторий дал особое имя origin вашему локальному клону репозитория.
$ git remote
origin
Локальная ветка master branch всегда отслеживает удаленный репозиторий на GitHub, поэтому команды git fetch, git pull и git push автоматически добавляются в origin, когда запускается checked out в master ветке.
$ git remote show origin
* remote origin
Fetch URL: git@github.com:lapcat/clicktoflash.git
Push URL: git@github.com:lapcat/clicktoflash.git
HEAD branch: master
Remote branches:
cutting-edge tracked
master tracked
Local branch configured for 'git pull':
master merges with remote master
Local ref configured for 'git push':
master pushes to master (up to date)
Несмотря на тот факт, что локальный репозиторий является клоном origin, а origin - это форк (fork) из репозитория пользователя rentzsch, локальный репозиторий ничего не знает о rentzsch. Поэтому, если вы хотите добавить (pull) ваши изменения и репозитория rentzsch, то вы должны добавить (add) удаленный репозиторий. В этом случае вы можете использовать read-only URL, из-за того, что вы не можете передать изменения в репозиторий rentzsch напрямую.
$ git remote add rentzsch git://github.com/rentzsch/clicktoflash.git
$ git remote
origin
rentzsch
Некоторые люди предпочитают давать имя удаленному репозиторию upstream, но я нахожу это несколько запутывающим. Имя rentzsch говорит точно откуда приходят изменения. В отличии от имени upstream оно не абстрактно и его не спутаешь с origin.
Обратите внимание, что пока вы не используете опцию -f, удаленный репозиторий rentzsch автоматически не фетчится. Поэтому вам нужно фетчить (получать из него данные) вручную.
$ git branch rentzsch-master rentzsch/master
fatal: Not a valid object name: 'rentzsch/master'.
$ git branch -r
origin/HEAD -> origin/master
origin/cutting-edge
origin/master
$ git fetch rentzsch
$ git branch -r
origin/HEAD -> origin/master
origin/cutting-edge
origin/master
rentzsch/1.4.2-64bit
rentzsch/cutting-edge
rentzsch/gh-pages
rentzsch/master
$ git branch rentzsch-master rentzsch/master
Branch rentzsch-master set up to track remote branch master from rentzsch.
Я рекомендую создать вам ответвление branch для отслеживания форкнутого репозитория, как мы это сделали в последнуй инструкции выше. После этого будет не важно какие изменения вы сделаете, вы всегда сможете посмотреть "официальную" версию проекта, делая check rentzsch-master branch. Если вы используете удаленный rentzsch-master branch как стартовую точку для локального ответвления rentzsch-master, то локальный branch автоматически будет отслеживать удаленный репозиторий rentzsch, как если бы локальный master автоматически отслеживал удаленный origin.
$ git remote show rentzsch
* remote rentzsch
Fetch URL: git://github.com/rentzsch/clicktoflash.git
Push URL: git://github.com/rentzsch/clicktoflash.git
HEAD branch: master
Remote branches:
1.4.2-64bit tracked
cutting-edge tracked
gh-pages tracked
master tracked
Local branch configured for 'git pull':
rentzsch-master merges with remote master
Local ref configured for 'git push':
master pushes to master (fast-forwardable)
Когда изменения происходят в master branch удаленного репозитория rentzsch, то мы используем процедуру merge для слияния их:
$ git checkout rentzsch-master
$ git fetch
$ git merge rentzsch/master
$ git checkout master
$ git merge rentzsch-master
$ git push
Вы можете использовать одну команду git pull вместо двух git fetch и git merge rentzsch/master. Однако я слышал, что git pull иногда вызывает проблемы.
В любом случае, вы сперва делаете слияние (merge) удаленного репозитория rentzsch с локальной веткой rentzsch-master branch. Затем делаете слияние (merge) ветки rentzsch-master branch с локальной master branch и, наконец, добавляете (pull) локальные изменения в удаленный репозиторий origin. Эта процедура необходима, потому что вы не можете напрямую добавить изменения из удаленного репозитория rentzsch в origin. Они должны пройти через локальный репозиторий.
Никогда не записывайте код в локальный master branch, иначе все сломается. Оставляйте master чистым.
Когда вы хотите сделать локальные изменения, то всегда создавайте и делайте check out в новый branch, а затем делайте слияние (merge) изменений branch, в который вы хотите добавить изменения.
$ git branch rentzsch-cutting-edge rentzsch/cutting-edge
Branch rentzsch-cutting-edge set up to track remote branch cutting-edge from rentzsch.
$ git branch cutting-edge rentzsch-cutting-edge
$ git checkout cutting-edge
Switched to branch 'cutting-edge'
Для примера форкнем проект ClickToFlash. Перейдите в проект по адресу http://github.com/rentzsch/clicktoflash и кликните на кнопку "Fork". После этого копия данного проекта появится в вашем GitHub-репозиторий (например http://github.com/lapcat/clicktoflash). Теперь вам необходимо клонировать код из вашего репозитория на ваш локальный компьютер. Убедитесь в том, что вы клонируете SSH-версию. Если вы клонируете read-only версию, тогда вы не сможете добавлять (pull) изменения в ваш GitHub-репозиторий.
$ git clone git@github.com:lapcat/clicktoflash.git
Ваша частная локальная копия автоматически будет иметь master branch ветку, которая соответствует master branch ветке в ваше публичном репозитории на GitHub.
$ cd clicktoflash
$ git branch
* master
$ git status
# On branch master nothing to commit (working directory clean)
Ваш удаленный клонированный репозиторий дал особое имя origin вашему локальному клону репозитория.
$ git remote
origin
Локальная ветка master branch всегда отслеживает удаленный репозиторий на GitHub, поэтому команды git fetch, git pull и git push автоматически добавляются в origin, когда запускается checked out в master ветке.
$ git remote show origin
* remote origin
Fetch URL: git@github.com:lapcat/clicktoflash.git
Push URL: git@github.com:lapcat/clicktoflash.git
HEAD branch: master
Remote branches:
cutting-edge tracked
master tracked
Local branch configured for 'git pull':
master merges with remote master
Local ref configured for 'git push':
master pushes to master (up to date)
Несмотря на тот факт, что локальный репозиторий является клоном origin, а origin - это форк (fork) из репозитория пользователя rentzsch, локальный репозиторий ничего не знает о rentzsch. Поэтому, если вы хотите добавить (pull) ваши изменения и репозитория rentzsch, то вы должны добавить (add) удаленный репозиторий. В этом случае вы можете использовать read-only URL, из-за того, что вы не можете передать изменения в репозиторий rentzsch напрямую.
$ git remote add rentzsch git://github.com/rentzsch/clicktoflash.git
$ git remote
origin
rentzsch
Некоторые люди предпочитают давать имя удаленному репозиторию upstream, но я нахожу это несколько запутывающим. Имя rentzsch говорит точно откуда приходят изменения. В отличии от имени upstream оно не абстрактно и его не спутаешь с origin.
Обратите внимание, что пока вы не используете опцию -f, удаленный репозиторий rentzsch автоматически не фетчится. Поэтому вам нужно фетчить (получать из него данные) вручную.
$ git branch rentzsch-master rentzsch/master
fatal: Not a valid object name: 'rentzsch/master'.
$ git branch -r
origin/HEAD -> origin/master
origin/cutting-edge
origin/master
$ git fetch rentzsch
$ git branch -r
origin/HEAD -> origin/master
origin/cutting-edge
origin/master
rentzsch/1.4.2-64bit
rentzsch/cutting-edge
rentzsch/gh-pages
rentzsch/master
$ git branch rentzsch-master rentzsch/master
Branch rentzsch-master set up to track remote branch master from rentzsch.
Я рекомендую создать вам ответвление branch для отслеживания форкнутого репозитория, как мы это сделали в последнуй инструкции выше. После этого будет не важно какие изменения вы сделаете, вы всегда сможете посмотреть "официальную" версию проекта, делая check rentzsch-master branch. Если вы используете удаленный rentzsch-master branch как стартовую точку для локального ответвления rentzsch-master, то локальный branch автоматически будет отслеживать удаленный репозиторий rentzsch, как если бы локальный master автоматически отслеживал удаленный origin.
$ git remote show rentzsch
* remote rentzsch
Fetch URL: git://github.com/rentzsch/clicktoflash.git
Push URL: git://github.com/rentzsch/clicktoflash.git
HEAD branch: master
Remote branches:
1.4.2-64bit tracked
cutting-edge tracked
gh-pages tracked
master tracked
Local branch configured for 'git pull':
rentzsch-master merges with remote master
Local ref configured for 'git push':
master pushes to master (fast-forwardable)
Когда изменения происходят в master branch удаленного репозитория rentzsch, то мы используем процедуру merge для слияния их:
$ git checkout rentzsch-master
$ git fetch
$ git merge rentzsch/master
$ git checkout master
$ git merge rentzsch-master
$ git push
Вы можете использовать одну команду git pull вместо двух git fetch и git merge rentzsch/master. Однако я слышал, что git pull иногда вызывает проблемы.
В любом случае, вы сперва делаете слияние (merge) удаленного репозитория rentzsch с локальной веткой rentzsch-master branch. Затем делаете слияние (merge) ветки rentzsch-master branch с локальной master branch и, наконец, добавляете (pull) локальные изменения в удаленный репозиторий origin. Эта процедура необходима, потому что вы не можете напрямую добавить изменения из удаленного репозитория rentzsch в origin. Они должны пройти через локальный репозиторий.
Никогда не записывайте код в локальный master branch, иначе все сломается. Оставляйте master чистым.
Когда вы хотите сделать локальные изменения, то всегда создавайте и делайте check out в новый branch, а затем делайте слияние (merge) изменений branch, в который вы хотите добавить изменения.
$ git branch rentzsch-cutting-edge rentzsch/cutting-edge
Branch rentzsch-cutting-edge set up to track remote branch cutting-edge from rentzsch.
$ git branch cutting-edge rentzsch-cutting-edge
$ git checkout cutting-edge
Switched to branch 'cutting-edge'
пятница, 2 ноября 2012 г.
Ruby on Rails MySQL
Если вы используете Ruby on Rails 3, то для создания приложения с установленной базой данных MySQL вместо sqlite3 наберите в консоли команду:
rails new your_project_name -d mysql
Если вы используете более раннюю версию Ruby on Rails, то наберите в консоли команду:
rails new -d mysql your_project_name
Для того, чтобы узнать номер вашей версии Ruby on Rails наберите в консоли:
rails -v
rails new your_project_name -d mysql
Если вы используете более раннюю версию Ruby on Rails, то наберите в консоли команду:
rails new -d mysql your_project_name
Для того, чтобы узнать номер вашей версии Ruby on Rails наберите в консоли:
rails -v
среда, 31 октября 2012 г.
Ruby on Rails Подсказки
Запомнить, где именно в модели в Ruby on Rails нужно помещать инструкцию belongs_to, несложно: если у таблицы есть внешние ключи (англ. foreign key), то соответствующая модель должна иметь для каждого из них инструкцию belongs_to.
Здесь проиллюстрировано одно важное правило: модель для таблицы, имеющей
внешний ключ (англ. foreign key), всегда содержит объявление belongs_to.
В Active Record родительский объект (тот самый, который логически содержит коллекцию дочерних объектов) использует для объявления своих связей с дочерней таблицей has_many, а для дочерней таблицы, чтобы указать на ее родителя, используется объявление belongs_to.
Связи многие ко многим являются симметричными, обе связываемые таблицы объявляют свою связь друг с другом, используя has_and_belongs_to_many.
Rails реализует связи многие ко многим, используя промежуточную объединительную таблицу. В ней содержатся пары внешних ключей, связывающие две заданные таблицы. Active Record предполагает, что имя этой объединительной таблицы является объединением имен двух заданных таблиц, следующих в алфавитном порядке.
product_path против product_url
Поначалу кажется, что будет довольно сложно узнать, когда при необходимости создать ссылку или перенаправление по заданному маршруту нужно использовать метод product_path, а когда метод product_url. Но на самом деле все довольно просто.
При использовании метода product_url вы получите полную начинку с протоколом и доменным именем, наподобие http://example.com /products/l. Его следует использовать, если осуществляется перенаправление redirect_to, поскольку спецификация HTTP при осуществлении перенаправлений с кодом 302 и им подобных требует указывать URL-адрес полностью.
Полный URL-адрес нужен также при перенаправлении с одного домена на другой, например, product_url(domain: "example2.com", product: product).
Во всех остальных случаях можно с успехом использовать product_path. Этот метод будет енерировать только часть пути /products/1, а для ссылок или указания форм вроде link_to "My lovely product", produc_path (product) больше ничего и не нужно.
Путаница возникает из-за того, что снисходительность браузеров зачастую делает эти два метода взаимозаменяемыми. Перенаправление redirect_to можно производить с product_path, и такой вариант, скорее всего, сработает, но с точки зрения спецификации он будет считаться неправильным. Точно так ж е можно при ссылке link_to использовать product_url, но тогда ваш HTML будет засорен ненужными символами, что также нельзя признать удачным вариантом.
Здесь проиллюстрировано одно важное правило: модель для таблицы, имеющей
внешний ключ (англ. foreign key), всегда содержит объявление belongs_to.
В Active Record родительский объект (тот самый, который логически содержит коллекцию дочерних объектов) использует для объявления своих связей с дочерней таблицей has_many, а для дочерней таблицы, чтобы указать на ее родителя, используется объявление belongs_to.
Связи многие ко многим являются симметричными, обе связываемые таблицы объявляют свою связь друг с другом, используя has_and_belongs_to_many.
Rails реализует связи многие ко многим, используя промежуточную объединительную таблицу. В ней содержатся пары внешних ключей, связывающие две заданные таблицы. Active Record предполагает, что имя этой объединительной таблицы является объединением имен двух заданных таблиц, следующих в алфавитном порядке.
product_path против product_url
Поначалу кажется, что будет довольно сложно узнать, когда при необходимости создать ссылку или перенаправление по заданному маршруту нужно использовать метод product_path, а когда метод product_url. Но на самом деле все довольно просто.
При использовании метода product_url вы получите полную начинку с протоколом и доменным именем, наподобие http://example.com /products/l. Его следует использовать, если осуществляется перенаправление redirect_to, поскольку спецификация HTTP при осуществлении перенаправлений с кодом 302 и им подобных требует указывать URL-адрес полностью.
Полный URL-адрес нужен также при перенаправлении с одного домена на другой, например, product_url(domain: "example2.com", product: product).
Во всех остальных случаях можно с успехом использовать product_path. Этот метод будет енерировать только часть пути /products/1, а для ссылок или указания форм вроде link_to "My lovely product", produc_path (product) больше ничего и не нужно.
Путаница возникает из-за того, что снисходительность браузеров зачастую делает эти два метода взаимозаменяемыми. Перенаправление redirect_to можно производить с product_path, и такой вариант, скорее всего, сработает, но с точки зрения спецификации он будет считаться неправильным. Точно так ж е можно при ссылке link_to использовать product_url, но тогда ваш HTML будет засорен ненужными символами, что также нельзя признать удачным вариантом.
вторник, 30 октября 2012 г.
Установка Heroku и развертывание приложения на сервере
На Heroku желательно устанавливать приложения, использующие базу данных Postgress, но подойдет и MySQL. База данных sqlite3 работать не будет.
После создания учетной записи Heroku, установите гем Heroku, набрав в консоли:
gem install heroku
Как и с GitHub, при использовании Heroku вы должны будете создать ключи SSH если у вас их еще нет, а затем сообщить Heroku свой открытый ключ так, чтобы можно было использовать Git, для отправки репозитория примера приложения на их серверы:
heroku keys:add
Наконец, используйте heroku команду, чтобы создать для примера приложения место на серверах Heroku:
heroku create
Да, вот и все. Нeroku команда создает новый субдомен только для нашего приложения, доступный для немедленного просмотра. Там еще ничего нет, так что давайте займемся развертыванием.
Первый шаг для развертывания на Heroku это отправка приложения на Heroku с использованием Git:
git push heroku master
Чтобы увидеть ваше свежеразвернутое приложение, нужно посетить адрес, который вы видели, когда выполняли, heroku create. Вы также можете использовать аргумент heroku команды который автоматически откроет ваш браузер с правильным адресом:
heroku open
Есть тонны команд Heroku. Уделю минуту, чтобы показать только одну из них, переименовав приложение следующим образом:
heroku rename railstutorial
После создания учетной записи Heroku, установите гем Heroku, набрав в консоли:
gem install heroku
Как и с GitHub, при использовании Heroku вы должны будете создать ключи SSH если у вас их еще нет, а затем сообщить Heroku свой открытый ключ так, чтобы можно было использовать Git, для отправки репозитория примера приложения на их серверы:
heroku keys:add
Наконец, используйте heroku команду, чтобы создать для примера приложения место на серверах Heroku:
heroku create
Да, вот и все. Нeroku команда создает новый субдомен только для нашего приложения, доступный для немедленного просмотра. Там еще ничего нет, так что давайте займемся развертыванием.
Первый шаг для развертывания на Heroku это отправка приложения на Heroku с использованием Git:
git push heroku master
Чтобы увидеть ваше свежеразвернутое приложение, нужно посетить адрес, который вы видели, когда выполняли, heroku create. Вы также можете использовать аргумент heroku команды который автоматически откроет ваш браузер с правильным адресом:
heroku open
Есть тонны команд Heroku. Уделю минуту, чтобы показать только одну из них, переименовав приложение следующим образом:
heroku rename railstutorial
Установка Git на Windows и его использование совместно с GitHub
Установка Git в Windows очень проста.
У проекта msysGit процедура установки - одна из самых простых. Просто скачайте файл exe-инсталлятора со страницы Google Code и запустите его:
code.google.com/p/msysgit
После установки у вас будет как консольная версия (включающая SSH-клиент, который пригодится позднее), так и стандартная графическая.
После установки Git следует выполнить ряд разовых настроек. Это системные настройки, что означает, что их необходимо сделать лишь единожды.
Наберите в консоли:
git config --global user.name "Your Name"
git config --global user.email youremail@example.com
Теперь мы подошли к нескольким шагам, которые будут необходимы каждый раз, когда вы создаете новый репозиторий. Сначала перейдите в корневой каталог вашего приложения и инициализируйте новый репозиторий, набрав в консоли:
git init
Следующий шаг добавит файлы проекта в репозиторий. Есть незначительная сложность: Git по умолчанию отслеживает изменения всех файлов, но есть несколько файлов, которые мы не хотели бы отслеживать. Например, Rails создает log файлы, для записи поведения приложения; эти файлы часто изменяются, и мы не хотим, чтобы наша система управления версиями постоянно обновляла их. у Git есть простой механизм, чтобы игнорировать такие файлы: просто включите файл, названный .gitignore в корневой каталог Rails с небольшим количеством правил, которые говорят Git какие файлы следует игнорировать.
Дефолтный .gitignore создаваемый командой rails.
.bundle
db/*.sqlite3
log/*.log
tmp/**/*
Листинг заставляет Git игнорировать такие файлы как log файлы, Rails временные (tmp) файлы, и базы данных SQLite. (Например, чтобы игнорировать log файлы, которые живут в log/ каталоге, мы используем log/*.log, чтобы игнорировать все файлы, заканчивающиеся на .log.) Большинство этих игнорируемых файлов изменяются часто и автоматически, так что включать их в управление версиями неудобно; кроме того, при совместной разработке они могут вызвать конфликты.
Наконец, мы добавим файлы вашего проекта к Git, а затем закоммитим результаты. Можно добавить все файлы (кроме тех, которые соответствуют правилам игнорирования в .gitignore) следующим образом, набрав в консоли:
git add .
Здесь точка ‘.’ представляет текущий каталог, и Git достаточно умен, чтобы добавить файлы рекурсивно, таким образом, это автоматически включает все подкаталоги. Эта команда добавляет файлы проекта в зону ожидания, которая содержит незавершенные изменения вашего проекта; можно видеть, какие файлы находятся в зоне ожидания, используя команду status:
git status
Для того, чтобы сказать Git, что вы хотите сохранить изменения, используйте команду commit:
git commit -m "Moy perviy commit"
Метка -m позволяет вам добавлять сообщение для фиксации.
Важно отметить, что коммиты Git локальны, и записываются только на машине, на которой происходят коммиты. Что отличает его от популярной open-source системы управления версиями под названием Subversion, в которой коммит обязательно приводит к изменениям в удаленном репозитарии. Git делит коммит в стиле Subversion на два логических куска: локальная запись изменений (git commit) и отправка изменений в удаленный репозиторий (git push)..
Между прочим, вы можете увидеть список своих сообщений о коммитах, используя команду log:
git log
Чтобы выйти из log git, нажмите q.
Мы можем легко отменить изменения в файлах, получив из Git предыдущую фиксацию командой checkout (и флагом -f, чтобы инициировать перезапись текущих изменений):
git checkout -f
Теперь, когда вы поместили свой проект в систему управления версиями Git, пора отправить ваш код на GitHub - веб-сервис для хостинга исходного кода проектов и их совместной разработки основанный на системе контроля версий Git.
После регистрации на GitHub (github.com) вы увидите следующую страницу:
Щелкните создать репозиторий и заполните форму.
После подтверждения формы отправьте свое первое приложение, набрав в консоли следующие команды:
git remote add origin git@github.com:<username>/your_app.git
git push origin master
Эти команды говорят Git, что вы хотите добавить GitHub как начальный адрес для вашей основной (master) ветки, а затем отправить ваш репозиторий на GitHub. Конечно, следует заменить <username> вашим фактическим именем пользователя. Например, команда, которую я запустил для railstutorial пользователя, была
git remote add origin git@github.com:railstutorial/first_app.git
Результатом является страница на GitHub для репозитория первого приложения (first application), с браузером файлов, полной историей коммитов.
Git невероятно хорош в создании веток, которые фактически являются копиями репозитория, где мы можем произвести (возможно экспериментальные) изменения, не модифицируя родительские файлы. В большинстве случаев родительский репозиторий – master ветка, и мы можем создать новую рабочую ветку используя checkout с флагом -b:
git checkout -b izmenenie-README
git branch
Здесь вторая команда, git branch, только перечисляет все локальные ветки, а звездочка * указывает, какая ветка в настоящий момент включена.
Отметьте, что git checkout -b izmenenie-README одновременно и создает новую ветку и переключает на нее, на что указывает звездочка перед izmenenie-README веткой.
После внесения изменений в файлы мы могли бы использовать git add . , но Git предусматривает флаг -a как сокращение для (очень частого) случая фиксации всех изменений к существующим файлам (или файлов, созданных с использованием git mv, которые не считаются новыми для Git):
git commit -a -m "Uluchshenie fayla README"
Будьте осторожны с использованием флага -a; если вы добавили какие-либо новые файлы в проект после последней фиксации, вы должны сообщить Git о них используя сначала git add.Будьте осторожны с использованием флага -a; если вы добавили какие-либо новые файлы в проект после последней фиксации, вы должны сообщить Git о них используя сначала git add.
Теперь, когда мы закончили производить наши изменения, мы готовы объединить результаты с нашей master веткой:
git checkout master
git merge izmenenie-README
После того, как вы объединили изменения, вы можете почистить свои ветки, стерев тему ветки, используя git branch -d, если вы хотите, сделайте это так:
git branch -d izmenenie-README
Этот шаг не является обязательным, и, фактически, это довольно распространено - оставлять рабочие ветки нетронутыми. Это позволяет переключаться между рабочей и master ветками, объединяя изменения каждый раз, когда вы достигаете естественной точки остановки.
Как упомянуто выше, вы также можете отказаться от изменений, относящихся к рабочей ветке, в таком случае используйте git branch -D:
# Только для иллюстрации; не делайте этого, если планируете в дальнейшем пользоваться веткой
git checkout -b topic-branch
git add .
git commit -a -m "Screwed up"
git checkout master
git branch -D topic-branch
В отличие от флага -d, флаг -D сотрет ветку даже если мы не объединили изменения.
Теперь, когда мы обновили README, мы можем отправить изменения в GitHub чтобы увидеть результат. Так как мы уже сделали одну отправку, на большинстве систем, мы теперь можем опустить origin master, и просто выполнить git push:
git push
На некоторых системах, эта команда выдает ошибку:
git push
fatal: The current branch master is not tracking anything.
В этом случае необходимо выполнить команду:
git push origin master
У проекта msysGit процедура установки - одна из самых простых. Просто скачайте файл exe-инсталлятора со страницы Google Code и запустите его:
code.google.com/p/msysgit
После установки у вас будет как консольная версия (включающая SSH-клиент, который пригодится позднее), так и стандартная графическая.
После установки Git следует выполнить ряд разовых настроек. Это системные настройки, что означает, что их необходимо сделать лишь единожды.
Наберите в консоли:
git config --global user.name "Your Name"
git config --global user.email youremail@example.com
Теперь мы подошли к нескольким шагам, которые будут необходимы каждый раз, когда вы создаете новый репозиторий. Сначала перейдите в корневой каталог вашего приложения и инициализируйте новый репозиторий, набрав в консоли:
git init
Следующий шаг добавит файлы проекта в репозиторий. Есть незначительная сложность: Git по умолчанию отслеживает изменения всех файлов, но есть несколько файлов, которые мы не хотели бы отслеживать. Например, Rails создает log файлы, для записи поведения приложения; эти файлы часто изменяются, и мы не хотим, чтобы наша система управления версиями постоянно обновляла их. у Git есть простой механизм, чтобы игнорировать такие файлы: просто включите файл, названный .gitignore в корневой каталог Rails с небольшим количеством правил, которые говорят Git какие файлы следует игнорировать.
Дефолтный .gitignore создаваемый командой rails.
.bundle
db/*.sqlite3
log/*.log
tmp/**/*
Листинг заставляет Git игнорировать такие файлы как log файлы, Rails временные (tmp) файлы, и базы данных SQLite. (Например, чтобы игнорировать log файлы, которые живут в log/ каталоге, мы используем log/*.log, чтобы игнорировать все файлы, заканчивающиеся на .log.) Большинство этих игнорируемых файлов изменяются часто и автоматически, так что включать их в управление версиями неудобно; кроме того, при совместной разработке они могут вызвать конфликты.
Наконец, мы добавим файлы вашего проекта к Git, а затем закоммитим результаты. Можно добавить все файлы (кроме тех, которые соответствуют правилам игнорирования в .gitignore) следующим образом, набрав в консоли:
git add .
Здесь точка ‘.’ представляет текущий каталог, и Git достаточно умен, чтобы добавить файлы рекурсивно, таким образом, это автоматически включает все подкаталоги. Эта команда добавляет файлы проекта в зону ожидания, которая содержит незавершенные изменения вашего проекта; можно видеть, какие файлы находятся в зоне ожидания, используя команду status:
git status
Для того, чтобы сказать Git, что вы хотите сохранить изменения, используйте команду commit:
git commit -m "Moy perviy commit"
Метка -m позволяет вам добавлять сообщение для фиксации.
Важно отметить, что коммиты Git локальны, и записываются только на машине, на которой происходят коммиты. Что отличает его от популярной open-source системы управления версиями под названием Subversion, в которой коммит обязательно приводит к изменениям в удаленном репозитарии. Git делит коммит в стиле Subversion на два логических куска: локальная запись изменений (git commit) и отправка изменений в удаленный репозиторий (git push)..
Между прочим, вы можете увидеть список своих сообщений о коммитах, используя команду log:
git log
Чтобы выйти из log git, нажмите q.
Мы можем легко отменить изменения в файлах, получив из Git предыдущую фиксацию командой checkout (и флагом -f, чтобы инициировать перезапись текущих изменений):
git checkout -f
Теперь, когда вы поместили свой проект в систему управления версиями Git, пора отправить ваш код на GitHub - веб-сервис для хостинга исходного кода проектов и их совместной разработки основанный на системе контроля версий Git.
После регистрации на GitHub (github.com) вы увидите следующую страницу:
Щелкните создать репозиторий и заполните форму.
После подтверждения формы отправьте свое первое приложение, набрав в консоли следующие команды:
git remote add origin git@github.com:<username>/your_app.git
git push origin master
Эти команды говорят Git, что вы хотите добавить GitHub как начальный адрес для вашей основной (master) ветки, а затем отправить ваш репозиторий на GitHub. Конечно, следует заменить <username> вашим фактическим именем пользователя. Например, команда, которую я запустил для railstutorial пользователя, была
git remote add origin git@github.com:railstutorial/first_app.git
Результатом является страница на GitHub для репозитория первого приложения (first application), с браузером файлов, полной историей коммитов.
Git невероятно хорош в создании веток, которые фактически являются копиями репозитория, где мы можем произвести (возможно экспериментальные) изменения, не модифицируя родительские файлы. В большинстве случаев родительский репозиторий – master ветка, и мы можем создать новую рабочую ветку используя checkout с флагом -b:
git checkout -b izmenenie-README
git branch
Здесь вторая команда, git branch, только перечисляет все локальные ветки, а звездочка * указывает, какая ветка в настоящий момент включена.
Отметьте, что git checkout -b izmenenie-README одновременно и создает новую ветку и переключает на нее, на что указывает звездочка перед izmenenie-README веткой.
После внесения изменений в файлы мы могли бы использовать git add . , но Git предусматривает флаг -a как сокращение для (очень частого) случая фиксации всех изменений к существующим файлам (или файлов, созданных с использованием git mv, которые не считаются новыми для Git):
git commit -a -m "Uluchshenie fayla README"
Будьте осторожны с использованием флага -a; если вы добавили какие-либо новые файлы в проект после последней фиксации, вы должны сообщить Git о них используя сначала git add.Будьте осторожны с использованием флага -a; если вы добавили какие-либо новые файлы в проект после последней фиксации, вы должны сообщить Git о них используя сначала git add.
Теперь, когда мы закончили производить наши изменения, мы готовы объединить результаты с нашей master веткой:
git checkout master
git merge izmenenie-README
После того, как вы объединили изменения, вы можете почистить свои ветки, стерев тему ветки, используя git branch -d, если вы хотите, сделайте это так:
git branch -d izmenenie-README
Этот шаг не является обязательным, и, фактически, это довольно распространено - оставлять рабочие ветки нетронутыми. Это позволяет переключаться между рабочей и master ветками, объединяя изменения каждый раз, когда вы достигаете естественной точки остановки.
Как упомянуто выше, вы также можете отказаться от изменений, относящихся к рабочей ветке, в таком случае используйте git branch -D:
# Только для иллюстрации; не делайте этого, если планируете в дальнейшем пользоваться веткой
git checkout -b topic-branch
git add .
git commit -a -m "Screwed up"
git checkout master
git branch -D topic-branch
В отличие от флага -d, флаг -D сотрет ветку даже если мы не объединили изменения.
Теперь, когда мы обновили README, мы можем отправить изменения в GitHub чтобы увидеть результат. Так как мы уже сделали одну отправку, на большинстве систем, мы теперь можем опустить origin master, и просто выполнить git push:
git push
На некоторых системах, эта команда выдает ошибку:
git push
fatal: The current branch master is not tracking anything.
В этом случае необходимо выполнить команду:
git push origin master
пятница, 12 октября 2012 г.
Ruby on Rails Tutorial 12
Глава 12 Слежение за сообщениями пользователей
В этой главе мы завершим ядро примера приложения, добавив социальный слой, что позволит пользователям читать (и не читать) сообщения других пользователей 27, в результате чего на главной странице каждого пользователя будет отображаться список микросообщений тех пользователей, сообщения которых он читает. Мы также сделаем представления для отображения читающих и читаемых пользователей. Мы узнаем, как смоделировать слежение за сообщениями пользователей в Разделе 12.1, а затем сделаем веб-интерфейс в Разделе 12.2 (включая введение в Ajax). Наконец, мы закончим, разработав полнофункциональную ленту сообщений в Разделе 12.3.Эта последняя глава содержит несколько из наиболее сложных материалов учебника, в том числе, сложные модели данных и несколько Ruby / SQL хитростей для создания потока сообщений. С помощью этих примеров вы увидите как Rails может обрабатывать даже весьма сложные модели данных, что должно вам пригодиться, так как вы двигаетесь к разработке собственных приложений с их собственными требованиями. Чтобы помочь с переходом от учебника к самостоятельной разработке, Раздел 12.4 содержит рекомендуемые расширения к ядру примера приложения, а также ссылки на более продвинутые ресурсы.
Как обычно, Git пользователи должны создать новую тему ветки:
$ git checkout -b following-users
12.1 Модель Relationship
Наш первый шаг в реализации слежения за сообщениями пользователей, заключается в построении модели данных, которая не так проста, как кажется. Naïvely, кажется, чтоhas_many
отношение должно сработать: пользователь has_many
(имеет_много) читаемых и has_many
(имеет_много) читателей. Как мы увидим, в этом подходе есть проблема, и мы узнаем как ее исправить используя has_many :through
.
Вполне вероятно, что многие идеи этого раздела окажутся непонятыми с
первого раза, и может потребоваться некоторое время для осознания
довольно сложной модели данных. Если вы обнаружите что запутались,
попробуйте пройти главу до конца, а затем прочитать этот раздел еще раз,
чтобы прояснить для себя некоторые вещи.12.1.1 Проблема с моделью данных (и ее решение)
В качестве первого шага на пути построения модели данных для слежения за сообщениями пользователей, давайте рассмотрим следующий типичный случай. Возьмем, в качестве примера, пользователя, который следит за сообщениями второго пользователя: мы могли бы сказать, что, например, Кальвин читает сообщения Гоббса, и Гоббс читается Кальвином, таким образом, Кальвин является читателем, а Гоббс является читаемым. При использовании дефолтной Rails’ плюрализации, множество таких читаемых пользователей называлось бы followeds, но это безграмотно и неуклюже; вместо этого мы перепишем умолчание и назовем их читаемые, иuser.following
будет содержать массив пользователей, за сообщениями которых следит
текущий пользователь. Аналогично, множество пользователей, читающих
данного пользователя это читатели пользователя, и user.followers
будет массивом таких пользователей.Это предполагает моделирование читаемых пользователей как на Рис. 12.6, с
following
таблицей и has_many
ассоциацией. Поскольку user.following
должно быть массивом пользователей, каждая строка таблицы following
должна быть пользователем, идентифицируемым с помощью followed_id
, совместно с follower_id
для установления ассоциации.2
Кроме того, так как каждая строка является пользователем, мы должны
были бы включить другие атрибуты пользователя, включая имя, пароль и
т.д.users
. Еще хуже то, что для моделирования читателей пользователя нам потребуется отдельная followers
таблица. Наконец, эта модель данных кошмарно неудобна в эксплуатации,
так как каждый раз, при изменении пользователем (скажем) своего имени,
нам пришлось бы обновлять запись пользователя не только в users
таблице, но также каждую строку, содержащую этого пользователя
в обоих following
и followers
таблицах.Проблема здесь в том, что нам не хватает лежащей в основе абстракции (an underlying abstraction (??)). Один из способов найти правильную абстракцию, это рассмотреть, как мы могли бы реализовать following в веб-приложении. Вспомним из Раздела 6.3.3, что REST архитектура включает в себя ресурсы которые создаются и уничтожаются. Это приводит нас к двум вопросам: Что создается, когда пользователь начинает читать сообщения другого пользователя? Что уничтожается, когда пользователь прекращает следить за сообщениями другого пользователя?
Поразмыслив, мы видим, что в этих случаях приложение должно создать либо разрушить взаимоотношение (или связь3) между двумя пользователями. Затем пользователь
has_many :relationships
(имеет_много :взаимоотношений), и имеет много following
(или followers
) через эти взаимоотношения. Действительно, Рис. 12.6 уже содержит бОльшую часть реализации: поскольку каждый читаемый пользователь уникально идентифицирован посредством followed_id
, мы можем преобразовать following
в таблицу relationships
, опустив информацию о пользователе, и использовать followed_id
для получения читаемых пользователей из users
таблицы. Кроме того, приняв во внимание обратные взаимоотношения, мы могли бы использовать follower_id
столбец для извлечения массива читателей пользователя.Чтобы создать
following
массив пользователей, мы могли бы вытянуть массив атрибутов followed_id
,
а затем найти пользователя для каждого из них. Однако, как и следовало
ожидать, в Rails есть более удобный способ для этой процедуры;
соответствующая техника известна как has_many :through
(имеет_много :через).4 Как мы увидим в Разделе 12.1.4, Rails позволяет нам сказать, что пользователь следит за сообщениями многих пользователей через взаимоотношения, используя краткий кодhas_many :following, :through => :relationships, :source => "followed_id"
user.following
массивом читаемых пользователей. Схема модели данных представлена на Рис. 12.7.$ rails generate model Relationship follower_id:integer followed_id:integer
follower_id
и по followed_id
, мы должны добавить индекс на каждой колонке для повышения эффективности поиска, как показано в Листинге 12.1.
Листинг 12.1. Добавление индексов для
relationships
таблицы. db/migrate/<timestamp>_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration
def self.up
create_table :relationships do |t|
t.integer :follower_id
t.integer :followed_id
t.timestamps
end
add_index :relationships, :follower_id
add_index :relationships, :followed_id
add_index :relationships, [:follower_id, :followed_id], :unique => true
end
def self.down
drop_table :relationships
end
end
follower_id
, followed_id
), так что пользователь не может следить за сообщениями другого пользователя более одного раза:add_index :relationships, [:follower_id, :followed_id], :unique => true
Для создания таблицы
relationships
, мы мигрируем базу данных и подготавливаем тестовую бд, как обычно:$ bundle exec rake db:migrate
$ bundle exec rake db:test:prepare
followed_id
должен быть доступным, поскольку пользователи будут создавать взаимоотношения через веб, но атрибут follower_id
должен быть недоступным; в противном случае злоумышленник может
вынудить других пользователей следить за его сообщениями. Результат
представлен в Листинге 12.2.
Листинг 12.2. Открытие доступа к
followed_id
атрибуту модели Relationship (но не к follower_id
). app/models/relationship.rb
class Relationship < ActiveRecord::Base
attr_accessible :followed_id
end
12.1.2 Ассоциации пользователь/взаимоотношение
Прежде чем приступить к реализации читателей и читаемых, нам вначале необходимо установить ассоциацию между пользователями и взаимоотношениями. Пользовательhas_many
(имеет_много) взаимоотношений, и, так как взаимоотношения включают двух пользователей — взаимоотношение belongs_to
(принадлежит_к) читающим и читаемым пользователям.Как и с микросообщениями в Разделе 11.1.2, мы будем создавать новые взаимоотношения используя ассоциацию, с помощью такого кода
user.relationships.create(:followed_id => ...)
@relationships
(используется ниже) и проверяет, что она может быть сохранена используя save!
. Как и create!
, метод save!
вызывает исключение в случае неудачного сохранения; сравните это с использованием create!
в Листинге 11.4.
Листинг 12.3. Тестирование создания Relationship с
save!
. spec/models/relationship_spec.rb
require 'spec_helper'
describe Relationship do
before(:each) do
@follower = Factory(:user)
@followed = Factory(:user, :email => Factory.next(:email))
@relationship = @follower.relationships.build(:followed_id => @followed.id)
end
it "should create a new instance given valid attributes" do
@relationship.save!
end
end
relationships
атрибут, как показано в Листинге 12.4.
Листинг 12.4. Тестирование на
user.relationships
атрибут. spec/models/user_spec.rb
describe User do
.
.
.
describe "relationships" do
before(:each) do
@user = User.create!(@attr)
@followed = Factory(:user)
end
it "should have a relationships method" do
@user.should respond_to(:relationships)
end
end
end
class Micropost < ActiveRecord::Base
belongs_to :user
.
.
.
end
class User < ActiveRecord::Base
has_many :microposts
.
.
.
end
microposts
есть атрибут user_id
для идентификации пользователя (Раздел 11.1.1). Id используемый таким способом для связи двух таблиц базы данных, известен как внешний ключ, и когда внешним ключом для объекта модели User является user_id
, Rails может вывести ассоциацию автоматически: по умолчанию, Rails ожидает внешний ключ в форме <class>_id
, где <class>
является строчной версией имени класса.5 В данном случае, несмотря на то, что мы по прежнему имеем дело с пользователями, они теперь отождествляются с внешним ключом follower_id
, поэтому мы должны сообщить об этом Rails, как показано в Листинге 12.5.6
Листинг 12.5. Реализация
has_many
ассоциации пользователь/взаимоотношение. app/models/user.rb
class User < ActiveRecord::Base
.
.
.
has_many :microposts, :dependent => :destroy
has_many :relationships, :foreign_key => "follower_id",
:dependent => :destroy
.
.
.
end
:dependent => :destroy
к ассоциации; написание теста на это останется в качестве упражнения (Раздел 12.5).) На данный момент, тест ассоциации из Листинга 12.3 и Листинга 12.4 должен пройти.Как и у модели Micropost, у Relationship модели есть
belongs_to
взаимоотношения с пользователями; в данном случае, объект взаимоотношение принадлежит к обоим follower
и followed
пользователям, что мы и тестируем в Листинге 12.6.
Листинг 12.6. Тестирование
belongs_to
ассоциации пользователь/взаимоотношения. spec/models/relationship_spec.rb
describe Relationship do
.
.
.
describe "follow methods" do
before(:each) do
@relationship.save
end
it "should have a follower attribute" do
@relationship.should respond_to(:follower)
end
it "should have the right follower" do
@relationship.follower.should == @follower
end
it "should have a followed attribute" do
@relationship.should respond_to(:followed)
end
it "should have the right followed user" do
@relationship.followed.should == @followed
end
end
end
belongs_to
взаимоотношения как обычно. Rails выводит названия внешних ключей из соответствующих символов (т.e., follower_id
из :follower
, и followed_id
из :followed
), но, так как нет ни Followed ни Follower моделей, мы должны снабдить их именем класса User
. Результат показан в Листинге 12.7.
Листинг 12.7. Добавление
belongs_to
ассоциаций к модели Relationship. app/models/relationship.rb
class Relationship < ActiveRecord::Base
attr_accessible :followed_id
belongs_to :follower, :class_name => "User"
belongs_to :followed, :class_name => "User"
end
followed
на самом деле не потребуется до Раздела 12.1.5, но параллельность структуры читатели/читаемые лучше видна при одновременной реализации.12.1.3 Валидации
Прежде чем двигаться дальше, мы добавим пару валидаций модели Relationship для комплектности. Тесты (Листинг 12.8) и код приложения (Листинг 12.9) просты.
Листинг 12.8. Тестирование валидаций модели Relationship.
spec/models/relationship_spec.rb
describe Relationship do
.
.
.
describe "validations" do
it "should require a follower_id" do
@relationship.follower_id = nil
@relationship.should_not be_valid
end
it "should require a followed_id" do
@relationship.followed_id = nil
@relationship.should_not be_valid
end
end
end
Листинг 12.9. Добавление валидаций модели Relationship.
app/models/relationship.rb
class Relationship < ActiveRecord::Base
attr_accessible :followed_id
belongs_to :follower, :class_name => "User"
belongs_to :followed, :class_name => "User"
validates :follower_id, :presence => true
validates :followed_id, :presence => true
end
12.1.4 Читаемые пользователи
Теперь мы переходим к сердцу ассоциаций Relationship:following
и followers
. Мы начнем с following
, как показано в Листинге 12.10.
Листинг 12.10. Тест для атрибута
user.following
. spec/models/user_spec.rb
describe User do
.
.
.
describe "relationships" do
before(:each) do
@user = User.create!(@attr)
@followed = Factory(:user)
end
it "should have a relationships method" do
@user.should respond_to(:relationships)
end
it "should have a following method" do
@user.should respond_to(:following)
end
end
end
has_many :through
: пользователь имеет много читаемых (пользователей) через взаимоотношения, как показано на Рис. 12.7. По умолчанию, в ассоциации has_many :through
Rails ищет внешний ключ, соответствующий ассоциации в единственном числе; другими словами, кодhas_many :followeds, :through => :relationships
followed_id
в таблице relationships
. Но, как отмечалось в Разделе 12.1.1, user.followeds
это довольно неуклюже; гораздо более естественным будет использование “following” в качестве множественного числа для “followed”, и написание user.following
для массива читаемых пользователей. Естественно, Rails позволяет переопределить умолчание, в данном случае с помощью :source
параметра (Листинг 12.11), что явно говорит Rails, что источником массива following
является множество followed
id.
Листинг 12.11. Добавление к модели User ассоциации
following
с has_many :through
. app/models/user.rb
class User < ActiveRecord::Base
.
.
.
has_many :microposts, :dependent => :destroy
has_many :relationships, :foreign_key => "follower_id",
:dependent => :destroy
has_many :following, :through => :relationships, :source => :followed
.
.
.
end
follow!
и мы сможем написать user.follow!(other_user)
.7 Мы также добавим связанный булев метод following?
для того чтобы протестировать, читает ли пользователь сообщения других пользователей.8 Тесты в Листинге 12.12 показывают как мы планируем использовать эти методы на практике.
Листинг 12.12. Тесты для некоторых служебных методов “following”.
spec/models/user_spec.rb
describe User do
.
.
.
describe "relationships" do
.
.
.
it "should have a following? method" do
@user.should respond_to(:following?)
end
it "should have a follow! method" do
@user.should respond_to(:follow!)
end
it "should follow another user" do
@user.follow!(@followed)
@user.should be_following(@followed)
end
it "should include the followed user in the following array" do
@user.follow!(@followed)
@user.following.should include(@followed)
end
end
end
include?
виденный в Листинге 11.31 на should include
, преобразовав@user.following.include?(@followed).should be_true
@user.following.should include(@followed)
include
уже является ключевым словом Ruby (используется для включения модуля, как мы видели, например, в Листинге 9.11), в этом контексте RSpec правильно угадывает, что мы хотим протестировать включение массива.В коде приложения, метод
following?
принимает пользователя, называемого followed
, и проверяет, существует ли он в базе данных; метод follow!
вызывает create!
через relationships
ассоциацию для создания взаимоотношения с читаемым. Результаты представлены в Листинге 12.13.9
Листинг 12.13. Служебные методы
following?
и follow!
. app/models/user.rb
class User < ActiveRecord::Base
.
.
.
def self.authenticate_with_salt(id, stored_salt)
.
.
.
end
def following?(followed)
relationships.find_by_followed_id(followed)
end
def follow!(followed)
relationships.create!(:followed_id => followed.id)
end
.
.
.
end
relationships.create!(...)
self.relationships.create!(...)
self
в данном случае дело вкуса.Конечно, пользователи должны иметь возможность прекратить слежение за сообщениями других пользователей, что приводит нас к немного предсказуемому методу
unfollow!
, как показано в Листинге 12.14.10
Листинг 12.14. Тест для прекращения слежения за сообщениями пользователя.
spec/models/user_spec.rb
describe User do
.
.
.
describe "relationships" do
.
.
.
it "should have an unfollow! method" do
@followed.should respond_to(:unfollow!)
end
it "should unfollow a user" do
@user.follow!(@followed)
@user.unfollow!(@followed)
@user.should_not be_following(@followed)
end
end
end
unfollow!
прост: нужно просто найти взаимоотношение по followed id и уничтожить его (Листинг 12.15).11
Листинг 12.15. Прекращение слежения за сообщениями пользователя посредством уничтожения взаимоотношения.
app/models/user.rb
class User < ActiveRecord::Base
.
.
.
def following?(followed)
relationships.find_by_followed_id(followed)
end
def follow!(followed)
relationships.create!(:followed_id => followed.id)
end
def unfollow!(followed)
relationships.find_by_followed_id(followed).destroy
end
.
.
.
end
12.1.5 Читатели пользователя
Последняя часть головоломки отношений это добавление методаuser.followers
, сопутствующего user.following
. Вы могли заметить в Рис. 12.7 что все сведения, необходимые для извлечения массива читателей уже присутствуют в таблице relationships
. Действительно, техника та же, что и для читаемых пользователей, но с реверсированием ролей follower_id
и followed_id
. Это говорит о том, что, если бы мы смогли как-то организовать таблицу reverse_relationships
, поменяв местами эти два столбца (Рис. 12.9), то мы бы с легкостью реализовали user.followers
.
Листинг 12.16. Тестирование перевернутых взаимоотношений.
spec/models/user_spec.rb
describe User do
.
.
.
describe "relationships" do
.
.
.
it "should have a reverse_relationships method" do
@user.should respond_to(:reverse_relationships)
end
it "should have a followers method" do
@user.should respond_to(:followers)
end
it "should include the follower in the followers array" do
@user.follow!(@followed)
@followed.followers.should include(@user)
end
end
end
reverse_relationships
, передав followed_id
в качестве внешнего ключа. Иными словами, там где ассоциация relationships
использует внешний ключ follower_id
,has_many :relationships, :foreign_key => "follower_id"
reverse_relationships
использует followed_id
:has_many :reverse_relationships, :foreign_key => "followed_id"
followers
затем строится через реверсированные взаимоотношения, как показано в Листинге 12.17.
Листинг 12.17. Реализация
user.followers
использующая реверсированные взаимоотношения. app/models/user.rb
class User < ActiveRecord::Base
.
.
.
has_many :reverse_relationships, :foreign_key => "followed_id",
:class_name => "Relationship",
:dependent => :destroy
has_many :followers, :through => :reverse_relationships, :source => :follower
.
.
.
end
dependent :destroy
остается в качестве упражнения (Раздел 12.5).) Обратите внимание, что мы должны включить имя класса для этой ассоциации, т.e.,has_many :reverse_relationships, :foreign_key => "followed_id",
:class_name => "Relationship"
ReverseRelationship
.Стоит также отметить, что мы могли бы в этом случае пропустить
:source
, используя простоhas_many :followers, :through => :reverse_relationships
follower_id
в данном случае. Я сохранил ключ :source
для того чтобы подчеркнуть параллельность со структурой ассоциации has_many :following
, но вы можете пропустить его.С кодом в Листинге 12.17, ассоциации читаемые/читатели завершены, и все тесты должны пройти. Этот раздел предъявил довольно высокие требования к вашим навыкам моделирования данных, и это нормально, если для его усвоения потребуется некоторое время. Фактически, одним из самых лучших способов понять ассоциации является их использование в веб интерфейсе, как мы увидим в следующем разделе.
12.2 Веб-интерфейс для читаемых и читателей
Во введении к этой главе, мы сделали предварительный обзор страниц, необходимых для слежения за сообщениями пользователей. В этом разделе мы реализуем базовый интерфейс и читать/не читать функциональность, показанные в тех макетах. Мы также сделаем отдельные страницы для демонстрациии массивов читателей и читающих. В Разделе 12.3, мы завершим наш пример приложения, добавив ленту сообщений пользователя.12.2.1 Образцы данных
Как и в предыдущих главах, нам удобно будет использовать Rake задачу для заполнения базы данных образцами взаимоотношений. Это позволит нам заняться в первую очередь внешним видом страниц, временно отложив прикладную часть функциональности.Когда мы в последний раз видели заполнитель образцов данных в Листинге 11.20, он был немного суматошным. Поэтому мы начнем с определения отдельных методов для создания пользователей и микросообщений, а затем добавим образцы данных взаимоотношений используя новый метод
make_relationships
. Результаты показаны в Листинге 12.18.
Листинг 12.18. Добавление читатели/читающие взаимоотношений в образцы данных.
lib/tasks/sample_data.rake
namespace :db do
desc "Fill database with sample data"
task :populate => :environment do
Rake::Task['db:reset'].invoke
make_users
make_microposts
make_relationships
end
end
def make_users
admin = User.create!(:name => "Example User",
:email => "example@railstutorial.org",
:password => "foobar",
:password_confirmation => "foobar")
admin.toggle!(:admin)
99.times do |n|
name = Faker::Name.name
email = "example-#{n+1}@railstutorial.org"
password = "password"
User.create!(:name => name,
:email => email,
:password => password,
:password_confirmation => password)
end
end
def make_microposts
User.all(:limit => 6).each do |user|
50.times do
content = Faker::Lorem.sentence(5)
user.microposts.create!(:content => content)
end
end
end
def make_relationships
users = User.all
user = users.first
following = users[1..50]
followers = users[3..40]
following.each { |followed| user.follow!(followed) }
followers.each { |follower| follower.follow!(user) }
end
def make_relationships
users = User.all
user = users.first
following = users[1..50]
followers = users[3..40]
following.each { |followed| user.follow!(followed) }
followers.each { |follower| follower.follow!(user) }
end
Чтобы выполнить код Листинга 12.18, заполним базу данных как обычно:
$ bundle exec rake db:populate
12.2.2 Статистика и форма для слежения за сообщениями пользователя
Теперь, когда у наших образцов пользователей есть массивы читателей и читающих, нам нужно обновить главные страницы и страницы профилей, чтобы отразить это. Мы начнем с создания партиала для отображения статистики читаемых и читателей на странице профиля и на главной странице, как показано на макетах в Рис. 12.1 и Рис. 12.5. Результат будет отображать количиство читаемых и читающих пользователей, вместе со ссылками на специальные страницы для их просмотра. Затем мы добавим читать/не читать форму, и сделаем отдельные страницы для отображения читаемых и читающих пользователей.’#’
, но это было до того, как мы набрались опыта с маршрутами. Сейчас, несмотря на то, что мы отложили сами страницы до Раздела 12.2.3, мы сделаем маршруты сейчас, как показано в Листинге 12.19. Этот код использует метод :member
внутри блока resources
, с которым мы ранее не были знакомы, но посмотрим, сможете ли вы угадать, что он делает.
Листинг 12.19. Добавление действий
following
и followers
в контроллер Users. config/routes.rb
SampleApp::Application.routes.draw do
resources :users do
member do
get :following, :followers
end
end
.
.
.
end
get
для того чтобы организовать ответ URL на запросы GET (что требуется конвенцией REST для подобных страниц), и метод member
означает, что маршруты отвечают на URL, содержащие id пользователя. (Другой возможный метод, collection
, работает без id, так чтоresources :users do
collection do
get :tigers
end
end
HTTP запрос | URL | Действие | Именованный маршрут |
---|---|---|---|
GET | /users/1/following | following | following_user_path(1) |
GET | /users/1/followers | followers | followers_user_path(1) |
Листинг 12.20. Тестирование статистики читатели/читающие на Home странице.
spec/controllers/pages_controller_spec.rb
describe PagesController do
render_views
before(:each) do
@base_title = "Ruby on Rails Tutorial Sample App"
end
.
.
.
describe "GET 'home'" do
describe "when not signed in" do
before(:each) do
get :home
end
it "should be successful" do
response.should be_success
end
it "should have the right title" do
response.should have_selector("title",
:content => "#{@base_title} | Home")
end
end
describe "when signed in" do
before(:each) do
@user = test_sign_in(Factory(:user))
other_user = Factory(:user, :email => Factory.next(:email))
other_user.follow!(@user)
end
it "should have the right follower/following counts" do
get :home
response.should have_selector("a", :href => following_user_path(@user),
:content => "0 following")
response.should have_selector("a", :href => followers_user_path(@user),
:content => "1 follower")
end
end
end
end
response.should have_selector("a", :href => following_user_path(@user),
:content => "0 following")
response.should have_selector("a", :href => followers_user_path(@user),
:content => "1 follower")
Код приложения для партиала статистики это всего лишь таблица внутри div, как показано в Листинге 12.21.
Листинг 12.21. Партиал для отображения статистики.
app/views/shared/_stats.html.erb
<% @user ||= current_user %>
<div class="stats">
<table summary="User stats">
<tr>
<td>
<a href="<%= following_user_path(@user) %>">
<span id="following" class="stat">
<%= @user.following.count %> following
</span>
</a>
</td>
<td>
<a href="<%= followers_user_path(@user) %>">
<span id="followers" class="stat">
<%= pluralize(@user.followers.count, "follower") %>
</span>
</a>
</td>
</tr>
</table>
</div>
@user.following.count
@user.followers.count
@user.microposts.count
Поскольку мы будем включать статистику и в страницу профиля пользователя и в главную страницу, первая строка Листинга 12.21 выбирает правильное используя
<% @user ||= current_user %>
@user
не является nil
(как на странице профиля), но в противном случае (как на Home странице) это устанавливает @user
к текущему пользователю.Одна последняя деталь, которую стоит отметить, это наличие CSS id в некоторых элементах, как в
<span id="following" class="stat">
...
</span>
С готовым партиалом включить статистику в Home страницу проще простого, как показано в Листинге 12.22. (Это также приводит к прохождению тестов из Листинга 12.20.) результат представлен в Рис. 12.11.
Листинг 12.22. Добавление статистики к Home странице.
app/views/pages/home.html.erb
<% if signed_in? %>
.
.
.
<%= render 'shared/user_info' %>
<%= render 'shared/stats' %>
</td>
</tr>
</table>
<% else %>
.
.
.
<% end %>
Листинг 12.23. Партиал для формы follow/unfollow.
app/views/users/_follow_form.html.erb
<% unless current_user?(@user) %>
<div id="follow_form">
<% if current_user.following?(@user) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
</div>
<% end %>
follow
и unfollow
партиалы, которым нужен новый файл маршрутов чьи правила для ресурса Relationships, следуют примеру ресурса Microposts (Листинг 11.21), как показано в Листинге 12.24.
Листинг 12.24. Добавление маршрутов для пользовательских взаимоотношений.
config/routes.rb
SampleApp::Application.routes.draw do
.
.
.
resources :sessions, :only => [:new, :create, :destroy]
resources :microposts, :only => [:create, :destroy]
resources :relationships, :only => [:create, :destroy]
.
.
.
end
Листинг 12.25. Форма для слежения за сообщениями пользователя.
app/views/users/_follow.html.erb
<%= form_for current_user.relationships.
build(:followed_id => @user.id) do |f| %>
<div><%= f.hidden_field :followed_id %></div>
<div class="actions"><%= f.submit "Follow" %></div>
<% end %>
Листинг 12.26. Форма для отписки от сообщений пользователя.
app/views/users/_unfollow.html.erb
<%= form_for current_user.relationships.find_by_followed_id(@user),
:html => { :method => :delete } do |f| %>
<div class="actions"><%= f.submit "Unfollow" %></div>
<% end %>
form_for
для манипуляций с объектом модели Relationship; основное отличие между ними заключается в том, что Листинг 12.25 строит новое взаимоотношение, тогда как Листинг 12.26 ищет существующее взаимоотношение. Естественно, первый отправляет POST запрос к контроллеру Relationships для create
взаимоотношения, в то время как последний отправляет DELETE запрос для destroy
взаимоотношения. (Мы напишем эти действия в Разделе 12.2.4.) Наконец, отметьте, что форма follow/unfollow не содержит никакого контента кроме кнопки, но нам все еще необходимо отправить followed_id
, чего мы можем добиться с помощью hidden_field
; который производит HTML формы<input id="relationship_followed_id" name="relationship[followed_id]"
type="hidden" value="3" />
Теперь мы можем включить форму чтения и статистику на страницу профиля пользователя простым рендерингом партиалов, как показано в Листинге 12.27. Профили с follow и unfollow кнопками, соответственно, представлены на Рис. 12.12 и Рис. 12.13.
Листинг 12.27. Добавление формы слежения за сообщениями пользователя и статистики на страницу профиля пользователя.
app/views/users/show.html.erb
<table class="profile" summary="Profile information">
<tr>
<td class="main">
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
<%= render 'follow_form' if signed_in? %>
.
.
.
</td>
<td class="sidebar round">
<strong>Name</strong> <%= @user.name %><br />
<strong>URL</strong> <%= link_to user_path(@user), @user %><br />
<strong>Microposts</strong> <%= @user.microposts.count %>
<%= render 'shared/stats' %>
</td>
</tr>
</table>
12.2.3 Страницы с читаемыми и читателями
Страницы для отображения читающих сообщения пользователя и читаемых им пользователей будут напоминать гибрид страницы профиля пользователя и страницы со списком пользователей (Раздел 10.3.1), с сайдбаром пользовательской информации (включая статистику слежения за сообщениями) и таблицу пользователей. Кроме того, мы включим a raster (??) пользовательских профильных изображений-ссылок в сайдбаре. Макет соответствующий этим требованиям представлен в Рис. 12.14 (читаемые) и Рис. 12.15 (читатели).
Листинг 12.28. Тесты для действий
following
и followers
. spec/controllers/users_controller_spec.rb
describe UsersController do
.
.
.
describe "follow pages" do
describe "when not signed in" do
it "should protect 'following'" do
get :following, :id => 1
response.should redirect_to(signin_path)
end
it "should protect 'followers'" do
get :followers, :id => 1
response.should redirect_to(signin_path)
end
end
describe "when signed in" do
before(:each) do
@user = test_sign_in(Factory(:user))
@other_user = Factory(:user, :email => Factory.next(:email))
@user.follow!(@other_user)
end
it "should show user following" do
get :following, :id => @user
response.should have_selector("a", :href => user_path(@other_user),
:content => @other_user.name)
end
it "should show user followers" do
get :followers, :id => @other_user
response.should have_selector("a", :href => user_path(@user),
:content => @user.name)
end
end
end
end
following
и followers
. Каждому действию нужно установить заголовок, найти пользователя, вытянуть @user.following
или @user.followers
(в пагинированной форме), а затем отренедерить страницу. Результат представлен в Листинге 12.29.
Листинг 12.29. Действия
following
и followers
. app/controllers/users_controller.rb
class UsersController < ApplicationController
before_filter :authenticate, :except => [:show, :new, :create]
.
.
.
def following
@title = "Following"
@user = User.find(params[:id])
@users = @user.following.paginate(:page => params[:page])
render 'show_follow'
end
def followers
@title = "Followers"
@user = User.find(params[:id])
@users = @user.followers.paginate(:page => params[:page])
render 'show_follow'
end
.
.
.
end
render
, в данном случае делая рендеринг представления, названного show_follow
,
которое мы должны создать. Причина создания общего представления в том,
что ERb является практически идентичным для обоих случаев, и Листинг 12.30 охватывает их.
Листинг 12.30. Представление
show_follow
используемое для рендеринга читаемых и читателей. app/views/users/show_follow.html.erb
<table summary="Information about following/followers">
<tr>
<td class="main">
<h1><%= @title %></h1>
<% unless @users.empty? %>
<ul class="users">
<%= render @users %>
</ul>
<%= will_paginate @users %>
<% end %>
</td>
<td class="sidebar round">
<strong>Name</strong> <%= @user.name %><br />
<strong>URL</strong> <%= link_to user_path(@user), @user %><br />
<strong>Microposts</strong> <%= @user.microposts.count %>
<%= render 'shared/stats' %>
<% unless @users.empty? %>
<% @users.each do |user| %>
<%= link_to gravatar_for(user, :size => 30), user %>
<% end %>
<% end %>
</td>
</tr>
</table>
:only
на :except
. До сих пор в этом учебнике мы использовали :only
для указания к каким действиям применяется фильтр; с добавлением новых
защищаемых действий баланс сместился и стало проще указать какие
действия не должны быть отфильтрованы. Мы сделли это с :except
опцией предфильтра authenticate
:before_filter :authenticate, :except => [:show, :new, :create]
show_followers
, following
и followers
действия все еще содержат много повторений. Кроме того, сам партиал show_followers
разделяет общие функции со страницей отбражающей пользователей. Раздел 12.5 включает упражнения для устранения этих источников дублированного кода.12.2.4 Стандартный способ реализации кнопки "читать" (follow)
Теперь когда наши представления в порядке, пришло время получить рабочие follow/unfollow кнопки. Поскольку чтение сообщений пользователя создает взаимоотношение, а отписка от сообщений пользователя разрушает взаимоотношение, это предполагает написаниеcreate
и destroy
действий для контроллера Relationships. Естественно, оба действия
должны быть защишены; для вошедших пользователей мы будем использовать follow!
и unfollow!
служебные методы, определенные в Разделе 12.1.4 для создания и разрушения соответствующих взаимоотношений. Эти требования приводят к тестам в Листинге 12.31.
Листинг 12.31. Тесты для действий контроллера Relationships.
spec/controllers/relationships_controller_spec.rb
require 'spec_helper'
describe RelationshipsController do
describe "access control" do
it "should require signin for create" do
post :create
response.should redirect_to(signin_path)
end
it "should require signin for destroy" do
delete :destroy, :id => 1
response.should redirect_to(signin_path)
end
end
describe "POST 'create'" do
before(:each) do
@user = test_sign_in(Factory(:user))
@followed = Factory(:user, :email => Factory.next(:email))
end
it "should create a relationship" do
lambda do
post :create, :relationship => { :followed_id => @followed }
response.should be_redirect
end.should change(Relationship, :count).by(1)
end
end
describe "DELETE 'destroy'" do
before(:each) do
@user = test_sign_in(Factory(:user))
@followed = Factory(:user, :email => Factory.next(:email))
@user.follow!(@followed)
@relationship = @user.relationships.find_by_followed_id(@followed)
end
it "should destroy a relationship" do
lambda do
delete :destroy, :id => @relationship
response.should be_redirect
end.should change(Relationship, :count).by(-1)
end
end
end
:relationship => { :followed_id => @followed }
<%= f.hidden_field :followed_id %>
Листинг 12.32. Контроллер Relationships.
app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
before_filter :authenticate
def create
@user = User.find(params[:relationship][:followed_id])
current_user.follow!(@user)
redirect_to @user
end
def destroy
@user = Relationship.find(params[:id]).followed
current_user.unfollow!(@user)
redirect_to @user
end
end
12.2.5 Реализация кнопки "читать" (follow) с Ajax
Хотя наша реализация слежения за сообщениями пользователей является законченной и в своем нынешнем виде, нам осталось совсем немного подправить ее прежде чем заняться лентой сообщений. Вы могли заметить в Разделе 12.2.4 что обаcreate
и destroy
действия в контроллере Relationships просто перенаправляют обратно
к исходному профилю. Другими словами, пользователь начинает на странице
профиля, подписывается на сообщения пользователя и немедленно
перенаправляется на исходную страницу. Резонно спросить - почему
пользователь вообще должен покидать эту страницу?Именно эту проблему решает Ajax, который позволяет веб страницам отправлять асинхронные запросы на сервер не покидая страницы.13 Поскольку практика добавления Ajax в веб формы является довольно распространенной, Rails делает реализацию Ajax легкой. Действительно, обновление партиалов формы follow/unfollow тривиально: просто заменим
form_for
form_for ..., :remote => true
Листинг 12.33. Форма для чтения сообщений пользователя использующая Ajax.
app/views/users/_follow.html.erb
<%= form_for current_user.relationships.build(:followed_id => @user.id),
:remote => true do |f| %>
<div><%= f.hidden_field :followed_id %></div>
<div class="actions"><%= f.submit "Follow" %></div>
<% end %>
Листинг 12.34. Форма для прекращения чтения сообщений пользователя использующая Ajax.
app/views/users/_unfollow.html.erb
<%= form_for current_user.relationships.find_by_followed_id(@user),
:html => { :method => :delete },
:remote => true do |f| %>
<div class="actions"><%= f.submit "Unfollow" %></div>
<% end %>
<form action="/relationships/117" class="edit_relationship" data-remote="true"
id="edit_relationship_117" method="post">
.
.
.
</form>
data-remote="true"
внутри
тега формы, что говорит Rails о том, что форма будет обрабатываться
JavaScript. Используя простое свойство HTML вместо вставки полного
JavaScript кода (как в предыдущих версиях Rails), Rails 3 следует
философии ненавязчивого JavaScript.После обновления формы нам нужно уговорить контроллер Relationships отвечать на Ajax запросы. Мы начнем с пары простых тестов. Тестирование Ajax является довольно сложным, и тщательное делание этого является большой темой со своими собственными правилами, но мы можем начать с кодом в Листинге 12.35. Это использует
xhr
метод (от “XmlHttpRequest”) для выдачи Ajax запроса; сравните с get
, post
, put
и delete
методами в предыдущих тестах. Затем мы проверяем что create
и destroy
действия делают правильные вещи когда вызываются Ajax запросом. (Для
написания более основательного набора тестов для насыщенных Ajax-ом
приложений, взгляните на Selenium и Watir.)
Листинг 12.35. Тесты для ответов контроллера Relationships на Ajax запросы.
spec/controllers/relationships_controller_spec.rb
describe RelationshipsController do
.
.
.
describe "POST 'create'" do
.
.
.
it "should create a relationship using Ajax" do
lambda do
xhr :post, :create, :relationship => { :followed_id => @followed }
response.should be_success
end.should change(Relationship, :count).by(1)
end
end
describe "DELETE 'destroy'" do
.
.
.
it "should destroy a relationship using Ajax" do
lambda do
xhr :delete, :destroy, :id => @relationship
response.should be_success
end.should change(Relationship, :count).by(-1)
end
end
end
create
и delete
действия для ответа на Ajax запросы которые он выдает чтобы ответить на обычные POST и DELETE HTTP запросы. Все что нам нужно сделать это ответить на обычный HTTP запрос с переадресацией (как в Разделе 12.2.4) и ответить на Ajax запрос с JavaScript.15 Код контроллера представлен в Листинге 12.36. (См. в Разделе 12.5 пример, показывающий более компактный способ выполнить то же самое.)
Листинг 12.36. Ответ на Ajax запросы в контроллере Relationships.
app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
before_filter :authenticate
def create
@user = User.find(params[:relationship][:followed_id])
current_user.follow!(@user)
respond_to do |format|
format.html { redirect_to @user }
format.js
end
end
def destroy
@user = Relationship.find(params[:id]).followed
current_user.unfollow!(@user)
respond_to do |format|
format.html { redirect_to @user }
format.js
end
end
end
respond_to
чтобы принять соответствующее действие в зависимости от вида запроса.16 Синтаксис может запутать и важно понимать, что вrespond_to do |format|
format.html { redirect_to @user }
format.js
end
В случае Ajax запроса, Rails автоматически вызывает JavaScript Embedded Ruby (
.js.erb
) файл с тем же именем что и действие, т.е., create.js.erb
или destroy.js.erb
.
Как вы можете догадаться, эти файлы позволяют смешивать JavaScript и
Embedded Ruby для выполнения действий на текущей странице. Именно эти
файлы нам нужны для создания и редактирования страницы профиля
пользователя после начала слежения за сообщениями пользователя или после
его прекращения.Внутри JS-ERb файла, Rails автоматически обеспечивает Prototype JavaScript хелперы для манипуляции страницей при помощи Document Object Model (DOM). Prototype обеспечивает большое количество методов для манипуляции DOM, но здесь нам понадобятся только два. Во первых мы должны знать о синтаксисе знака доллара, используемого Prototype для доступа к DOM элементу, основанному на его уникальном CSS id. Например, для манипуляции элементом
follow_form
, мы используем синтаксис$("follow_form")
div
который обертывает форму, а не сама форма.) Второй метод который нам потребуется это update
,
который обновляет HTML внутри соответствующего элемента содержимым
своего аргумента. Например, чтобы полностью заменить follow form на
строку "foobar"
, мы можем написать$("follow_form").update("foobar")
create.js.erb
файле для обновления в форме unfollow
партиала (это то, что должно быть видно после успешного following) и обновления количества читаемых. Результат представлен в Листинге 12.37.
Листинг 12.37. JavaScript Embedded Ruby для создания взаимоотношения при чтении сообщений другого пользователя.
app/views/relationships/create.js.erb
$("follow_form").update("<%= escape_javascript(render('users/unfollow')) %>")
$("followers").update('<%= "#{@user.followers.count} followers" %>')
destroy.js.erb
аналогичен (Листинг 12.38). Отметьте, что как и в Листинге 12.37, мы должны использовать escape_javascript
для маскирования результатов при вставке HTML.
Листинг 12.38. Ruby JavaScript (RJS) для уничтожения взаимоотношения при чтении сообщений другого пользователя.
app/views/relationships/destroy.js.erb
$("follow_form").update("<%= escape_javascript(render('users/follow')) %>")
$("followers").update('<%= "#{@user.followers.count} followers" %>')
Использование Ajax в Rails является большой и стремительно развивающейся темой, и здесь мы лишь слегка коснулись ее, но (как и с остальными материалами в этом учебнике) наш подход дает вам хорошую основу для изучения более продвинутых ресурсов. Особенно стоит отметить, что, в дополнение к Prototype, JavaScript фреймворк jQuery получил большое признание в Rails сообществе. Фактически, как обсуждается в Главе 13, в Rails 3.1 jQuery является дефолтным фреймворком. Реализация Ajax функций из этого раздела с использованием jQuery оставлена в качестве упражнения; см. Раздел 12.5 и особенно Раздел 13.1.4.2.
12.3 Лента сообщений
Мы подошли к кульминации нашего примера приложения: ленте сообщений. Соответствено, этот раздел содержит некоторые из самых продвинутых материалов в данном учебнике. Создание ленты сообщений подразумевает сборку массива микросообщений из микросообщений читаемых пользователей, совместно с собственными микросообщениями текущего пользователя. Чтобы совершить этот подвиг, нам понадобятся некоторые довольно продвинутые Rails, Ruby и даже SQL техники программирования.Так как нам предстоит тяжелая работа, особенно важно понимать куда мы будем двигаться. Макет финальной ленты сообщений пользователя, которая основана на предварительной реализации потока сообщений из Раздела 11.3.3, представлен в Рис. 12.18.
12.3.1 Мотивация и стратегия
Основная идея потока сообщений проста. Рис. 12.19 показывает пример таблицы базы данныхmicroposts
и результирующий поток сообщений. Цель потока заключается в вытягивании
микросообщений чей user id соответствует пользователям, сообщения
которых читает текущий пользователь (и id самого текущего пользователя),
как указано стрелками на схеме.from_users_followed_by
, который мы будем использовать следующим образом:Micropost.from_users_followed_by(user)
from_users_followed_by
, как показано в Листинг 12.39.
Листинг 12.39. Тесты для
Micropost.from_users_followed_by
. spec/models/micropost_spec.rb
describe Micropost do
.
.
.
describe "from_users_followed_by" do
before(:each) do
@other_user = Factory(:user, :email => Factory.next(:email))
@third_user = Factory(:user, :email => Factory.next(:email))
@user_post = @user.microposts.create!(:content => "foo")
@other_post = @other_user.microposts.create!(:content => "bar")
@third_post = @third_user.microposts.create!(:content => "baz")
@user.follow!(@other_user)
end
it "should have a from_users_followed_by class method" do
Micropost.should respond_to(:from_users_followed_by)
end
it "should include the followed user's microposts" do
Micropost.from_users_followed_by(@user).should include(@other_post)
end
it "should include the user's own microposts" do
Micropost.from_users_followed_by(@user).should include(@user_post)
end
it "should not include an unfollowed user's microposts" do
Micropost.from_users_followed_by(@user).should_not include(@third_post)
end
end
end
before(:each)
и последующая проверка всех трех требований: микросообщения читаемых
пользователей и самого пользователя включены, но сообщений нечитаемых пользователей нет.Сам поток сообщений живет в модели User (Раздел 11.3.3), так что мы должны добавить дополнительный тест в User model specs из Листинга 11.31, как показано в Листинге 12.40. (Отметьте, что мы здесь переключились с использования
include?
, как показано в Листинге 11.31, к более компактной include
конвенции, введеной в Листинге 12.12.)
Листинг 12.40. Финальные тесты для ленты сообщений.
spec/models/user_spec.rb
describe User do
.
.
.
describe "micropost associations" do
.
.
.
describe "status feed" do
it "should have a feed" do
@user.should respond_to(:feed)
end
it "should include the user's microposts" do
@user.feed.should include(@mp1)
@user.feed.should include(@mp2)
end
it "should not include a different user's microposts" do
mp3 = Factory(:micropost,
:user => Factory(:user, :email => Factory.next(:email)))
@user.feed.should_not include(mp3)
end
it "should include the microposts of followed users" do
followed = Factory(:user, :email => Factory.next(:email))
mp3 = Factory(:micropost, :user => followed)
@user.follow!(followed)
@user.feed.should include(mp3)
end
end
.
.
.
end
end
Micropost.from_users_followed_by
, как показано в Листинге 12.41.
Листинг 12.41. Добавление завершенного потока к модели User.
app/models/user.rb
class User < ActiveRecord::Base
.
.
.
def feed
Micropost.from_users_followed_by(self)
end
.
.
.
end
12.3.2 Первая реализация ленты сообщений
Пришло время реализоватьMicropost.from_users_followed_by
,
который мы для простоты будем называть “поток”. Поскольку конечный
результат довольно сложен, мы будем строить конечную реализацию потока
по кусочкам.В первую очередь нужно подумать о том, какой вид запроса нам нужен. Что мы хотим сделать, это выбрать из таблицы
microposts
все микросообщения с id соответствующими пользователям, читаемыми
данным пользователем (или самому пользователю). Мы можем схематически
написать это следующим образом:SELECT * FROM microposts
WHERE user_id IN (<list of ids>) OR user_id = <user id>
IN
, что позволяет нам протестировать множественное включение. (К счастью это так.)Вспомним из предварительной реализации потока в Разделе 11.3.3 что Active Record использует
where
метод для осуществления вида выбора, показанного выше, что иллюстрирует Листинг 11.32. Там наш выбор был очень прост; мы просто взяли все микросообщения с user id соответствующим текущему пользователю:Micropost.where("user_id = ?", id)
where("user_id in (#{following_ids}) OR user_id = ?", user)
user
вместо user.id
; Rails автоматически использует id
. Мы также опустили впереди идущую часть Micropost.
поскольку мы ожидаем что этот метод будет жить в самой модели Micropost.)Мы видим из этих условий, что нам нужен массив id пользователей, читаемых данным пользователем (или какой-то эквивалент). Один из способов сделать это заключается в использовании Ruby метода
map
,
доступного на любом “перечисляемом” объекте, т.е., любом объекте (таком
как Массив или Хэш), который состоит из коллекции элементов.17 Мы видели пример этого метода в Разделе 4.3.2; он работает следующим образом:$ rails console
>> [1, 2, 3, 4].map { |i| i.to_s }
=> ["1", "2", "3", "4"]
to_s
) вызывается на каждый элемент, настолько обычная вещь, что есть сокращенная запись, использующая ампресанд &
и символ, соответствующий методу:18>> [1, 2, 3, 4].map(&:to_s)
=> ["1", "2", "3", "4"]
id
на каждом элементе в user.following
. Например, для первого пользователя в базе данных этот массив выглядит следующим образом:>> User.first.following.map(&:id)
=> [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51]
>> User.first.following_ids
=> [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51]
Micropost.from_users_followed_by(user)
Micropost
классе (конструкция в последний раз виденная в User
классе в Листинге 7.12). Предполагаемая реализация с этими строками представлена в Листинге 12.42.
Листинг 12.42. Первый дубль для
from_users_followed_by
метода. app/models/micropost.rb
class Micropost < ActiveRecord::Base
.
.
.
def self.from_users_followed_by(user)
following_ids = user.following_ids
where("user_id IN (#{following_ids}) OR user_id = ?", user)
end
end
Кстати, если бы мы использовали метод из Листинга 12.42 для финальной реализации, мы могли бы сделать отличный рефакторинг. Метод
where
может принимать хэш аргументов с ключом :user_id
и значением эквивалентным массиву пользователей. В данном случае нам
необходим массив включающий в себя всех читаемых пользователей, а также
самого пользователя; мы можем организовать это используя push
метод (упоминался в Разделе 4.3.1) на following
ассоциации, как видно в Листинге 12.42.
Листинг 12.43. Реорганизованный метод
from_users_followed_by
. app/models/micropost.rb
class Micropost < ActiveRecord::Base
.
.
.
def self.from_users_followed_by(user)
where(:user_id => user.following.push(user))
end
end
12.3.3 Пространства, подзапросы и лямбда
Как намекалось в последнем разделе, реализация потока сообщений в Разделе 12.3.2 не очень хорошо масштабируется при большом количестве микросообщений, что скорее всего произойдет если пользователь начнет читать сообщения, скажем, 5000 других пользователей. В этом разделе мы повторно реализуем ленту сообщений способом, который лучше масштабируется с количеством читаемых пользователей.Есть несколько проблем с кодом в Разделе 12.3.2. Во-первых, выражение
following_ids = user.following_ids
Решение обеих задач включает в себя преобразование потока сообщений из метода класса в пространство, которое является Rails методом для ограничения выборки из базы данных, основанном на определенных условиях. Например, чтобы организовать выборку методом scope всех административных пользователей в нашем приложении, мы могли бы добавить пространство к User модели следующим образом:
class User < ActiveRecord::Base
.
.
.
scope :admin, where(:admin => true)
.
.
.
end
User.admin
Основная причина, по которой пространства лучше подходят, чем обычные методы класса заключается в том, что они могут быть сцеплены с другими методами, так что, например,
User.admin.paginate(:page => 1)
пространство для потока сообщений немногим более сложно чем проиллюстрированное выше: ему нужен аргумент, а именно, пользователь, чей поток нам необходимо генерировать. Мы можем сделать это с анонимной функцией, или
lambda
(обсуждалось в Разделе 8.4.2), как показано в Листинге 12.44.20
Листинг 12.44. Улучшение
from_users_followed_by
. app/models/micropost.rb
class Micropost < ActiveRecord::Base
.
.
.
default_scope :order => 'microposts.created_at DESC'
# Return microposts from the users being followed by the given user.
scope :from_users_followed_by, lambda { |user| followed_by(user) }
private
# Return an SQL condition for users followed by the given user.
# We include the user's own id as well.
def self.followed_by(user)
following_ids = user.following_ids
where("user_id IN (#{following_ids}) OR user_id = :user_id",
{ :user_id => user })
end
end
from_users_followed_by
scope довольно длинные, мы определили вспомогательную функцию для его обработки:def self.followed_by(user)
following_ids = user.following_ids
where("user_id IN (#{following_ids}) OR user_id = :user_id",
{ :user_id => user })
end
where("... OR user_id = ?", user)
where("... OR user_id = :user_id", { :user_id => user })
Блок 12.1.Процент скобки
Код в этом разделе использует Ruby конструкцию процент-круглые скобки, как в
Из вышеизложенного следует, что мы будем добавлять второе вхождение %(SELECT followed_id FROM relationships WHERE follower_id = :user_id)Вы можете думать о
%()
как об эквиваленте двойных
кавычек, но с возможностью создания мультилинейных (multiline (??))
строк. (Если вам нужен способ получения multiline строк без впереди
идущих пробелов, ищите в Google “ruby here document”.) Поскольку %()
поддерживают интерполяцию строк, это особенно полезно, когда вам нужно
поставить строку в двойные кавычки и в то же время интерполировать ее.
Например, код>> foo = "bar" >> puts %(The variable "foo" is equal to "#{foo}".)производит
The variable "foo" is equal to "bar".Чтобы получить тот же вывод со строками в двойных кавычках, вам нужно маскировать внутренние двойные кавычки бэкслэшем, как в
>> "The variable \"foo\" is equal to \"#{foo}\"."В данном случае, синтаксис
%()
более удобен так как он дает тот же результат но без явного маскирования.user_id
в SQL запросе, и это действительно так. Мы можем заменить Ruby кодfollowing_ids = user.following_ids
following_ids = %(SELECT followed_id FROM relationships
WHERE follower_id = :user_id)
%()
синтаксиса.) Этот код содержит SQL подзапрос, и внутренне вся выборка для пользователя 1 будет выглядеть подобным образом:SELECT * FROM microposts
WHERE user_id IN (SELECT followed_id FROM relationships
WHERE follower_id = 1)
OR user_id = 1
С этим фундаментом мы готовы к эффективной релизации потока сообщений, как видно в Листинге 12.45.
Листинг 12.45. Финальная реализация
from_users_followed_by
. app/models/micropost.rb
class Micropost < ActiveRecord::Base
.
.
.
default_scope :order => 'microposts.created_at DESC'
# Return microposts from the users being followed by the given user.
scope :from_users_followed_by, lambda { |user| followed_by(user) }
private
# Return an SQL condition for users followed by the given user.
# We include the user's own id as well.
def self.followed_by(user)
following_ids = %(SELECT followed_id FROM relationships
WHERE follower_id = :user_id)
where("user_id IN (#{following_ids}) OR user_id = :user_id",
{ :user_id => user })
end
end
12.3.4 Новая лента сообщений
С кодом в Листинге 12.45, наша лента сообщений завершена. Напомним, что код для Home страницы, представлен в Листинге 12.46; этот код создает пагинированный поток соответствущих микросообщений для использования в представлении, как видно в Рис. 12.20.23 Отметим, чтоpaginate
метод фактически достигает цели в методе модели Micropost в Листинге 12.45, организуя вытягивание только 30 микросообщений за раз из базы данных.24
Листинг 12.46.
Home
действие с пагинированным потоком. app/controllers/pages_controller.rb
class PagesController < ApplicationController
def home
@title = "Home"
if signed_in?
@micropost = Micropost.new
@feed_items = current_user.feed.paginate(:page => params[:page])
end
end
.
.
.
end
12.4 Заключение
Добавив ленту сообщений, мы закончили ядро примера приложения Учебника Ruby on Rails. Это приложение включает в себя примеры всех основных возможностей Rails, включая модели, представления, контроллеры, шаблоны, партиалы, фильтры, валидации, обратные вызовы,has_many
/belongs_to
и has_many :through
ассоциации, безопасность, тестирование и развертывание. Несмотря на это
внушительный список, предстоит еще многое узнать о Rails. В качестве
первого шага в этом процессе, этот раздел содержит некоторые
рекомендуемые расширения основного приложения, а также рекомендации для
дальнейшего обучения.Прежде чем перейти к решению любого из предложенных расширений приложения, хорошо бы объединить ваши изменения и развернуть приложение:
$ git add .
$ git commit -m "Added user following"
$ git checkout master
$ git merge following-users
$ git push heroku
$ heroku rake db:migrate
12.4.1 Расширения к примеру приложения
Рекомендуемые расширения в этом разделе в основном вдохновлены основными функциями, общепринятыми для веб-приложений, такими как напоминание пароля и подтверждение адреса электронной почты, или функции характерные для нашего типа примера приложения, такие как поиск, ответы, обмен сообщениями. Реализация одного или более расширений приложения поможет вам сделать переход от выполнения примеров учебника к написанию собственных оригинальных приложений.Не удивляйтесь, если по началу будет непросто; чистый лист новой функции может быть немного пугающим. Чтобы помочь вам начать, я могу дать две большие рекомендации. Во-первых, пежде чем добавлять какую-либо функцию к Rails приложению, взгляните на Railscasts, все эпизоды чтобы посмотреть, не рассказал ли уже об этом Ryan Bates.25 Если он это сделал, просмотр соответствующего Railscast сэкономит вам массу времени. Во-вторых, всегда делайте обширный поиск в Google по вашей предполагаемой функции, чтобы найти соответствующее сообщения в блогах и пособиях. Разработка веб приложений это непростое дело и это поможет вам учиться на чужом опыте (и ошибках).
Многие из следующих функций являются довольно непростыми задачами, и я дал несколько подсказок по поводу средств, которые могут вам понадобиться для их реализации. Даже с подсказками, они являются намного более трудными чем упражнения, которые приводились в конце каждой главы учебника, так что не расстраивайтесь, если вы не можете решить их без значительных усилий. Из-за нехватки времени я недоступен для личной помощи, но если есть достаточный интерес я мог бы выпустить автономную статью/скринкаст охватывающий эти расширения в будущем; перейдите на основной сайт Rails Tutorial http://www.railstutorial.org/ и подпишитесь на ленту новостей, чтобы быть в курсе последних обновлений.
Реплики
Твиттер позволяет пользователям делать “@replies”, которые являются микросообщениями, чьи первые символы являются логином пользователя предшествующим знаку @. Эти сообщения появляются только в потоке сообщений у пользователя задавшего вопрос или у пользователей читающих данного пользователя. Реализуйте упрощенную версию этого, ограничив появление @replies только в потоках сообщений получателя и отправителя. Это может подразумевать добавлениеin_reply_to
столбца в таблицу microposts
и дополнительного including_replies
пространства к модели Micropost.Поскольку нашему приложению не хватает уникальных пользовательских логинов, вам также необходимо решить, каким способом представлять пользователей. Один из вариантов это использование комбинации id и имени, например
@1-michael-hartl
. Другой способ это добавить уникальное имя пользователя в процесс регистрации и затем использовать его в @replies.Обмен сообщениями
Твиттер поддерживает непосредственный (приватный) обмен сообщениями с помощью добавления префикса с буквой “d” к микросообщению. Реализуйте эту функцию для примера приложения. Решение, вероятно, подразумевает наличие модели Message и проверку новых микросообщений с помощью регулярных выражений.Уведомления о новых читателях
Реализуйте функцию, отправляющую каждому пользователю email уведомление когда у него появляется новый читатель. Затем сделайте уведомления необязательными, так чтобы пользователи могли отказаться при желании.Помимо всего прочего, добавление этой функции требует знания о том, как отправлять почту с помощью Rails. Начните с Railscast on sending email. Помните, что главная Rails библиотека для отправки электронной почты, Action Mailer, претерпела значительные изменения в Rails 3, как показано в Railscast про Action Mailer в Rails 3.
Напоминание пароля
В настоящее время, если пользователи нашего приложения забудут свои пароли, они не смогут их восстановить. Из-за одностороннего безопасного хэширования паролей в Главе 7, наше приложение не может отправить по email пароли пользователей, но оно может отправить ссылку на форму сброса пароля. ВведитеPasswordReminders
ресурс для реализации этой функции. Вам следует создавать уникальный
token для каждого сброса и отправлять его пользователю. Посещение URL с
token должно позволять им сбрасывать старый пароль и назначать новый.Подтверждение регистрации
Помимо регулярного выражения для электронной почты, пример приложения в настоящее время не имеет способа проверки валидности пользовательского email адреса. Добавьте шаг проверки email адреса в подтверждение регистрации пользователя. Новая функция должна создавать пользователей в неактивном состоянии, отправлять по email пользователям активационный URL, и затем активировать статус пользователя при посещении соответствующего URL. Дя работы с активный/неактивный переходами вам может помочь прочтение state machines in Rails.RSS канал
Реализовать для каждого пользователя RSS канал их микросообщений. Затем реализовать RSS канал для их лент сообщений, опционально ограничив доступ к этому каналу используя аутентификационную схему. Railscast on generating RSS feeds поможет вам начать.REST API
Многие веб сайты раскрывают Application Programmer Interface (API) так что сторонние приложения могут get, post, put и delete ресурсы приложения. Реализуйте такой REST API для примера приложения. Решение подразумевает добавлениеrespond_to
блоков (Раздел 12.2.5)
ко многим действиям Application контроллера; они должны отвечать на
запросы для XML. Позаботьтесь о безопасности; API должен быть доступен
только авторизированным пользователям.Поиск
В настоящее время у пользователей нет другого способа найти друг-друга, кроме как просмотром списка пользователей или просматривая потоки сообщений других пользователей. Реализуйте функцию поиска чтобы исправить ситуацию. Затем добавьте другую поисковую функцию для микросообщений. Railscast on simple search forms поможет вам начать. Если вы используете шаред хостинг или выделенный сервер, я советую использовать Thinking Sphinx (следуйте Railscast on Thinking Sphinx). Если вы развернуты на Heroku, вы должны следовать инструкциям Heroku full text search.12.4.2 Руководство по дальнейшим ресурсам
Существует огромное количество Rails ресурсов в магазинах и в сети — предложений настолько много что это подавляет и может быть сложно понять, с чего же именно начать. Теперь вы знаете с чего начать — с этой книги, конечно. И если вы дошли до этого места, вы готовы практически ко всему за ее пределами. Вот несколько советов:- Ruby on Rails Tutorial screencasts: я подготовил полноценный скринкаст курс основанный на этой книге. В дополнение к раскрытиювсех материалов этой книги, скринкасты дополнены советами, трюками и демонстрациями типа смотри-как-это-делается, которые сложно зафиксировать в печатном варианте. Они доступны на сайте Ruby on Rails Tutorial, через Safari Books Online, и через InformIT.
- Railscasts: Трудно переоценить важность ресурса Railscasts, я советую начать с посещения архива эпизодов Railscasts и клика по любой зацепившей вас теме.
- Масштабирование Rails: Одна тема в Ruby on Rails Tutorial осталась почти нетронутой: производительность, оптимизация и масштабирование. К счастью, большинство сайтов никогда не столкнется с серьезными проблемами масштабирования, и использование чего-либо кроме простого Rails для оптимизации вероятно будет преждевременным. Если вы столкнулись с проблемами производительности, Scaling Rails серия от Gregg Pollack Envy Labs является прекрасным местом для старта. Я также рекомендую исследовать приложения для мониторинга сайтов Scout и New Relic.26 И, как вы к этому времени можете подозревать, есть Railscasts по многим темам масштабирования, включая профилирование, кэширование и фоновые процессы.
- Ruby и Rails книги: Как упоминалось в Главе 1, я рекомендую Beginning Ruby Петера Купера, The Well-Grounded Rubyist David A. Black, и The Ruby Way Хэла Фултона fдля дальнейшего изучения Ruby, и The Rails 3 Way Obie Fernandez для подробной информации о Rails.
- PeepCode: PeepCode: я упоминал несколько коммерческих скринкастеров в Главе 1,но только с одним из них у меня есть значительный опыт и это PeepCode. Скринкасты на PeepCode неизменно высокого качества и я горячо рекомендую их.
12.5 Упражнения
- Добавьте тесты для
dependent :destroy
в модели Relationship (Листинг 12.5 и Листинг 12.17) следуя примеру в Листинге 11.11. - Метод
respond_to
виденый в Листинге 12.36 на самом деле может быть поднят из действий в сам контроллер Relationships, иrespond_to
блоки могут быть заменены на Rails методrespond_with
. Подтвердите, что результирующий код, показаный в Листинге 12.47, является корректным проверив что набор тестов все еще проходит. (Подробнее об этом методе см в Google поиске “rails respond_with”.) - Действия
following
иfollowers
в Листинге 12.29 до сих пор имеют значительное дублирование. Проверьте чтоshow_follow
метод в Листинге 12.48 устраняет это дублирование. (Посмотрим, сможете ли вы вывести чем занимается методsend
, как в, например,@user.send(:following)
.) - Реорганизуйте Листинг 12.30 добавив партиалы для кода общего для читаемые/читатели страниц, Home страницы, и страницы показывающей пользователя.
- Следуя модели в Листинге 12.20, напишите тесты для статистики на странице профиля.
- Напишите интеграционный тест для чтения и не чтения сообщений пользователя.
- Перепишите Ajax методы из Раздела 12.2.5 используя jQuery вместо Prototype. Подсказка: вы можете почитать jQuery раздел Mikel Lindsaar’s blog post about jQuery, RSpec, and Rails 3. Также см. Раздел 13.1.4.2, в котором обсуждается jQuery в контексте Rails 3.1.
Листинг 12.47. Компактный рефакторинг Листинга 12.36.
class RelationshipsController < ApplicationController
before_filter :authenticate
respond_to :html, :js
def create
@user = User.find(params[:relationship][:followed_id])
current_user.follow!(@user)
respond_with @user
end
def destroy
@user = Relationship.find(params[:id]).followed
current_user.unfollow!(@user)
respond_with @user
end
end
Листинг 12.48. Реорганизованные
following
и followers
действия. app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def following
show_follow(:following)
end
def followers
show_follow(:followers)
end
def show_follow(action)
@title = action.to_s.capitalize
@user = User.find(params[:id])
@users = @user.send(action).paginate(:page => params[:page])
render 'show_follow'
end
.
.
.
end
- Фотографии для макетов взяты на http://www.flickr.com/photos/john_lustig/2518452221/ и http://www.flickr.com/photos/30775272@N05/2884963755/. ↑
- Для простоты, Рис. 12.6 подавляет
id
столбец таблицыfollowing
. ↑ - К сожалению, Rails использует
connection
для подключения к базе данных, поэтому введение Connection модели приведет к некоторым довольно тонким багам. (Я узнал это на собственной шкуре при разработке Insoshi.) ↑ - Действительно, эта конструкция является столь характерной для Rails, что известный Rails программист Josh Susser использует ее в качестве названия для своего гик блога. ↑
- Технически, Rails использует метод
underscore
для преобразования имени класса в id. Например,"FooBar".underscore
этоfoo_bar
, поэтому внешним ключом для объектаFooBar
будетfoo_bar_id
. (Кстати, инверсиейunderscore
являетсяcamelize
, который конвертируетcamel_case
вCamelCase
.) ↑ - Если вы заметили что
followed_id
также идентифицирует пользователя, и обеспокоены ассиметричным обращением с читателями и читаемыми, вы готовы к любым неожиданностям. Мы займемся этим вопросом в Разделе 12.1.5. ↑ - Этот метод
follow!
должен работать всегда, поэтому, (следуя моделиcreate!
иsave!
) мы ставим восклицательный знак и в случае провала будет вызвано исключение. ↑ - Once you have a lot of experience modeling a particular domain, you can often guess such utility methods in advance, and even when you can’t you’ll often find yourself writing them to make the tests cleaner. In this case, though, it’s OK if you wouldn’t have guessed them. Software development is usually an iterative process—you write code until it starts getting ugly, and then you refactor it—but for brevity the tutorial presentation is streamlined a bit. (??) ↑
- Метод
authenticate_with_salt
включен только в качестве ориентира в файле модели User. ↑ - Метод
unfollow!
не вызывает исключения при неудаче — фактически я даже не знаю как Rails указывает на провальное уничтожение — но мы используем восклицательный знак для поддержанияfollow!
/unfollow!
симметрии. ↑ - Вы могли заметить, что иногда мы получаем доступ к
id
явно, как вfollowed.id
, а иногда мы просто используемfollowed
. Мне стыдно признаться, но мой обычный алгоритм для выяснения того, что id можно опустить, заключается в проверке работает ли код без.id
, а затем в добавлении.id
если не сработало. ↑ - Все что содержится в Листинге 12.28 было раскрыто в других местах этого руководства, так что это хорошее упражнение в чтении кода. ↑
- Так как номинально это является акронимом asynchronous JavaScript and XML, Ajax иногда ошибочно пишут как “AJAX”, хотя на протяжении всей оригинальной Ajax статьи используется написание “Ajax”. ↑
- Это работает, только если JavaScript включен в браузере, но изящно деградирует, работая в точности как в Разделе 12.2.4 если JavaScript отключен. ↑
- В этот момент вы должны включить дефолтную Prototype JavaScript Library в вашем Rails приложение как в Листинге 10.39 если вы этого еще не сделали. ↑
- Нет никакой связи между этим
respond_to
иrespond_to
используемым в RSpec примерах. ↑ - Основное требование заключается в том, что перечисляемые объекты должны реализовывать
each
метод для перебора коллекции. ↑ - Эта запись фактически началась, как расширение Rails, внесенное в ядро языка Ruby; она была настолько полезной, что в настоящее время она включена в сам Ruby. Замечательно, правда? ↑
- Вызов
paginate
на объектArray
преобразует его в объектWillPaginate::Collection
, но это не сильно нам поможет, поскольку весь массив уже создан в памяти. ↑ - Функция в комплекте с элементом данных (пользователем, в данном случае) называется замыкание, мы с ним сталкивались при кратком обсуждении блоков в Разделе 4.3.2. ↑
- Для более продвинутых способов создания необходимых подзапросов, см. сообщение в блоге “Hacking a subselect in ActiveRecord”. ↑
- Конечно, даже подзапрос не масштабирует навсегда. Для больших сайтов вам, вероятно потребуется генерировать поток сообщений асинхронно используя фоновый процессы. Такие тонкости масштабирования выходят за рамки этого учебника, но скринкасты Scaling Rails являются хорошей отправной точкой. ↑
- Для того чтобы сделать поток сообщений более привлекательным в Рис. 12.20, я добавил несколько дополнительных микросообщений вручную используя Rails консоль. ↑
- В этом можно убедиться, изучив SQL выражения в в лог-файле сервера разработки. (Rails Tutorial screencasts раскрывают подобные тонкости более подробно.)
- Единственная моя оговорка по поводу Railscasts — они обычно опускают тесты. Это, вероятно, необходимо для сохранения красоты и краткости эпизодов, но у вас может сформироваться неправильное представление о важности тестов. После просмотра соответствующего Railscast для получения представления о процессе, я советую писать новую фнкцию используя разработку через тестирование. ↑
- In addition to being a clever phrase — new relic being a contradiction in terms (??) — New Relic также является анаграммой имени основателя компании, Lew Cirne. ↑
- # позволю себе небольшой комментарий к
переводу этой главы. В тексте очень часто употребляются различные формы
слова follow (following, follower, followers и т.п.) в дословном
переводе это означает "следовать" (слежение, следящий, следящие
соответственно). Однако Twitter, (клоном которого является пример
приложения, рассматриваемый в этом учебнике) вместо дословного перевода,
использует термины "Читает" (для following) и "Читают" (для followers)
и лично мне термин "Читатели" вместо возможного дословного перевода
(следователи, последователи и т.п.) нравится больше. И, несмотря на
несколько казусов с этим связанных, в оставшейся части учебника я
постараюсь придерживаться именно этого варианта.
P.s. Надеюсь данный комментарий не запутал вас окончательно. :) ↑
Подписаться на:
Сообщения (Atom)