Модель
Шаг 1: Создаем спеку для модели Article в файле spec/models/article_spec.rb:
require 'spec_helper'
describe Article do
describe "3rd party features" do
describe "- acts_as_commentable_with_threading" do
it "responds to :comment_threads call" do
Article.new.should respond_to(:comment_threads)
end
end
end
end
Естественно, rake spec или autotest выдаёт падение спеки
Шаг 2: Добавляем gem 'acts_as_commentable_with_threading' в Gemfile
Шаг 3: Выполняем команду
bundle install
Шаг 4: Генерируем модель Comment и миграционную процедуру для создания таблицы comments командой
rails generate acts_as_commentable_with_threading_migration
Шаг 5: Накатываем миграционную процедуру
rake db:migrate
Шаг 6: Добавляем строку acts_as_commentable в модель Article и убеждаемся, что наша спека выполняется без падения
Маршрут
Шаг 7: Далее проложим маршрут к articles#add_comment
Создаем спеку в файле spec/routing/article_routing_spec.rb:
require "spec_helper"
describe ArticlesController do
describe "routing" do
it "routes to #add_comment" do
put("/articles/1/add_comment").should route_to("articles#add_comment", :id => "1")
end
end
end
Добавляем определение маршрута put :add_comment, :on => :member в файл config/routes.rb:
resources :articles do
get :autocomplete_tag_name, :on => :collection
put :add_comment, :on => :member
end
и убеждаемся, что спека проходит и переходим к следующему шагу:
Контроллер
Начинаем со спеки в файле spec/controllers/articles_controller_spec.rb. У нас там сейчас два контекста для анонимусов и для вошедших пользователей. Начнём с анонимусов.
Шаг 8: Анонимные пользователи не могут добавлять комментарии к статьям.
Добавляем в контекст анонимного пользователя:
describe :add_comment do
it "redirects to 'Sign in' page" do
put :add_comment, :id => 1, :comment => 'php is b3tt3r'
response.should redirect_to(new_user_session_path)
end
end
И наблюдаем падение, вызванное отсутствием метода add_comment в контроллере ArticlesController
Контроллер: Добавляем метод add_comment в app/controllers/articles_controller.rb
def add_comment
end
Зелёный свет, и мы переходим к контексту вошедшего пользователя
Шаг 9: Добавляем новый пример в спеку в контекст вошедшего пользователя:
describe :add_comment do
it "finds article by passed id" do
Article.should_receive(:find).with(1)
put :add_comment, :id => 1, :comment => 'ruby is c00l'
end
end
Хотя наш метод контроллера не содержит вызова Article.find(params[:id]), спека проходит без падения, потому что declarative_authorization определяет права доступа текущего пользователя к ресурсу, поэтому объект находится по переданному id и сохряется в переменной @article.
Шаг 10: Следующая спека также проходит без падений
it "assigns found article to @article variable" do
put :add_comment, :id => 1, :comment => 'ruby is c00l'
assigns(:article).should == article
end
Предварительно надо заглушить вызов метода find у класса Article, чтобы не было обращения к базе:
before do
Article.stub!(:find).and_return(article)
end
Не забываем про то, что нам надо добавить
let!(:article) { mock_model(Article) }
прямо в контекст. Зелёный свет, идём дальше.
Шаг 11: Создание объекта класса Comment
Спека:
it "calls Comment.build_from" do
Comment.should_receive(:build_from).with(article, user.id, 'ruby is c00l')
put :add_comment, :id => 1, :comment => 'ruby is c00l'
end
Прежде чем править контроллер, добавим разрешение пользователю вызывать это действие в config/authorization_rules.rb:
role :regular do
...
has_permission_on :articles, :to => [ :new, :create, :autocomplete_tag_name, :add_comment ]
...
end
Правим контроллер:
def add_comment
Comment.build_from(@article, current_user.id, params[:comment])
end
Шаг 12: Присваивание созданного объекта переменной @comment
Глушим вызов Comment.build_from в before блоке:
Comment.stub!(:build_from).and_return(comment)
it "assigns built comment object to @comment variable" do
put :add_comment, :id => 1, :comment => 'ruby is c00l'
assigns(:comment).should == comment
end
Контроллер:
def add_comment
@comment = Comment.build_from(@article, current_user.id, params[:comment])
end
Шаг 13: Сохраняем ссылку на родительский комментарий
Меняем заглушку комментария:
let!(:comment) { mock_model(Comment, :parent_id= => true) }
Добавляем пример:
it "sets parent_id for built comment" do
comment.should_receive(:parent_id=).with(30)
put :add_comment, :id => 1, :comment => 'ruby is c00l', :parent_id => 30
end
Контроллер:
def add_comment
@comment = Comment.build_from(@article, current_user.id, params[:comment])
@comment.parent_id = params[:parent_id]
end
Шаг 14: Сохранение комментария
Спека:
it "saves @comment" do
comment.should_receive(:save)
put :add_comment, :id => 1, :comment => 'ruby is c00l'
end
При этом исправляем строку с объявлением comment
let!(:comment) { mock_model(Comment, :parent_id= => true, :save => true) }
Контроллер:
def add_comment
@comment = Comment.build_from(@article, current_user.id, params[:comment])
@comment.parent_id = params[:parent_id]
@comment.save
end
Шаг 15: Редирект на articles#show
Спека:
it "redirects to articles#show" do
put :add_comment, :id => 1, :comment => 'ruby is c00l'
response.should redirect_to(article_path(article))
end
Контроллер:
def add_comment
@comment = Comment.build_from(@article, current_user.id, params[:comment])
@comment.parent_id = params[:parent_id]
@comment.save
redirect_to @article
end