Статьи

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

В нашем последнем посте мы закончили с очень простой аутентификацией. Тем не менее, мы подделываем events_path в нашей спецификации sign_in, с которой и начнем. Успешный вход перенаправляет на страницу пользовательских событий, которая, предположительно, имеет список событий, принадлежащих этому пользователю. Давайте вернемся к Mockbuilder и подберем макет для нашей страницы событий.

Макет страницы событий

Макет страницы отличается от макета домашней страницы, а основным содержанием является карта. Последние события будут видны на карте, а события пользователей будут перечислены под картой. Еще один очень простой макет, помогающий нам управлять реализацией сайта. На этом этапе мы можем идентифицировать События как ресурс и приступить к разработке элементов, которые будут представлять этот ресурс. Конечно, нам понадобится модель Event и контроллер событий.

Модель события

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

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

В этой истории перечислены наши атрибуты в явном виде, поэтому у нас достаточно для создания нашей модели. Во-первых, давайте сделаем ветку git:

git checkout -b adding_events 

Прежде чем мы сгенерируем модель, кратко рассмотрим атрибут «последнее вхождение». Зная, что события будут иметь место, мне кажется, что мы на самом деле не будем хранить значение для last_occurrence, а извлечем самую последнюю дату из событий. Это делает последнее вхождение «виртуальным атрибутом», поскольку оно генерируется, когда вы запрашиваете его. Тем не менее, часть меня беспокоит необходимость запроса этого атрибута. Другое дизайнерское решение: создайте виртуальный атрибут или реальный атрибут, который должен обновляться при каждом вхождении. Что ж, преждевременная оптимизация — корень всего зла. Мы пойдем с виртуальным атрибутом … пока.

 rails g model Event name:string description:string user:references 

который генерирует модель и спец. Нам нужно написать пару тестов для Event. События принадлежат пользователям (таким образом, user:references в нашем генераторе) и не могут существовать без Имени. Я пошел дальше и добавил Фабрику для событий:

 factory :event do name "Test Event" user end 

Вот тест для пользователя:

 describe Event do it "should belong to a user" do event = Factory.build(:event, :user=>nil) event.valid?.should be_false event.errors[:user].should include("can't be blank") event.user = User.new event.valid?.should be_true end end< 

что не получается, пока мы не добавим это в нашу модель событий:

 validates :user, :presence => true 

Прежде чем мы продолжим работу со спецификациями Event, я разочарован тем, сколько времени это занимает каждый раз, когда я запускаю свои спецификации. Время раскрутки слишком велико, поэтому я хочу сделать что-то, чтобы ускорить это. Мои инстинкты говорят мне, что спецификации должны загружать среду приложения Rails, и это занимает больше всего времени. Я пытался просто запустить спецификации моделей ( rake spec:models ), но время запуска все еще беспокоит меня. После долгих поисков я попытаюсь добавить Spork в нашу тестовую среду.

Добавление Spork

Добавьте gem 'spork', '~> 0.9.0.rc' в часть development вашего Gemfile и bundle install . Теперь нам нужно настроить спецификацию для Spork. К счастью, у Spork есть несколько помощников для этого. В корне вашего проекта введите spork --bootstrap , который добавит некоторые инструкции в ваш файл spec / spec_helper.rb , поэтому откройте этот файл. По сути, нам нужно разделить наш spec_helper на два блока: prefork и each_run .
Все, что нам нужно запустить только один раз для каждого запуска спецификации, помещается в блок prefork . Сейчас я помещаю все в блок prefork , поэтому мой файл spec / spec_helper.rb выглядит так

 require 'rubygems' require 'spork' Spork.prefork do # Loading more in this block will cause your tests to run faster. However, # if you change any configuration or code from libraries loaded here, you'll # need to restart spork for it take effect. ENV["RAILS_ENV"] ||= 'test' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'capybara/rspec' # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} RSpec.configure do |config| # == Mock Framework # # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: # # config.mock_with :mocha # config.mock_with :flexmock # config.mock_with :rr config.mock_with :rspec # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures # config.fixture_path = "#{::Rails.root}/spec/fixtures" # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. # config.use_transactional_fixtures = true # Clean up the database require 'database_cleaner' config.before(:suite) do DatabaseCleaner.strategy = :truncation DatabaseCleaner.orm = "mongoid" end config.before(:each) do DatabaseCleaner.clean end end end Spork.each_run do # This code will be run each time you run your specs. end 

У нас могут возникнуть проблемы с необходимостью перезапустить Spork для получения изменений, но мы справимся с этим, если это произойдет. Выполнение быстрой проверки времени до и после ( time rake против time rspec --drb spec/ ), время выполнения моей спецификации сократилось на 20 секунд! Ух ты … это достойно зануды. Чтобы завершить изменения в --drb , добавьте --drb в ваш файл .rspec, чтобы он по умолчанию использовал Spork. Кстати, поскольку Spork загружает среду Rails, необходимо будет перезапустить Spork при изменении этой среды (добавление нового маршрута и т. Д.).

Вернуться к тестированию

Хорошо, теперь мы можем закончить наши спецификации событий. Давайте добавим быстрый тест, чтобы убедиться, что name является обязательным атрибутом.

 it "should require a name" do event = Factory.build(:event, :name=>nil) event.valid?.should be_false event.errors[:name].should include("can't be blank") event.name ="Event Name" event.valid?.should be_true end 

