Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comments #19

Merged
merged 3 commits into from about 2 years ago

1 participant

Terence Ponce
Terence Ponce
Owner

I have added an article feature. This closes #6. The articles are polymorphic, so we can add it to future models.

Terence Ponce terenceponce merged commit 84ff894 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
3  app/assets/javascripts/comments.js.coffee
... ... @@ -0,0 +1,3 @@
  1 +# Place all the behaviors and hooks related to the matching controller here.
  2 +# All this logic will automatically be available in application.js.
  3 +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
3  app/assets/stylesheets/comments.css.scss
... ... @@ -0,0 +1,3 @@
  1 +// Place all the styles related to the Comments controller here.
  2 +// They will automatically be included in application.css.
  3 +// You can use Sass (SCSS) here: http://sass-lang.com/
2  app/controllers/articles_controller.rb
@@ -6,6 +6,8 @@ def index
6 6 end
7 7
8 8 def show
  9 + @comment = @article.comments.build
  10 + @comments = @article.comments.paginate(:page => params[:page])
9 11 end
10 12
11 13 def new
38 app/controllers/comments_controller.rb
... ... @@ -0,0 +1,38 @@
  1 +class CommentsController < ApplicationController
  2 + before_filter :authenticate_user!, :only => [:create]
  3 +
  4 + def index
  5 + @commentable = find_commentable
  6 + @comments = @commentable.comments
  7 +
  8 + if @commentable.respond_to? :title
  9 + @title = @commentable.title
  10 + end
  11 + end
  12 +
  13 + def create
  14 + @commentable = find_commentable
  15 + @comment = @commentable.comments.build(params[:comment])
  16 + unauthorized! if cannot? :create, @comment
  17 +
  18 + @comment.user = current_user
  19 + if @comment.save
  20 + flash[:success] = "Successfully submitted comment!"
  21 + redirect_to :back
  22 + else
  23 + flash[:error] = "Failed to submit comment"
  24 + redirect_to :back
  25 + end
  26 + end
  27 +
  28 + private
  29 +
  30 + def find_commentable
  31 + params.each do |name, value|
  32 + if name =~ /(.+)_id$/
  33 + return $1.classify.constantize.find(value)
  34 + end
  35 + end
  36 + nil
  37 + end
  38 +end
2  app/helpers/comments_helper.rb
... ... @@ -0,0 +1,2 @@
  1 +module CommentsHelper
  2 +end
8 app/models/ability.rb
@@ -9,6 +9,14 @@ def initialize(user)
9 9 else
10 10 can :read, :all
11 11
  12 + can :create, Comment
  13 + can :update, Comment do |comment|
  14 + comment.try(:user) == user
  15 + end
  16 + can :destroy, Comment do |comment|
  17 + comment.try(:user) == user
  18 + end
  19 +
12 20 if user.role?(:author)
13 21 can :create, Article
14 22 can :update, Article do |article|
1  app/models/article.rb
@@ -14,6 +14,7 @@ class Article < ActiveRecord::Base
14 14 attr_accessible :title, :content, :category_ids
15 15 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
16 16 has_and_belongs_to_many :categories
  17 + has_many :comments, :as => :commentable
17 18
18 19 validates :title, :presence => true
19 20 validates :content, :presence => true
9 app/models/comment.rb
... ... @@ -0,0 +1,9 @@
  1 +class Comment < ActiveRecord::Base
  2 + attr_accessible :content
  3 +
  4 + belongs_to :commentable, :polymorphic => true
  5 + belongs_to :user
  6 +
  7 + validates :content, :presence => true
  8 + validates :user, :presence => true
  9 +end
1  app/models/user.rb
@@ -30,6 +30,7 @@ class User < ActiveRecord::Base
30 30 attr_accessible :name, :roles
31 31
32 32 has_many :articles, :foreign_key => 'author_id', :dependent => :destroy
  33 + has_many :comments, :as => :commentable, :foreign_key => 'user_id', :dependent => :destroy
