From ade54533e6ed51c661f7f322c3caa0a8f21fd447 Mon Sep 17 00:00:00 2001 From: Andrew Tomaka Date: Tue, 14 Jul 2015 16:29:22 -0400 Subject: [PATCH] Add ability to create posts --- app/assets/javascripts/posts.coffee | 3 + app/assets/stylesheets/posts.scss | 3 + app/controllers/posts_controller.rb | 55 ++++++++ app/models/post.rb | 15 ++ app/models/subcreddit.rb | 1 + app/views/posts/_form.html.slim | 7 + app/views/posts/create.html.slim | 0 app/views/posts/destroy.html.slim | 0 app/views/posts/edit.html.slim | 1 + app/views/posts/new.html.slim | 1 + app/views/posts/show.html.slim | 2 + app/views/posts/update.html.slim | 0 app/views/subcreddits/show.html.slim | 5 + config/routes.rb | 5 +- db/migrate/20150714200610_create_posts.rb | 13 ++ db/schema.rb | 15 +- spec/controllers/posts_controller_spec.rb | 162 ++++++++++++++++++++++ spec/factories/post_factory.rb | 9 ++ spec/features/posts/edit_post_spec.rb | 32 +++++ spec/features/posts/lists_posts_spec.rb | 15 ++ spec/features/posts/new_post_spec.rb | 49 +++++++ spec/models/post_spec.rb | 54 ++++++++ 22 files changed, 445 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/posts.coffee create mode 100644 app/assets/stylesheets/posts.scss create mode 100644 app/controllers/posts_controller.rb create mode 100644 app/models/post.rb create mode 100644 app/views/posts/_form.html.slim create mode 100644 app/views/posts/create.html.slim create mode 100644 app/views/posts/destroy.html.slim create mode 100644 app/views/posts/edit.html.slim create mode 100644 app/views/posts/new.html.slim create mode 100644 app/views/posts/show.html.slim create mode 100644 app/views/posts/update.html.slim create mode 100644 db/migrate/20150714200610_create_posts.rb create mode 100644 spec/controllers/posts_controller_spec.rb create mode 100644 spec/factories/post_factory.rb create mode 100644 spec/features/posts/edit_post_spec.rb create mode 100644 spec/features/posts/lists_posts_spec.rb create mode 100644 spec/features/posts/new_post_spec.rb create mode 100644 spec/models/post_spec.rb diff --git a/app/assets/javascripts/posts.coffee b/app/assets/javascripts/posts.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/posts.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://coffeescript.org/ diff --git a/app/assets/stylesheets/posts.scss b/app/assets/stylesheets/posts.scss new file mode 100644 index 0000000..ed4dfd1 --- /dev/null +++ b/app/assets/stylesheets/posts.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Posts controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb new file mode 100644 index 0000000..e854a07 --- /dev/null +++ b/app/controllers/posts_controller.rb @@ -0,0 +1,55 @@ +class PostsController < ApplicationController + before_filter :set_post, except: [:index, :new, :create] + before_filter :set_subcreddit + + def show + end + + def new + @post = Post.new + end + + def create + @post = @subcreddit.posts.build(post_params) + @post.user = current_user + + if @post.save + redirect_to subcreddit_post_path(@subcreddit, @post), + notice: 'Post created' + else + render :new + end + end + + def edit + end + + def update + if @post.update(post_params) + redirect_to subcreddit_post_path(@subcreddit, @post), + notice: 'Post was updated' + else + render :edit + end + end + + def destroy + @post.destroy + + redirect_to subcreddits_path(@subcreddit), notice: 'Post was deleted' + end + + private + + def post_params + params.require(:post).permit(:title, :link, :content, :subcreddit_id) + end + + def set_subcreddit + @subcreddit = Subcreddit.find_by_slug(params['subcreddit_id']) + end + + def set_post + @post = Post.find(params[:id]) + end +end diff --git a/app/models/post.rb b/app/models/post.rb new file mode 100644 index 0000000..1cde9dc --- /dev/null +++ b/app/models/post.rb @@ -0,0 +1,15 @@ +class Post < ActiveRecord::Base + belongs_to :user + belongs_to :subcreddit + + validates :title, + presence: true, + length: { maximum: 300 } + + validates :content, + length: { maximum: 15000 } + + def to_param + "#{self.id}-#{self.title.parameterize}" + end +end diff --git a/app/models/subcreddit.rb b/app/models/subcreddit.rb index e481b30..b9a3c63 100644 --- a/app/models/subcreddit.rb +++ b/app/models/subcreddit.rb @@ -2,6 +2,7 @@ RESERVED_SLUGS = %w(new edit) class Subcreddit < ActiveRecord::Base belongs_to :owner, class_name: 'User' + has_many :posts attr_accessor :closed diff --git a/app/views/posts/_form.html.slim b/app/views/posts/_form.html.slim new file mode 100644 index 0000000..69ae3c3 --- /dev/null +++ b/app/views/posts/_form.html.slim @@ -0,0 +1,7 @@ += simple_form_for [subcreddit, post] do |f| + .form-inputs + = f.input :title + = f.input :link + = f.input :content + .form-actions + = f.button :submit diff --git a/app/views/posts/create.html.slim b/app/views/posts/create.html.slim new file mode 100644 index 0000000..e69de29 diff --git a/app/views/posts/destroy.html.slim b/app/views/posts/destroy.html.slim new file mode 100644 index 0000000..e69de29 diff --git a/app/views/posts/edit.html.slim b/app/views/posts/edit.html.slim new file mode 100644 index 0000000..12d6097 --- /dev/null +++ b/app/views/posts/edit.html.slim @@ -0,0 +1 @@ += render 'form', subcreddit: @subcreddit, post: @post diff --git a/app/views/posts/new.html.slim b/app/views/posts/new.html.slim new file mode 100644 index 0000000..12d6097 --- /dev/null +++ b/app/views/posts/new.html.slim @@ -0,0 +1 @@ += render 'form', subcreddit: @subcreddit, post: @post diff --git a/app/views/posts/show.html.slim b/app/views/posts/show.html.slim new file mode 100644 index 0000000..d57a53a --- /dev/null +++ b/app/views/posts/show.html.slim @@ -0,0 +1,2 @@ +h1= @post.title += @post.content diff --git a/app/views/posts/update.html.slim b/app/views/posts/update.html.slim new file mode 100644 index 0000000..e69de29 diff --git a/app/views/subcreddits/show.html.slim b/app/views/subcreddits/show.html.slim index 156ddef..e401b25 100644 --- a/app/views/subcreddits/show.html.slim +++ b/app/views/subcreddits/show.html.slim @@ -1,3 +1,8 @@ h1= @subcreddit.name - if @subcreddit.closed? = "Board has been closed" +- else + = link_to 'New Post', new_subcreddit_post_path(@subcreddit) + ul + - @subcreddit.posts.each do |post| + li= link_to post.title, [@subcreddit, post] diff --git a/config/routes.rb b/config/routes.rb index 414bc68..f1d4a29 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,7 +3,10 @@ Rails.application.routes.draw do get 'signin', to: 'user_sessions#new', as: :signin get 'signout', to: 'user_sessions#destroy', as: :signout - resources :subcreddits, path: 'c', except: [:destroy] + resources :subcreddits, path: 'c', except: [:destroy] do + resources :posts, except: [:index] + end + resources :user_sessions, only: [:new, :create, :destroy] resources :users, only: [:new, :create] diff --git a/db/migrate/20150714200610_create_posts.rb b/db/migrate/20150714200610_create_posts.rb new file mode 100644 index 0000000..91a9108 --- /dev/null +++ b/db/migrate/20150714200610_create_posts.rb @@ -0,0 +1,13 @@ +class CreatePosts < ActiveRecord::Migration + def change + create_table :posts do |t| + t.references :user, index: true, foreign_key: true + t.references :subcreddit, index: true, foreign_key: true + t.string :title + t.string :link + t.text :content + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index dd2c75c..f582bac 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,20 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150713045745) do +ActiveRecord::Schema.define(version: 20150714200610) do + + create_table "posts", force: :cascade do |t| + t.integer "user_id" + t.integer "subcreddit_id" + t.string "title" + t.string "link" + t.text "content" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "posts", ["subcreddit_id"], name: "index_posts_on_subcreddit_id" + add_index "posts", ["user_id"], name: "index_posts_on_user_id" create_table "subcreddits", force: :cascade do |t| t.integer "owner_id" diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb new file mode 100644 index 0000000..807857a --- /dev/null +++ b/spec/controllers/posts_controller_spec.rb @@ -0,0 +1,162 @@ +require 'rails_helper' + +describe PostsController, type: :controller do + let!(:user) { build(:user) } + let!(:subcreddit) { create(:subcreddit) } + let(:data) do + { + title: 'A New Post', + content: 'Here is some content for that post' + } + end + before(:each) do + allow_any_instance_of(ApplicationController) + .to receive(:current_user).and_return(user) + end + + describe '#show' do + let(:post) { create(:post) } + before(:each) { get :show, subcreddit_id: post.subcreddit, id: post } + + + it 'should render :show' do + expect(response).to render_template(:show) + end + + it 'should assign correct Post to @post' do + expect(assigns(:post)).to eq(post) + end + end + + describe '#new' do + let(:post) { build(:post) } + before(:each) { get :new, subcreddit_id: post.subcreddit } + + it 'should render :new' do + expect(response).to render_template(:new) + end + + it 'should assign new Post to @post' do + expect(assigns(:post)).to be_a_new(Post) + end + end + + describe '#create' do + + context 'with valid data' do + it 'should create a post' do + expect { post :create, subcreddit_id: subcreddit, post: data } + .to change(Post, :count).by(1) + end + + it 'should redirect to the new post' do + expect(post :create, subcreddit_id: subcreddit, post: data) + .to redirect_to(subcreddit_post_path(assigns(:post).subcreddit, + assigns(:post))) + end + + it 'should send a notice flash message' do + post :create, subcreddit_id: subcreddit, post: data + + expect(flash[:notice]).to be_present + end + end + + context 'with invalid data' do + before(:each) { data['title'] = '' } + + it 'should not create a post' do + expect { post :create, subcreddit_id: subcreddit, post: data } + .to change(Post, :count).by(0) + end + + it 'should render :new' do + post :create, subcreddit_id: subcreddit, post: data + + expect(response).to render_template(:new) + end + end + end + + context '#edit' do + let!(:post) { create(:post) } + before(:each) { get :edit, id: post, subcreddit_id: post.subcreddit } + + context 'with valid post' do + it 'should render :edit' do + expect(response).to render_template(:edit) + end + + it 'should assign correct Post to @post' do + expect(assigns(:post)).to eq(post) + end + end + end + + context '#update' do + let!(:post) { create(:post) } + let(:data) do + { + title: 'New title', + content: 'New content' + } + end + + context 'with valid data' do + before(:each) do + put :update, id: post, subcreddit_id: post.subcreddit, post: data + end + + it 'should assign correct Post to @post' do + expect(assigns(:post)).to eq(post) + end + + it 'should update the post' do + post.reload + + expect(post.title).to eq(data[:title]) + expect(post.content).to eq(data[:content]) + end + + it 'should redirect to the post' do + expect(response) + .to redirect_to(subcreddit_post_path(assigns(:post).subcreddit, + assigns(:post))) + end + + it 'should display a notice flash message' do + expect(flash[:notice]).to be_present + end + end + + context 'with invalid data' do + before(:each) { data[:title] = '' } + + it 'should render :edit' do + put :update, id: post, subcreddit_id: post.subcreddit, post: data + + expect(response).to render_template(:edit) + end + end + end + + context '#destroy' do + let!(:post) { create(:post, subcreddit: subcreddit) } + + it 'should delete the post' do + expect { delete :destroy, subcreddit_id: subcreddit, id: post } + .to change(Post, :count).by(-1) + end + + it 'should redirect to the subcreddit index' do + expect(delete :destroy, subcreddit_id: subcreddit, id: post) + .to redirect_to(subcreddits_path(subcreddit)) + end + + it 'should flash notify that the post was deleted' do + delete :destroy, subcreddit_id: subcreddit, id: post + + expect(flash[:notice]).to be_present + end + end +end diff --git a/spec/factories/post_factory.rb b/spec/factories/post_factory.rb new file mode 100644 index 0000000..56ae495 --- /dev/null +++ b/spec/factories/post_factory.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :post do + user + subcreddit + title { Faker::Lorem.sentence } + link '' + content { Faker::Lorem.paragraph } + end +end diff --git a/spec/features/posts/edit_post_spec.rb b/spec/features/posts/edit_post_spec.rb new file mode 100644 index 0000000..c0e3cf2 --- /dev/null +++ b/spec/features/posts/edit_post_spec.rb @@ -0,0 +1,32 @@ +require 'rails_helper' + +describe 'Edit Post', type: :feature do + context 'when signed in' do + let!(:user) { create(:user) } + let!(:subcreddit) { create(:subcreddit) } + let!(:post) { create(:post, subcreddit: subcreddit, user: user) } + let(:new_post) { build_stubbed(:post) } + + before(:each) { signin(user: user) } + + context 'with valid data' do + before(:each) do + visit edit_subcreddit_post_path(subcreddit, post) + + fill_in :post_title, with: new_post.title + fill_in :post_link, with: new_post.link + fill_in :post_content, with: new_post.content + + click_button 'Update Post' + end + + it 'should notify that the post was edited' do + expect(page).to have_content('updated') + end + + it 'should show the post' do + expect(page).to have_content(new_post.title) + end + end + end +end diff --git a/spec/features/posts/lists_posts_spec.rb b/spec/features/posts/lists_posts_spec.rb new file mode 100644 index 0000000..9fa1eb9 --- /dev/null +++ b/spec/features/posts/lists_posts_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +describe 'List Posts', type: :feature do + let!(:subcreddit) { create(:subcreddit) } + let!(:posts) { 10.times.collect { create(:post, subcreddit: subcreddit) } } + + it 'should list all posts for a subcreddit' do + visit subcreddit_path(subcreddit) + + posts.each do |post| + expect(page) + .to have_link(post.title, subcreddit_post_path(post, post.subcreddit)) + end + end +end diff --git a/spec/features/posts/new_post_spec.rb b/spec/features/posts/new_post_spec.rb new file mode 100644 index 0000000..2d37634 --- /dev/null +++ b/spec/features/posts/new_post_spec.rb @@ -0,0 +1,49 @@ +require 'rails_helper' + +describe 'New Post', type: :feature do + context 'when signed in' do + let!(:user) { create(:user) } + let!(:subcreddit) { create(:subcreddit) } + let!(:post) { build(:post, subcreddit: subcreddit) } + + before(:each) { signin(user: user) } + + context 'with valid data' do + before(:each) do + visit new_subcreddit_post_path(subcreddit) + + fill_in :post_title, with: post.title + fill_in :post_link, with: post.link + fill_in :post_content, with: post.content + + click_button 'Create Post' + end + + it 'should notify that a new post was created' do + expect(page).to have_content('created') + end + + it 'should display the new post' do + expect(page).to have_content(post.title) + end + end + + context 'with invalid data' do + before(:each) do + visit new_subcreddit_post_path(subcreddit) + + post.title = '' + + fill_in :post_title, with: post.title + fill_in :post_link, with: post.link + fill_in :post_content, with: post.content + + click_button 'Create Post' + end + + it 'should display errors' do + expect(page).to have_content("can't be blank") + end + end + end +end diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb new file mode 100644 index 0000000..f4d88bd --- /dev/null +++ b/spec/models/post_spec.rb @@ -0,0 +1,54 @@ +require 'rails_helper' + +describe Post, type: :model do + let(:post) { build(:post) } + + it { should belong_to(:user) } + it { should belong_to(:subcreddit) } + + context 'with valid data' do + it 'should be valid' do + expect(post).to be_valid + end + + it 'should allow blank content' do + post.content = '' + + expect(post).to be_valid + end + + it 'should allow a blank link' do + post.link = '' + + expect(post).to be_valid + end + end + + context 'with invalid data' do + it 'should not allow a blank title' do + post.title = '' + + expect(post).to be_invalid + end + + it 'should not allow a long title' do + post.title = 'a' * 301 + + expect(post).to be_invalid + end + + it 'should not allow long content' do + post.content = 'a' * 15001 + + expect(post).to be_invalid + end + end + + context '#to_param' do + it 'generates the correct param' do + post.save + + expect(post.to_param).to eq("#{post.id}-#{post.title.parameterize}") + end + end +end