Статьи по тегу ruby.su

25 06

ruby.su hackathon #3

25 июня прошёл ruby.su hackathon #3.
Мы планировали сделать:

  • Публикацию новостей и статей
  • и начать реализацию форума (!!)

В очередной раз до реализации форума руки не дошли. Но

  • мы перевели процесс разработки на BDD с использованием rspec
  • доделали теги с автодополнением
  • а Захар сделал чумовое лого и дизайн.

Самая страшная проблема, которую мы не смогли победить: мы не смогли заставить залогиниться пользователя в спеке с использованием devise. Делали по инструкции: How To: Controllers and Views tests with Rails 3.

Проблемная строка на github’е. Если знаете как решить проблему, пожалуйста, дайте знать.

02 07

Как сделать вклад в проект ruby.su

Для того, чтобы сделать вклад в проект ruby.su надо сделать выполнить следующие шаги:

  1. Зарегистрироваться на github’е
  2. Сделать свою ветку (fork) проекта ruby.su на странице github.com/ruby-su/ruby.su
  3. Сделать локальную копию своей ветки, например:
    git clone git@github.com:yura/ruby.su.git
  4. Сделать изменение в своей локальной копии
  5. Сделать commit и push в свою ветку
  6. Сделать pull request
  7. Добавить основной репозиторий проекта в качестве upstream источника для своего локального репозитория:
    git remote add upstream git://github.com/ruby-su/ruby.su.git
  8. Периодически делать слияние с основным репозиторием:
    git fetch upstream
    git merge upstream/master

Дополнительная информация

02 07

Добавление комментариев к статьям с помощью acts_as_commentable_with_threading

Для добавление комментариев к статьям мы будем использовать acts_as_commentable_with_threading. Этот плагин не настолько популярен как acts_as_commentable, но позволяет делать комментарии в виде дерева.

Модель

Шаг 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