33 34
34 35 ROLES = %w[admin moderator author]
35 36
23 app/views/articles/show.html.erb
@@ -21,3 +21,26 @@
21 21 <% if can? :destroy, @article %>
22 22 <%= link_to 'Destroy', article_path(@article), :confirm => 'Are you sure?', :method => :delete %>
23 23 <% end %>
  24 +<h2>Comments</h2>
  25 +<div>
  26 + <% if @article.comments.any? %>
  27 + <ul class="comments">
  28 + <%= render @comments %>
  29 + </ul>
  30 + <%= will_paginate @comments %>
  31 + <% end %>
  32 + <% if user_signed_in? %>
  33 + <%= simple_form_for([@article, @comment]) do |f| %>
  34 + <%= render 'shared/error_messages', :object => f.object %>
  35 + <%= f.input :content, :label => 'Comment' %>
  36 +
  37 + <div class="form-actions">
  38 + <%= f.button :submit,
  39 + 'Submit comment',
  40 + :class => 'btn btn-large btn-primary' %>
  41 + </div>
  42 + <% end %>
  43 + <% else %>
  44 + <p>You must be <%= link_to 'logged in', new_user_session_path %> to comment</p>
  45 + <% end %>
  46 +</div>
7 app/views/comments/_comment.html.erb
... ... @@ -0,0 +1,7 @@
  1 +<li>
  2 + <span class="commenter"><%= comment.user.name %></span>
  3 + <span class="content"><%= comment.content %></span>
  4 + <span class="timestamp">
  5 + Posted <%= time_ago_in_words(comment.created_at) %> ago.
  6 + </span>
  7 +</li>
8 app/views/comments/_form.html.erb
... ... @@ -0,0 +1,8 @@
  1 +<%= simple_form_for([@commentable, Comment.new]) do |f| %>
  2 + <%= f.input :content, :label => 'Comment' %>
  3 +
  4 + <div class="form-actions">
  5 + <%= f.button :submit,
  6 + :class => 'btn btn-large btn-primary' %>
  7 + </div>
  8 +<% end %>
13 app/views/comments/index.html.erb
... ... @@ -0,0 +1,13 @@
  1 +<% provide(:title, "Comments on #{@title}") %>
  2 +
  3 +<section id="info">
  4 + <%= link_to @title, @commentable %>
  5 +</section>
  6 +
  7 +<ul id="comments">
  8 + <%= render @comments %>
  9 +</ul>
  10 +
  11 +<% if can? :create, Comment %>
  12 + <%= render 'form' %>
  13 +<% end %>
10 app/views/shared/_comment_form.html.erb
... ... @@ -0,0 +1,10 @@
  1 +<%= simple_form_for() do |f| %>
  2 + <%= render 'shared/error_messages', :object => f.object %>
  3 + <%= f.input :content %>
  4 +
  5 + <div class="form-actions">
  6 + <%= f.button :submit,
  7 + 'Submit comment',
  8 + :class => 'btn btn-large btn-primary' %>
  9 + </div>
  10 +<% end %>
4 config/routes.rb
@@ -9,7 +9,9 @@
9 9 get 'settings', :to => 'devise/registrations#edit', :as => :edit_user_registration
10 10 end
11 11
12   - resources :articles
  12 + resources :articles do
  13 + resources :comments, :except => [:new]
  14 + end
13 15 resources :categories
14 16
15 17 match '/contact', :to => 'static_pages#contact'
16 db/migrate/20120413005804_create_comments.rb
... ... @@ -0,0 +1,16 @@
  1 +class CreateComments < ActiveRecord::Migration
  2 + def change
  3 + create_table :comments do |t|
  4 + t.text :content
  5 + t.integer :commentable_id
  6 + t.string :commentable_type
  7 + t.integer :user_id
  8 +
  9 + t.timestamps
  10 + end
  11 +
  12 + add_index :comments, :user_id
  13 + add_index :comments, :commentable_type
  14 + add_index :comments, :commentable_id
  15 + end
  16 +end
