Статьи

Rails Deep Dive: локации, события

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

CRUDdy События

Если только вы не разморозились от многолетнего ледяного сна, вы знаете, что влечет за собой CRUDification модели. На земле Rails модель чаще всего превращается в ресурс REST . (Примечание. Если вы не разбираетесь в REST, я НАСТОЯТЕЛЬНО рекомендую книгу O’Reilly RESTful Web Services , я не буду подробно освещать REST в этой серии.) Для модели Event мы будем нужны конечные точки HTTP, которые позволяют нам добавлять (POST), изменять (PUT), извлекать (GET) и удалять (Um, DELETE) события.

ОБНОВЛЕНИЕ : читатель предупреждений указал, что я забыл добавить маршруты событий в файл route.rb. Итак, добавьте это в config / rout.rb

resources :events 

Который будет генерировать все RESTful маршруты, необходимые для событий.

Создание событий

Поскольку событие является таким простым объектом, мы можем разместить форму для их создания прямо на нашей домашней странице пользователя. С ловкостью кошки я возвращаюсь в MockupBuilder и изменяю нашу страницу событий, чтобы добавить форму внизу.

Добавить событие

Время написать тест, чтобы заполнить эту форму. Я добавил spec / accept / add_events_spec.rb с

 require 'spec_helper' feature 'Add Events', %q{ As a registered user I want to add Events } do background do login_user Factory(:user) end scenario "Add Basic Event" do fill_in "Name", :with => "New Event" fill_in "Description", :with => "This is my new event" click_button "Create Event" page.should have_content("New Event") page.should have_content("This is my new event") page.should have_selector("ul > li") end end 

Запуск этой спецификации приводит к жалобам на ElementNotFound потому что мы еще не создали нашу форму. Итак, давайте добавим форму в наше приложение / views / events / index.html.haml (добавить в конец файла)

 = form_for Event.new do |f| = f.label :name = f.text_field :name = f.label :description = f.text_field :description = f.submit 

Поскольку мы хотим, чтобы эта форма была «встроенной», нам необходимо переопределить базовые стили. В файл app / assets / stylesheets / events.css.scss добавьте

 .new_event label, input[type='text'] { display:inline; } input#event_description { width: 500px; } 

(Примечание: Rails добавляет класс new_event к форме). Теперь спецификация жалуется на отсутствие действия create на EventsController . Окей, ключ, мы можем добавить это (в app / controlles / events_controller.rb )

 def create event = current_user.events.build(params[:event]) event.save end 

Теперь в спецификации сказано, что у нас нет шаблона events/create , что правда. Но нам не нужен этот шаблон, мы хотим, чтобы действие create просто отображало домашнюю страницу. Итак, сейчас давайте перенаправим на страницу индекса событий. я добавил

 page.current_path.should == events_path 

к нашей спецификации и

 redirect_to events_path 

в метод EventsController#create . Спецификация жалуется на то, что текст описания отсутствует на странице. Ой, похоже, я пренебрегал этим, когда создавал список событий. Измените приложение / views / events / index.html.haml на

 %h2 Your Events map.sixteen_columns %ul#events - for event in @events %li %span.event_name= event.name %span.event_description= event.description = form_for Event.new do |f| = f.label :name = f.text_field :name = f.label :description = f.text_field :description = f.submit 

и спецификация проходит.

В такие моменты мне нравится запускать сервер ( rails s ) и смотреть по сторонам. Первое, что я замечаю, это то, что при входе в систему и на домашней странице нет четкой навигации по странице пользовательских событий . Кроме того, когда я нахожусь на странице пользовательских событий, список событий выглядит как дерьмо. Я не слишком зациклен на последнем вопросе, но я хотел бы получить некоторые визуальные подсказки, чтобы сделать их немного лучше на данный момент. Наконец, пространство, куда пойдет наша карта, — большая пустота, но я не собираюсь иметь с этим дело, пока мы не приступим к созданию Occasions.

Очистить зарегистрированную навигацию

В связи с первой проблемой, описанной выше, при входе пользователя в систему должна быть ссылка для доступа к событиям пользователей. Я собираюсь назвать это «Мои события». Однако сначала нам нужно написать тест, чтобы убедиться, что он там есть. Фактически, мы можем просто добавить его в сценарий «Успешный вход » в spec / accept / sign_in_spec.rb

 current_path.should == user_root_path # This line already exists page.should have_selector("a", :text => "My Events", :href => user_root_path) 

Мои события Ссылка Spec