Это не удается, потому что мы не проверяем имя. Измените строку, которую мы добавили, чтобы подтвердить пользователя:

 validates :user, :name, :presence => true 

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

Тестирование того, что у пользователя есть события

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

 describe "User Event" do it "can be built for a user" do lambda { @user.events.build(:name=>"A new event") }.should change(@user.events, :length).by(1) end it "can be removed from a user" do @user.events.build(:name => "A short event") lambda { @user.events.first.destroy }.should change(@user.events, :length).by(-1) end end 

Эти спецификации проходят без дальнейшего кодирования, так что, ладно? (ПРИМЕЧАНИЕ: это не так … Читатель оповещения Николас (см. Комментарии) справедливо указывает, что у нас еще есть 2 дела:

1) Добавьте embeds_many :events в модель User.
2) Либо перезапустите spork, либо добавьте ActiveSupport::Dependencies.clear в наш раздел Spork.each в spec_helper.rb.
)

Контроллер событий

Теперь нам нужно отобразить наши события на странице пользовательских событий. Нам понадобится EventController и индексное действие / представление. Генераторы, хо!

 rails g controller events index 


Давайте отменим наш чит о маршруте из последнего поста и укажем events_path на наше событие events # index. Удалите get "events#index" и измените маршрут «events», чтобы он match 'events' => 'events#index', :as => :events и запустите спецификации. Ммм … они терпят неудачу.

События Spec проваливается!

Я не ожидал, что это не удастся. Подождите секунду, я не написал этот events_controller_spec . Глупые генераторы … Я не хочу эту спецификацию. Удалите каталог spec / controllers с предубеждением. Там наши спецификации снова проходят. Как я уже упоминал при настройке этой серии, мы, насколько это возможно, проводим тестирование с помощью тестов соответствия. Из-за этого у нас не будет конкретных спецификаций контроллера / вида.

Создайте файл specs / accept / user_events_spec.rb для хранения спецификаций на нашей странице пользовательских событий. Чтобы увидеть страницу пользовательских событий, нам нужно войти в нашу спецификацию. Поскольку мы используем спецификации запросов, нам нужно, в основном, смоделировать отправку учетных данных на сервер. Мы можем сделать это с помощью быстрого миксина, который мы включим в нашу функцию. Добавьте это в spec / support / request_helpers.rb

 def login_user(user) visit new_user_session_path fill_in "Email", :with => "[email protected]" fill_in "Password", :with => "password" click_button "Sign in" end 

Как видите, мы буквально входим в приложение.

Теперь давайте создадим файл spec / accept / user_events_spec.rb с

 require 'spec_helper' feature 'User Events Page', %q{ As a signed in user I want to see my events on my events page } do background do @user = Factory(:user) @event = Factory(:event, :user => @user) end scenario "User is not signed in" do visit events_path current_path.should == new_user_session_path end end 

Сначала мы протестируем отрицательный путь, то есть, что произойдет, если мы не выполним вход и не попытаемся перейти на страницу пользовательских событий. В этом случае приложение должно перенаправить на страницу входа (которая является new_user_session_path ). Запустите эту спецификацию, и путь указывает на (довольно некрасиво, запишите это) URL-адрес событий, поэтому он не перенаправляет. Нам нужно сообщить EventsController для аутентификации пользователя. Благодаря Devise все, что нам нужно сделать, это добавить это (поместите это прямо под оператором class )

 before_filter :authenticate_user! 

и спецификация проходит. Теперь давайте создадим позитивный путь

 feature 'Signed In User Events Page', %q{ As a signed in user I want to see my events on my events page } do background do @user = Factory(:user) @event = Factory(:event, :user => @user) login_user(@user) end scenario "User is signed in" do visit events_path page.should have_content(@user.name) page.should have_content(@event.name) end end 

Мы измеряем здесь успех, просто следя за тем, чтобы имя пользователя и имя события были на странице. Вероятно, нам придется усилить этот тест позже.

Запустите спецификацию, и он будет жаловаться, что имя пользователя не было найдено на странице. Давайте откроем файл app / views / events / index.html.haml и посмотрим, что случилось. Это представление все еще является базовым, сгенерированным представлением, поэтому мы должны максимально согласовать его с нашим макетом. Прежде всего, «область входа» в макете имеет приветствие и имя пользователя. Этот бит находится в файле app / views / layouts / application.html.haml . Я изменил div #sign_in на

 #sign_in.sixteen.columns %span -if user_signed_in? Hullo #{current_user.name} | = link_to "Sign Out", destroy_user_session_path, :method => :delete - else = link_to "Sign In", new_user_session_path 

Запустите спецификацию, и теперь она жалуется на название события. Прогресс. Здесь мы откроем представление app / views / events / index.html.haml и изменим его на

 %ul#events - for event in @events %li= event.name 

Это приводит к тому, что спецификация жалуется на то, что у You have a nil object when you didn't expect it! (Я ненавижу эту ошибку, это вызвало мои часы разочарования), потому что мы @events объект @events , который не существует. К контроллеру!

 class EventsController < ApplicationController before_filter :authenticate_user! def index @events = current_user.events end end 

Мы вернулись к прохождению спецификации.

Заворачивать

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