Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Comments #19

Merged
merged 3 commits into from

1 participant

@terenceponce

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

@terenceponce terenceponce merged commit 84ff894 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 15, 2012
  1. Add comment model

    authored
Commits on Apr 16, 2012
Commits on Apr 18, 2012
This page is out of date. Refresh to see the latest.
View
3  app/assets/javascripts/comments.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
View
3  app/assets/stylesheets/comments.css.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Comments controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
View
2  app/controllers/articles_controller.rb
@@ -6,6 +6,8 @@ def index
end
def show
+ @comment = @article.comments.build
+ @comments = @article.comments.paginate(:page => params[:page])
end
def new
View
38 app/controllers/comments_controller.rb
@@ -0,0 +1,38 @@
+class CommentsController < ApplicationController
+ before_filter :authenticate_user!, :only => [:create]
+
+ def index
+ @commentable = find_commentable
+ @comments = @commentable.comments
+
+ if @commentable.respond_to? :title
+ @title = @commentable.title
+ end
+ end
+
+ def create
+ @commentable = find_commentable
+ @comment = @commentable.comments.build(params[:comment])
+ unauthorized! if cannot? :create, @comment
+
+ @comment.user = current_user
+ if @comment.save
+ flash[:success] = "Successfully submitted comment!"
+ redirect_to :back
+ else
+ flash[:error] = "Failed to submit comment"
+ redirect_to :back
+ end
+ end
+
+ private
+
+ def find_commentable
+ params.each do |name, value|
+ if name =~ /(.+)_id$/
+ return $1.classify.constantize.find(value)
+ end
+ end
+ nil
+ end
+end
View
2  app/helpers/comments_helper.rb
@@ -0,0 +1,2 @@
+module CommentsHelper
+end
View
8 app/models/ability.rb
@@ -9,6 +9,14 @@ def initialize(user)
else
can :read, :all
+ can :create, Comment
+ can :update, Comment do |comment|
+ comment.try(:user) == user
+ end
+ can :destroy, Comment do |comment|
+ comment.try(:user) == user
+ end
+
if user.role?(:author)
can :create, Article
can :update, Article do |article|
View
1  app/models/article.rb
@@ -14,6 +14,7 @@ class Article < ActiveRecord::Base
attr_accessible :title, :content, :category_ids
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
has_and_belongs_to_many :categories
+ has_many :comments, :as => :commentable
validates :title, :presence => true
validates :content, :presence => true
View
9 app/models/comment.rb
@@ -0,0 +1,9 @@
+class Comment < ActiveRecord::Base
+ attr_accessible :content
+
+ belongs_to :commentable, :polymorphic => true
+ belongs_to :user
+
+ validates :content, :presence => true
+ validates :user, :presence => true
+end
View
1  app/models/user.rb
@@ -30,6 +30,7 @@ class User < ActiveRecord::Base
attr_accessible :name, :roles
has_many :articles, :foreign_key => 'author_id', :dependent => :destroy
+ has_many :comments, :as => :commentable, :foreign_key => 'user_id', :dependent => :destroy
ROLES = %w[admin moderator author]
View
23 app/views/articles/show.html.erb
@@ -21,3 +21,26 @@
<% if can? :destroy, @article %>
<%= link_to 'Destroy', article_path(@article), :confirm => 'Are you sure?', :method => :delete %>
<% end %>
+<h2>Comments</h2>
+<div>
+ <% if @article.comments.any? %>
+ <ul class="comments">
+ <%= render @comments %>
+ </ul>
+ <%= will_paginate @comments %>
+ <% end %>
+ <% if user_signed_in? %>
+ <%= simple_form_for([@article, @comment]) do |f| %>
+ <%= render 'shared/error_messages', :object => f.object %>
+ <%= f.input :content, :label => 'Comment' %>
+
+ <div class="form-actions">
+ <%= f.button :submit,
+ 'Submit comment',
+ :class => 'btn btn-large btn-primary' %>
+ </div>
+ <% end %>
+ <% else %>
+ <p>You must be <%= link_to 'logged in', new_user_session_path %> to comment</p>
+ <% end %>
+</div>
View
7 app/views/comments/_comment.html.erb
@@ -0,0 +1,7 @@
+<li>
+ <span class="commenter"><%= comment.user.name %></span>
+ <span class="content"><%= comment.content %></span>
+ <span class="timestamp">
+ Posted <%= time_ago_in_words(comment.created_at) %> ago.
+ </span>
+</li>
View
8 app/views/comments/_form.html.erb
@@ -0,0 +1,8 @@
+<%= simple_form_for([@commentable, Comment.new]) do |f| %>
+ <%= f.input :content, :label => 'Comment' %>
+
+ <div class="form-actions">
+ <%= f.button :submit,
+ :class => 'btn btn-large btn-primary' %>
+ </div>
+<% end %>
View
13 app/views/comments/index.html.erb
@@ -0,0 +1,13 @@
+<% provide(:title, "Comments on #{@title}") %>
+
+<section id="info">
+ <%= link_to @title, @commentable %>
+</section>
+
+<ul id="comments">
+ <%= render @comments %>
+</ul>
+
+<% if can? :create, Comment %>
+ <%= render 'form' %>
+<% end %>
View
10 app/views/shared/_comment_form.html.erb
@@ -0,0 +1,10 @@
+<%= simple_form_for() do |f| %>
+ <%= render 'shared/error_messages', :object => f.object %>
+ <%= f.input :content %>
+
+ <div class="form-actions">
+ <%= f.button :submit,
+ 'Submit comment',
+ :class => 'btn btn-large btn-primary' %>
+ </div>
+<% end %>
View
4 config/routes.rb
@@ -9,7 +9,9 @@
get 'settings', :to => 'devise/registrations#edit', :as => :edit_user_registration
end
- resources :articles
+ resources :articles do
+ resources :comments, :except => [:new]
+ end
resources :categories
match '/contact', :to => 'static_pages#contact'
View
16 db/migrate/20120413005804_create_comments.rb
@@ -0,0 +1,16 @@
+class CreateComments < ActiveRecord::Migration
+ def change
+ create_table :comments do |t|
+ t.text :content
+ t.integer :commentable_id
+ t.string :commentable_type
+ t.integer :user_id
+
+ t.timestamps
+ end
+
+ add_index :comments, :user_id
+ add_index :comments, :commentable_type
+ add_index :comments, :commentable_id
+ end
+end
View
15 db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20120409085531) do
+ActiveRecord::Schema.define(:version => 20120413005804) do
create_table "articles", :force => true do |t|
t.string "title"
@@ -35,6 +35,19 @@
t.datetime "updated_at", :null => false
end
+ create_table "comments", :force => true do |t|
+ t.text "content"
+ t.integer "commentable_id"
+ t.string "commentable_type"
+ t.integer "user_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ add_index "comments", ["commentable_id"], :name => "index_comments_on_commentable_id"
+ add_index "comments", ["commentable_type"], :name => "index_comments_on_commentable_type"
+ add_index "comments", ["user_id"], :name => "index_comments_on_user_id"
+
create_table "users", :force => true do |t|
t.string "email", :default => "", :null => false
t.string "encrypted_password", :default => "", :null => false
View
8 spec/fabricators/comment_fabricator.rb
@@ -0,0 +1,8 @@
+Fabricator(:comment) do
+ content "This is a comment"
+ user!
+end
+
+Fabricator(:article_comment, :from => :comment) do
+ commentable!(:fabricator => :article)
+end
View
10 spec/models/article_spec.rb
@@ -13,10 +13,9 @@
require 'spec_helper'
describe Article do
-
- let(:user) { Fabricate(:user) }
before do
- @article = user.articles.build(:title => 'Hello World', :content => 'Lorem Ipsum')
+ @author = Fabricate(:author)
+ @article = @author.articles.build(:title => 'Hello World', :content => 'Lorem Ipsum')
end
subject { @article }
@@ -24,7 +23,8 @@
it { should respond_to(:title) }
it { should respond_to(:content) }
it { should respond_to(:author_id) }
- its(:author) { should == user }
+ its(:author) { should == @author }
+ it { should respond_to(:comments) }
it { should be_valid }
@@ -46,7 +46,7 @@
describe 'accessible attributes' do
it 'should not allow access to author_id' do
expect do
- Article.new(:author_id => user.id)
+ Article.new(:author_id => @author.id)
end.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
View
46 spec/models/comment_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe Comment do
+ before do
+ @user = Fabricate(:user)
+ @comment = Fabricate.build(:comment, :user => @user)
+ end
+
+ subject { @comment }
+
+ it { should respond_to(:content) }
+ it { should respond_to(:user) }
+ it { should respond_to(:commentable) }
+
+ it { should be_valid }
+
+ describe 'with blank content' do
+ before { @comment.content = ' ' }
+ it { should_not be_valid }
+ end
+
+ describe 'with no user' do
+ before { @comment.user = nil }
+ it { should_not be_valid }
+ end
+
+ describe 'accessible attributes' do
+ it 'should not allow access to user_id' do
+ expect do
+ Comment.new(:user_id => @user.id)
+ end.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
+ end
+
+ it 'should not allow access to commentable_id' do
+ expect do
+ Comment.new(:commentable_id => 1)
+ end.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
+ end
+
+ it 'should not allow access to commentable_type' do
+ expect do
+ Comment.new(:commentable_type => 'Something')
+ end.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
+ end
+ end
+end
View
1  spec/models/user_spec.rb
@@ -45,6 +45,7 @@
it { should respond_to(:name) }
it { should respond_to(:articles) }
it { should respond_to(:roles) }
+ it { should respond_to(:comments) }
it { should be_valid }
View
61 spec/requests/articles_pages_spec.rb
@@ -11,7 +11,51 @@
before { visit articles_path }
it { should have_page_title 'Articles' }
- it { should_not have_page_heading 'Post article' }
+ it { should have_page_heading 'Articles' }
+ it { should_not have_link 'New article' }
+ it { should_not have_link 'Edit' }
+ it { should_not have_link 'Destroy' }
+ end
+
+ describe 'in the show page' do
+
+ before do
+ @article = Fabricate(:article)
+ visit article_path(@article)
+ end
+
+ it { should have_page_title @article.title }
+ it { should have_page_heading @article.title }
+ it { should have_link 'Back to articles' }
+ it { should_not have_link 'Edit' }
+ it { should_not have_link 'Destroy' }
+
+ describe 'comments section' do
+ it { should have_content 'Comments' }
+ it { should have_content 'You must be logged in to comment' }
+ it { should_not have_button 'Submit comment' }
+ end
+ end
+
+ describe 'in the new page' do
+
+ before { visit new_article_path }
+
+ it { should have_error_message 'Access denied' }
+ it { should have_page_title '' }
+ it { should have_page_heading 'Developers Connect' }
+ end
+
+ describe 'in the edit page' do
+
+ before do
+ @article = Fabricate(:article)
+ visit edit_article_path(@article)
+ end
+
+ it { should have_error_message 'Access denied' }
+ it { should have_page_title '' }
+ it { should have_page_heading 'Developers Connect' }
end
end
@@ -48,6 +92,21 @@
it { should have_link 'Back to articles' }
it { should_not have_link 'Edit' }
it { should_not have_link 'Destroy' }
+
+ describe 'comments section' do
+ it { should have_content 'Comments' }
+ it { should_not have_content 'You must be logged in to comment' }
+
+ describe 'on posting comments' do
+ before do
+ fill_in 'Comment', :with => 'Foobar'
+ click_button 'Submit comment'
+ end
+
+ it { should have_content 'Foobar' }
+ it { should have_content @user.name }
+ end
+ end
end
describe 'in the new page' do
View
14 spec/requests/authentication_pages_spec.rb
@@ -134,7 +134,6 @@
describe 'when submitting to the destroy action' do
before do
-
@category = Fabricate(:category)
delete category_path(@category)
end
@@ -142,6 +141,19 @@
specify { response.should redirect_to(root_path) }
end
end
+
+ describe 'in the Comments controller' do
+
+ describe 'when submitting to the create action' do
+
+ before do
+ @article = Fabricate(:article)
+ post article_comments_path @article
+ end
+
+ specify { response.should redirect_to(new_user_session_path) }
+ end
+ end
end
end
end
Something went wrong with that request. Please try again.