Для прохождения этой спецификации просто добавьте ссылку «Мои события» в условное обозначение sign_in в файле app / views / layout / application.html.haml . (Примечание: просто добавьте строки с комментариями #Add this line Добавить #Add this line )

 -if user_signed_in? Hullo #{current_user.name} | <br> = link_to "My Events", user_root_path #Add this line | #Add this line = link_to "Sign Out", destroy_user_session_path, :method => :delete - else = link_to "Sign In", new_user_session_path 

и спецификация проходит. Если вы хотите запустить сервер и посмотреть на нашу новую область входа в систему, сделайте это.

Добавление больше CRUD к событиям

Мы можем создавать и извлекать события, но мы не можем редактировать или удалять события. Давайте добавим возможность сделать это сейчас. Мои первоначальные мысли о редактировании — просто загрузить «выбранное» событие в ту же форму, которую мы используем для создания событий. Я сомневаюсь, что так будет и дальше, но это дает нам быстрый способ проверить возможности обновления. Используя этот рабочий процесс, действие edit нашего EventsController будет извлекать выбранное событие из базы данных и @event переменную экземпляра @event . Представление редактирования затем отобразит эту информацию о событии в форме. Таким образом, важным выводом является то, что наша страница пользовательских событий также будет нашим представлением редактирования. Также пользователю потребуется способ «выбрать» конкретное событие. Пока что мы сделаем имя события в списке событий гиперссылкой, которая запускает представление события. Давайте напишем еще несколько тестов, чтобы избавиться от этого. Я создал файл spec / accept / edit_events_spec.rb с:

 require 'spec_helper' feature 'Select Event', %q{ As a registered user I want to select an Event } do background do @user = Factory(:user) @event = Factory(:event, :user => @user ) login_user @user end scenario "Select Event" do page.should have_selector("a", :text=> @event.name) click_link @event.name page.should have_selector("li.selected", :text=> @event.name) page.should have_selector("input[name='event[name]']", :value => @event.name) page.should have_selector("input[name='event[description]']", :value => @event.description) end end 

Конечно, спецификация терпит неудачу, потому что на странице нет ссылки с «Test Event» (помните, это имя нашего фабричного объекта Event). Откройте app / views / events / index.html.haml и измените:

 %span.event_name= event.name 

в

 %span.event_name = link_to event.name, edit_event_path(event) 

Спецификация жалуется The action 'edit' could not be found for EventsController , что имеет смысл. Итак, добавьте метод edit в EventsController.

 def edit end 

И теперь спецификация жалуется на отсутствие шаблона edit . В качестве быстрого касания Rails сообщает вам, где он выглядел, и вы можете видеть, что Devise изменил наш путь поиска представлений… довольно круто.

Где шаблон редактирования

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

 def edit @events = current_user.events @event = @events.find(params[:id]) render 'index' end 

Следующая проблема заключается в том, что нет li с selected классом, поэтому откройте шаблон индекса событий и измените его на:

 %h2 Your Events map.sixteen_columns %ul#events - for event in @events %li{:class => @event == event ? :selected : nil} #FIRST %span.event_name = link_to event.name, edit_event_path(event) %span.event_description= event.description = form_for @event || Event.new do |f| #SECOND = f.label :name = f.text_field :name = f.label :description = f.text_field :description = f.submit # %h2 Your Events map.sixteen_columns %ul#events - for event in @events %li{:class => @event == event ? :selected : nil} #FIRST %span.event_name = link_to event.name, edit_event_path(event) %span.event_description= event.description = form_for @event || Event.new do |f| #SECOND = f.label :name = f.text_field :name = f.label :description = f.text_field :description = f.submit 

В интересах времени я внес все изменения, чтобы наша спецификация прошла. Во-первых, цикл событий проверяет соответствие текущего события нашей переменной экземпляра @event и добавляет selected имя класса CSS к элементу списка, когда это происходит. Во-вторых, у нас есть тест form_for на @event переменной экземпляра @event , и мы возвращаемся к Event.new если его там нет.

Спецификация сейчас проходит. Мы можем выбрать событие, которое загружает его в форму. Тест HO! (Под «HO!» Я имею в виду, добавить эту функцию в тот же файл edit_event_spec.rb )

 feature 'Edit Event', %q{ As a registered user I want to edit a selected Event } do background do @user = Factory(:user) @event = Factory(:event, :user => @user ) login_user @user click_link @event.name end scenario "Edit Event" do fill_in "Name", :with=> "Edited Event" click_button "Update Event" page.should have_selector("a", :text => "Edited Event") end end 

Вы можете видеть, что мы выбираем наше событие в background блоке (нюхать, нюхать, я чувствую помощник …), а затем сценарий изменения имени события. Эта спецификация терпит неудачу, потому что на EventsController нет действия update Как и действие edit , нам нужно добавить наше новое действие в контроллер. Однако, прежде чем мы сделаем это, я хочу отметить кое-что классное, что Rails только что сделал для нас, бесплатно. Обратите внимание, что в сценарии «Редактировать событие» мы ищем кнопку «Обновить событие». Однако мы не поместили никакого кода в представление, чтобы различать форму создания и форму обновления. Rails и form_for делают это для нас, заставляя форму делать правильные вещи, основываясь на переданном в нее объекте. Некоторые мелочи, которые делает Rails, например, заставляют меня очень сильно его обнять.
Добавление действия update выполняется так же, как добавление действия edit . Добавьте пустой метод update в EventsController , EventsController , чтобы спецификация пожаловалась на отсутствующий шаблон обновления, затем добавьте кишки в метод update , перенаправьте в представление index и отключите. Перенаправление — небольшая разница, потому что после обновления мы просто хотим вернуться на страницу событий, чтобы обновить наше обновленное событие в списке событий.

 def update event = current_user.events.find(params[:id]) event.update_attributes(params[:event]) event.save redirect_to events_path end 

Спецификации проходят, и мы можем создавать и обновлять События. Прогресс это весело.

ДОЛЖНЫ УНИЧТОЖИТЬ СОБЫТИЯ

Как только Loccasions могут уничтожать события, мы закончим с манипулированием событиями. Во-первых, нам нужно что-то, чтобы пользователь мог указать, что событие является конченным. Кнопка «Удалить» звучит как хорошее начало, также как и написание теста для указанной кнопки. Вот спецификация:

 require 'spec_helper' feature "Delete Event", %q{ As a registered user, I want to delete an event } do background do Capybara.current_driver = :selenium #FIRST @user = Factory(:user) @event = Factory(:event, :user => @user, :name=>"Dead Event Walking") login_user @user end after do #afterFIRST Capybara.use_default_driver end scenario "Delete Event" do page.should have_content("Dead Event Walking") page.should have_selector("form[action='/events/#{@event.id}'] input[value='delete']") #SECOND # auto confirm the dialog page.execute_script('window.confirm = function() {return true;}') #FIRST click_button "X" page.should_not have_content("Dead Event Walking") end end 

Я сделал это, где немного забегаю вперед со спецификацией «Удалить событие», поэтому я попытаюсь объяснить, что происходит. ПЕРВЫЙ, я переключаю test_driver для Capybara на http://seleniumhq.org/ , приказываю странице просто автоматически подтверждать все диалоги, а затем снова переключаюсь на тестовый драйвер по умолчанию. Во-вторых, вы можете быть удивлены, почему я проверяю наличие формы с такими странными атрибутами. Из-за RESTful-природы Rails маршрут уничтожения для ресурса требует использования метода HTTP DELETE. Единственный способ выполнить HTTP-запрос, который не использует GET, — это использовать форму. Тем не менее, поддержка метода HTTP DELETE среди браузеров не является точной, а поддержка HTML5 находится в стадии разработки , поэтому нам нужно соглашение. Текущее соглашение и то, что Rails сделает для вас, — это создание формы POST, включающей скрытый ввод с именем _method который имеет значение глагола HTTP, который мы хотим использовать. Затем промежуточное программное обеспечение маршрутизации Rails проверяет этот параметр и соответствующим образом направляет запрос. Вот почему я написал эту спецификацию таким образом, даже если она идет немного глубже, чем обычные тесты приемлемости. Опять же, этот тест, скорее всего, изменится в будущем.
Спецификация, конечно, будет жаловаться на то, что представление не имеет формы с этими атрибутами. Вот наш новый индексный просмотр:

 %h2 Your Events map.sixteen_columns %ul#events - for event in @events %li{:class => @event == event ? :selected : nil} %span.del_form =button_to "X", event, :confirm => "Are you sure?", :method => :delete %span.event_name = link_to event.name, edit_event_path(event) %span.event_description= event.description %div.clear = form_for @event || Event.new do |f| = f.label :name = f.text_field :name = f.label :description = f.text_field :description = f.submit 

Если вы запустили rails s сейчас, вошли в систему и перешли на страницу пользовательских событий, вы можете увидеть (если событие существует) кнопку удаления. Просмотр источника этой страницы показывает форму удаления:

 <span class='del_form'> <form method="post" action="/events/4e67812841574e0462000002" class="button_to"> <div> <input name="_method" type="hidden" value="delete" /> <input data-confirm="Are you sure?" type="submit" value="X" /> <input name="authenticity_token" type="hidden" value="..elided.." /> </div> </form> </span> 

Rails предоставляет нам всевозможную помощь: скрытый ввод _method , атрибут data-confirm _method для нашего окна подтверждения и authenticity_token чтобы помочь избежать атак межсайтового скриптинга. А что ВЫ получили Rails? Ничего, а?
Запустите спецификацию, и мы получим знакомую жалобу о том, что EventsController пропустил действие, destroy в этом случае. На данный момент, вы должны знать, что будет. Добавьте пустой метод, посмотрите, как он провалится, добавьте логику уничтожения и перенаправления, посмотрите, как он проходит, и, наконец, почувствуйте себя хорошо. После того, как вы добавили метод destroy :

 def destroy event = current_user.events.find(params[:id]) event.destroy redirect_to events_path end 

все спецификации пройдут. Вы, возможно, были немного поражены браузером, появляющимся, когда вы запускали спецификации, а? Это Селен, и это круто. (Но мы, вероятно, избавимся от этого позже …) Итак, если вы запустите сервер, вы сможете добавлять, изменять и уничтожать события. В следующий раз мы добавим Occasions и, возможно, (DUN DUN DUUUUUN) карту. О, и не забудьте:

 git add . git commit -am "CRUDed events" git push origin adding_events git checkout master git merge adding_events git push origin master