15 db/schema.rb
@@ -11,7 +11,7 @@
11 11 #
12 12 # It's strongly recommended to check this file into your version control system.
13 13
14   -ActiveRecord::Schema.define(:version => 20120409085531) do
  14 +ActiveRecord::Schema.define(:version => 20120413005804) do
15 15
16 16 create_table "articles", :force => true do |t|
17 17 t.string "title"
@@ -35,6 +35,19 @@
35 35 t.datetime "updated_at", :null => false
36 36 end
37 37
  38 + create_table "comments", :force => true do |t|
  39 + t.text "content"
  40 + t.integer "commentable_id"
  41 + t.string "commentable_type"
  42 + t.integer "user_id"
  43 + t.datetime "created_at", :null => false
  44 + t.datetime "updated_at", :null => false
  45 + end
  46 +
  47 + add_index "comments", ["commentable_id"], :name => "index_comments_on_commentable_id"
  48 + add_index "comments", ["commentable_type"], :name => "index_comments_on_commentable_type"
  49 + add_index "comments", ["user_id"], :name => "index_comments_on_user_id"
  50 +
38 51 create_table "users", :force => true do |t|
39 52 t.string "email", :default => "", :null => false
40 53 t.string "encrypted_password", :default => "", :null => false
8 spec/fabricators/comment_fabricator.rb
... ... @@ -0,0 +1,8 @@
  1 +Fabricator(:comment) do
  2 + content "This is a comment"
  3 + user!
  4 +end
  5 +
  6 +Fabricator(:article_comment, :from => :comment) do
  7 + commentable!(:fabricator => :article)
  8 +end
10 spec/models/article_spec.rb
@@ -13,10 +13,9 @@
13 13 require 'spec_helper'
14 14
15 15 describe Article do
16   -
17   - let(:user) { Fabricate(:user) }
18 16 before do
19   - @article = user.articles.build(:title => 'Hello World', :content => 'Lorem Ipsum')
  17 + @author = Fabricate(:author)
  18 + @article = @author.articles.build(:title => 'Hello World', :content => 'Lorem Ipsum')
20 19 end
21 20
22 21 subject { @article }
@@ -24,7 +23,8 @@
24 23 it { should respond_to(:title) }
25 24 it { should respond_to(:content) }
26 25 it { should respond_to(:author_id) }
27   - its(:author) { should == user }
  26 + its(:author) { should == @author }
  27 + it { should respond_to(:comments) }
28 28
29 29 it { should be_valid }
30 30
@@ -46,7 +46,7 @@
46 46 describe 'accessible attributes' do
47 47 it 'should not allow access to author_id' do
48 48 expect do
49   - Article.new(:author_id => user.id)
  49 + Article.new(:author_id => @author.id)
50 50 end.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
51 51 end
52 52 end
46 spec/models/comment_spec.rb
... ... @@ -0,0 +1,46 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Comment do
  4 + before do
  5 + @user = Fabricate(:user)
  6 + @comment = Fabricate.build(:comment, :user => @user)
  7 + end
  8 +
  9 + subject { @comment }
  10 +
  11 + it { should respond_to(:content) }
  12 + it { should respond_to(:user) }
  13 + it { should respond_to(:commentable) }
  14 +
  15 + it { should be_valid }
  16 +
  17 + describe 'with blank content' do
  18 + before { @comment.content = ' ' }
  19 + it { should_not be_valid }
  20 + end
  21 +
  22 + describe 'with no user' do
  23 + before { @comment.user = nil }
  24 + it { should_not be_valid }
  25 + end
  26 +
  27 + describe 'accessible attributes' do
  28 + it 'should not allow access to user_id' do
  29 + expect do
  30 + Comment.new(:user_id => @user.id)
  31 + end.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
  32 + end
  33 +
  34 + it 'should not allow access to commentable_id' do
  35 + expect do
  36 + Comment.new(:commentable_id => 1)
  37 + end.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
  38 + end
  39 +
  40 + it 'should not allow access to commentable_type' do
  41 + expect do
  42 + Comment.new(:commentable_type => 'Something')
  43 + end.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
  44 + end
  45 + end
  46 +end
1  spec/models/user_spec.rb
@@ -45,6 +45,7 @@
45 45 it { should respond_to(:name) }
46 46 it { should respond_to(:articles) }
47 47 it { should respond_to(:roles) }
  48 + it { should respond_to(:comments) }
48 49
49 50 it { should be_valid }
50 51
61 spec/requests/articles_pages_spec.rb
@@ -11,7 +11,51 @@
11 11 before { visit articles_path }
12 12
13 13 it { should have_page_title 'Articles' }
14   - it { should_not have_page_heading 'Post article' }
  14 + it { should have_page_heading 'Articles' }
  15 + it { should_not have_link 'New article' }
  16 + it { should_not have_link 'Edit' }
  17 + it { should_not have_link 'Destroy' }
  18 + end
  19 +
  20 + describe 'in the show page' do
  21 +
  22 + before do
  23 + @article = Fabricate(:article)
  24 + visit article_path(@article)
  25 + end
  26 +
  27 + it { should have_page_title @article.title }
  28 + it { should have_page_heading @article.title }
  29 + it { should have_link 'Back to articles' }
  30 + it { should_not have_link 'Edit' }
  31 + it { should_not have_link 'Destroy' }
  32 +
  33 + describe 'comments section' do
  34 + it { should have_content 'Comments' }
  35 + it { should have_content 'You must be logged in to comment' }
  36 + it { should_not have_button 'Submit comment' }
  37 + end
  38 + end
  39 +
  40 + describe 'in the new page' do
  41 +
  42 + before { visit new_article_path }
  43 +
  44 + it { should have_error_message 'Access denied' }
  45 + it { should have_page_title '' }
  46 + it { should have_page_heading 'Developers Connect' }
  47 + end
  48 +
  49 + describe 'in the edit page' do
  50 +
  51 + before do
  52 + @article = Fabricate(:article)
  53 + visit edit_article_path(@article)
  54 + end
  55 +
  56 + it { should have_error_message 'Access denied' }
  57 + it { should have_page_title '' }
  58 + it { should have_page_heading 'Developers Connect' }
15 59 end
16 60 end
17 61
@@ -48,6 +92,21 @@
48 92 it { should have_link 'Back to articles' }
49 93 it { should_not have_link 'Edit' }
50 94 it { should_not have_link 'Destroy' }
  95 +
  96 + describe 'comments section' do
  97 + it { should have_content 'Comments' }
  98 + it { should_not have_content 'You must be logged in to comment' }
  99 +
  100 + describe 'on posting comments' do
  101 + before do
  102 + fill_in 'Comment', :with => 'Foobar'
  103 + click_button 'Submit comment'
  104 + end
  105 +
  106 + it { should have_content 'Foobar' }
  107 + it { should have_content @user.name }
  108 + end
  109 + end
51 110 end
52 111
53 112 describe 'in the new page' do
14 spec/requests/authentication_pages_spec.rb
@@ -134,7 +134,6 @@
134 134 describe 'when submitting to the destroy action' do
135 135
136 136 before do
137   -
138 137 @category = Fabricate(:category)
139 138 delete category_path(@category)
140 139 end
@@ -142,6 +141,19 @@
142 141 specify { response.should redirect_to(root_path) }
143 142 end
144 143 end
  144 +
  145 + describe 'in the Comments controller' do
  146 +
  147 + describe 'when submitting to the create action' do
  148 +
  149 + before do
  150 + @article = Fabricate(:article)
  151 + post article_comments_path @article
  152 + end
  153 +
  154 + specify { response.should redirect_to(new_user_session_path) }
  155 + end
  156 + end
145 157 end
146 158 end
147 159 end

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.