Add the subcreddit MVC
Notes on closing subcreddit: This doesn't seem great. Would be slightly better if closed could be referenced as true or false instead of '0' or '1' in the model. Testing this is a bit messy, but Tiemcop makes it a bit better. Notes on subcreddit validations: This is still not ideal. For starters, from http://guides.rubyonrails.org/active_record_validations.html: "Note that some databases are configured to perform case-insensitive searches anyway." It also seems difficult to validate fields that are set in before_save since validations occur before callbacks. Revisit this (perhaps friendly_id wll save us).
This commit is contained in:
parent
527dc493c6
commit
d3dd0ff848
22 changed files with 450 additions and 22 deletions
1
Gemfile
1
Gemfile
|
@ -50,4 +50,5 @@ group :test do
|
|||
gem 'capybara-webkit'
|
||||
gem 'shoulda'
|
||||
gem 'simplecov', require: false
|
||||
gem 'timecop'
|
||||
end
|
||||
|
|
|
@ -338,6 +338,7 @@ GEM
|
|||
thor (0.19.1)
|
||||
thread_safe (0.3.5)
|
||||
tilt (1.4.1)
|
||||
timecop (0.7.4)
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
uglifier (2.7.1)
|
||||
|
@ -392,6 +393,7 @@ DEPENDENCIES
|
|||
spring
|
||||
spring-commands-rspec
|
||||
sqlite3
|
||||
timecop
|
||||
uglifier (>= 1.3.0)
|
||||
|
||||
BUNDLED WITH
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
// Place all the styles related to the Tests controller here.
|
||||
// Place all the styles related to the Subcreddits controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
50
app/controllers/subcreddits_controller.rb
Normal file
50
app/controllers/subcreddits_controller.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
class SubcredditsController < ApplicationController
|
||||
before_filter :set_subcreddit, only: [:show, :edit, :update]
|
||||
|
||||
def index
|
||||
@subcreddits = Subcreddit.all
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def new
|
||||
@subcreddit = Subcreddit.new
|
||||
end
|
||||
|
||||
def create
|
||||
@subcreddit = Subcreddit.new(create_subcreddit_params)
|
||||
@subcreddit.owner = current_user
|
||||
|
||||
if @subcreddit.save
|
||||
redirect_to @subcreddit, notice: 'Subcreddit was created!'
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if @subcreddit.update(update_subcreddit_params)
|
||||
redirect_to @subcreddit, notice: 'Subcreddit was updated!'
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_subcreddit_params
|
||||
params.require(:subcreddit).permit(:name)
|
||||
end
|
||||
|
||||
def update_subcreddit_params
|
||||
params.require(:subcreddit).permit(:closed)
|
||||
end
|
||||
|
||||
def set_subcreddit
|
||||
@subcreddit = Subcreddit.find_by_slug(params[:id])
|
||||
end
|
||||
end
|
|
@ -1,7 +0,0 @@
|
|||
class TestsController < ApplicationController
|
||||
def index
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
end
|
53
app/models/subcreddit.rb
Normal file
53
app/models/subcreddit.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
RESERVED_SLUGS = %w(new edit)
|
||||
|
||||
class Subcreddit < ActiveRecord::Base
|
||||
belongs_to :owner, class_name: 'User'
|
||||
|
||||
attr_accessor :closed
|
||||
|
||||
before_save :set_slug
|
||||
before_save :set_closed_at
|
||||
|
||||
validates :name,
|
||||
presence: true,
|
||||
format: /\A(?! )[a-z0-9 ]*(?<! )\z/i,
|
||||
uniqueness: { case_sensitive: false },
|
||||
length: { minimum: 3, maximum: 21 }
|
||||
|
||||
validate :slug_is_not_a_route
|
||||
|
||||
validates :closed,
|
||||
format: /\A[01]?\z/
|
||||
|
||||
def to_param
|
||||
self.slug
|
||||
end
|
||||
|
||||
def closed?
|
||||
self.closed_at != nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_slug
|
||||
self.slug = sluggify_name
|
||||
end
|
||||
|
||||
def sluggify_name
|
||||
self.name.downcase.tr(' ', '_')
|
||||
end
|
||||
|
||||
def set_closed_at
|
||||
if closed == '1' && closed_at == nil
|
||||
self.closed_at = Time.now
|
||||
elsif closed == '0' && closed_at != nil
|
||||
self.closed_at = nil
|
||||
end
|
||||
end
|
||||
|
||||
def slug_is_not_a_route
|
||||
if RESERVED_SLUGS.include?(sluggify_name)
|
||||
errors.add(:name, 'cannot be a reserved keyword')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,10 +8,11 @@
|
|||
span.icon-bar
|
||||
= link_to 'Creddit', root_path, class: 'navbar-brand'
|
||||
.collapse.navbar-collapse
|
||||
ul.nav.navbar-nav
|
||||
li= link_to 'Subcreddits', subcreddits_path
|
||||
ul.nav.navbar-nav.navbar-right
|
||||
- if logged_in?
|
||||
li= link_to 'Sign Out', signout_path
|
||||
- else
|
||||
li= link_to 'Create Account', signup_path
|
||||
li= link_to 'Sign In', signin_path
|
||||
|
||||
|
|
8
app/views/subcreddits/_form.html.slim
Normal file
8
app/views/subcreddits/_form.html.slim
Normal file
|
@ -0,0 +1,8 @@
|
|||
= simple_form_for subcreddit do |f|
|
||||
.form-inputs
|
||||
- if subcreddit.new_record?
|
||||
= f.input :name
|
||||
- unless subcreddit.new_record?
|
||||
= f.input :closed, as: :boolean, input_html: { checked: subcreddit.closed? }
|
||||
.form-actions
|
||||
= f.button :submit
|
1
app/views/subcreddits/edit.html.slim
Normal file
1
app/views/subcreddits/edit.html.slim
Normal file
|
@ -0,0 +1 @@
|
|||
== render 'form', subcreddit: @subcreddit
|
7
app/views/subcreddits/index.html.slim
Normal file
7
app/views/subcreddits/index.html.slim
Normal file
|
@ -0,0 +1,7 @@
|
|||
= link_to 'Create', new_subcreddit_path
|
||||
|
||||
ul
|
||||
- @subcreddits.each do |subcreddit|
|
||||
li
|
||||
= link_to subcreddit.name, subcreddit
|
||||
== " (#{link_to 'Edit', edit_subcreddit_path(subcreddit)})"
|
1
app/views/subcreddits/new.html.slim
Normal file
1
app/views/subcreddits/new.html.slim
Normal file
|
@ -0,0 +1 @@
|
|||
== render 'form', subcreddit: @subcreddit
|
3
app/views/subcreddits/show.html.slim
Normal file
3
app/views/subcreddits/show.html.slim
Normal file
|
@ -0,0 +1,3 @@
|
|||
h1= @subcreddit.name
|
||||
- if @subcreddit.closed?
|
||||
= "Board has been closed"
|
0
app/views/subcreddits/update.html.slim
Normal file
0
app/views/subcreddits/update.html.slim
Normal file
|
@ -1 +0,0 @@
|
|||
= 'tests#index'
|
|
@ -1 +0,0 @@
|
|||
= 'tests#show'
|
|
@ -1,12 +1,11 @@
|
|||
Rails.application.routes.draw do
|
||||
resources :tests, only: [:index, :show]
|
||||
|
||||
get 'signup', to: 'users#new', as: :signup
|
||||
get 'signin', to: 'user_sessions#new', as: :signin
|
||||
get 'signout', to: 'user_sessions#destroy', as: :signout
|
||||
|
||||
resources :users, only: [:new, :create]
|
||||
resources :subcreddits, path: 'c', except: [:destroy]
|
||||
resources :user_sessions, only: [:new, :create, :destroy]
|
||||
resources :users, only: [:new, :create]
|
||||
|
||||
root to: 'tests#index'
|
||||
root to: 'subcreddits#index'
|
||||
end
|
||||
|
|
12
db/migrate/20150713045745_create_subcreddits.rb
Normal file
12
db/migrate/20150713045745_create_subcreddits.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
class CreateSubcreddits < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :subcreddits do |t|
|
||||
t.references :owner, index: true, foreign_key: true
|
||||
t.string :name
|
||||
t.string :slug
|
||||
t.datetime :closed_at
|
||||
|
||||
t.timestamps null: false
|
||||
end
|
||||
end
|
||||
end
|
13
db/schema.rb
13
db/schema.rb
|
@ -11,7 +11,18 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20150710040354) do
|
||||
ActiveRecord::Schema.define(version: 20150713045745) do
|
||||
|
||||
create_table "subcreddits", force: :cascade do |t|
|
||||
t.integer "owner_id"
|
||||
t.string "name"
|
||||
t.string "slug"
|
||||
t.datetime "closed_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
add_index "subcreddits", ["owner_id"], name: "index_subcreddits_on_owner_id"
|
||||
|
||||
create_table "user_sessions", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
|
|
156
spec/controllers/subcreddits_controller_spec.rb
Normal file
156
spec/controllers/subcreddits_controller_spec.rb
Normal file
|
@ -0,0 +1,156 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe SubcredditsController, type: :controller do
|
||||
let!(:user) { build(:user) }
|
||||
let(:data) do
|
||||
{
|
||||
name: 'Testing Subcreddit 1'
|
||||
}
|
||||
end
|
||||
before(:each) do
|
||||
allow_any_instance_of(ApplicationController)
|
||||
.to receive(:current_user).and_return(user)
|
||||
end
|
||||
|
||||
describe '#index' do
|
||||
let!(:subcreddits) { 3.times.collect { create(:subcreddit) } }
|
||||
|
||||
it 'should render :index' do
|
||||
get :index
|
||||
|
||||
expect(response).to render_template(:index)
|
||||
end
|
||||
|
||||
it 'should assign all Subcreddits to @subcreddits' do
|
||||
get :index
|
||||
|
||||
expect(assigns(:subcreddits)).to eq(subcreddits)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#show' do
|
||||
let(:subcreddit) { create(:subcreddit) }
|
||||
before(:each) { get :show, id: subcreddit }
|
||||
|
||||
it 'should render :show' do
|
||||
expect(response).to render_template(:show)
|
||||
end
|
||||
|
||||
it 'should assign the Subcreddit to @subcreddit' do
|
||||
expect(assigns(:subcreddit)).to eq(subcreddit)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#new' do
|
||||
it 'should render :new' do
|
||||
get :new
|
||||
|
||||
expect(response).to render_template(:new)
|
||||
end
|
||||
|
||||
it 'should assign new Subcreddit to @subcreddit' do
|
||||
get :new
|
||||
|
||||
expect(assigns(:subcreddit)).to be_a_new(Subcreddit)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create' do
|
||||
context 'with valid data' do
|
||||
it 'should create a subcreddit' do
|
||||
expect { post :create, subcreddit: data }
|
||||
.to change(Subcreddit, :count).by(1)
|
||||
end
|
||||
|
||||
it 'should redirect to new subcreddit page index' do
|
||||
expect(post :create, subcreddit: data)
|
||||
.to redirect_to(assigns(:subcreddit))
|
||||
end
|
||||
|
||||
it 'should send a notice flash message' do
|
||||
post :create, subcreddit: data
|
||||
|
||||
expect(flash[:notice]).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid data' do
|
||||
before(:each) { data['name'] = 'Bad name ' }
|
||||
|
||||
it 'should not create a subcreddit' do
|
||||
expect { post :create, subcreddit: data }
|
||||
.to change(Subcreddit, :count).by(0)
|
||||
end
|
||||
|
||||
it 'should render :new' do
|
||||
post :create, subcreddit: data
|
||||
|
||||
expect(response).to render_template(:new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#edit' do
|
||||
context 'with valid subcreddit' do
|
||||
let(:subcreddit) { create(:subcreddit) }
|
||||
|
||||
it 'should assign @subcreddit to the existing subcreddit' do
|
||||
get :edit, id: subcreddit
|
||||
|
||||
expect(assigns(:subcreddit)).to eq(subcreddit)
|
||||
end
|
||||
|
||||
it 'should render :edit' do
|
||||
get :edit, id: subcreddit
|
||||
|
||||
expect(response).to render_template(:edit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update' do
|
||||
let(:subcreddit) { create(:subcreddit) }
|
||||
let(:data) do
|
||||
{
|
||||
closed: '1'
|
||||
}
|
||||
end
|
||||
|
||||
context 'wth valid data' do
|
||||
it 'should assign @subcreddit to the existing subcreddit' do
|
||||
put :update, id: subcreddit, subcreddit: data
|
||||
|
||||
expect(assigns(:subcreddit)).to eq(subcreddit)
|
||||
end
|
||||
|
||||
it 'should update the subcreddit' do
|
||||
put :update, id: subcreddit, subcreddit: data
|
||||
subcreddit.reload
|
||||
|
||||
expect(subcreddit.closed_at).to_not eq(nil)
|
||||
end
|
||||
|
||||
it 'should redirect to the subcreddit' do
|
||||
put :update, id: subcreddit, subcreddit: data
|
||||
|
||||
expect(response).to redirect_to(subcreddit_url(subcreddit))
|
||||
end
|
||||
|
||||
it 'should display a notice flash message' do
|
||||
put :update, id: subcreddit, subcreddit: data
|
||||
|
||||
expect(flash[:notice]).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid data' do
|
||||
before(:each) { data[:closed] = 'bad' }
|
||||
|
||||
it 'should render :edit' do
|
||||
put :update, id: subcreddit, subcreddit: data
|
||||
|
||||
expect(response).to render_template(:edit)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
6
spec/factories/subcreddit_factory.rb
Normal file
6
spec/factories/subcreddit_factory.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
FactoryGirl.define do
|
||||
factory :subcreddit do
|
||||
owner { create(:user) }
|
||||
name { Faker::Team.name.first(21) } # 21 is the max length...brittle
|
||||
end
|
||||
end
|
126
spec/models/subcreddit_spec.rb
Normal file
126
spec/models/subcreddit_spec.rb
Normal file
|
@ -0,0 +1,126 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe Subcreddit, type: :model do
|
||||
let(:subcreddit) { build(:subcreddit) }
|
||||
|
||||
it { should belong_to(:owner).class_name('User') }
|
||||
|
||||
context 'with valid data' do
|
||||
it 'should be valid' do
|
||||
expect(subcreddit).to be_valid
|
||||
end
|
||||
|
||||
it 'should allow spaces in the name' do
|
||||
subcreddit.name = 'Testing Spaces'
|
||||
expect(subcreddit).to be_valid
|
||||
end
|
||||
|
||||
it 'should sluggify the name' do
|
||||
subcreddit.name = 'Testing Spaces 1'
|
||||
subcreddit.save
|
||||
expect(subcreddit.slug).to eq('testing_spaces_1')
|
||||
end
|
||||
|
||||
it 'should set closed_at if closed is true and closed_at is nil' do
|
||||
Timecop.freeze do
|
||||
subcreddit.closed = '1'
|
||||
subcreddit.save
|
||||
expect(subcreddit.closed_at).to eq(Time.now)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should not change closed_at if closed and closed_at is not nil' do
|
||||
subcreddit.closed_at = Time.now - 3.days
|
||||
|
||||
Timecop.freeze do
|
||||
subcreddit.closed = '1'
|
||||
subcreddit.save
|
||||
expect(subcreddit.closed_at).to_not eq(Time.now)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should clear closed_at if closed is "0"' do
|
||||
subcreddit.closed_at = Time.now
|
||||
|
||||
subcreddit.closed = '0'
|
||||
subcreddit.save
|
||||
expect(subcreddit.closed_at).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid data' do
|
||||
it 'should not allow a blank name' do
|
||||
subcreddit.name = ''
|
||||
|
||||
expect(subcreddit).to be_invalid
|
||||
end
|
||||
|
||||
it 'should not allow short names' do
|
||||
subcreddit.name = 'a' * 2
|
||||
|
||||
expect(subcreddit).to be_invalid
|
||||
end
|
||||
|
||||
it 'should not allow long names' do
|
||||
subcreddit.name = 'a' * 22
|
||||
|
||||
expect(subcreddit).to be_invalid
|
||||
end
|
||||
|
||||
it 'should only allow acceptable characters in the name' do
|
||||
subcreddit.name = 'Testing!'
|
||||
|
||||
expect(subcreddit).to be_invalid
|
||||
end
|
||||
|
||||
it 'should not be allowed to end with a space' do
|
||||
subcreddit.name = 'Testing '
|
||||
|
||||
expect(subcreddit).to be_invalid
|
||||
end
|
||||
|
||||
it 'should not be allowed to begin with a space' do
|
||||
subcreddit.name = ' Testing'
|
||||
|
||||
expect(subcreddit).to be_invalid
|
||||
end
|
||||
|
||||
it 'should not allow duplicate names' do
|
||||
original = create(:subcreddit)
|
||||
duplicate = build(:subcreddit, name: original.name)
|
||||
|
||||
expect(duplicate).to be_invalid
|
||||
end
|
||||
|
||||
it 'should not allow duplicate names (case insensitive)' do
|
||||
original = create(:subcreddit)
|
||||
duplicate = build(:subcreddit, name: original.name.upcase)
|
||||
|
||||
expect(duplicate).to be_invalid
|
||||
end
|
||||
|
||||
it 'should not use a reserved keyword' do
|
||||
subcreddit.name = 'New'
|
||||
|
||||
expect(subcreddit).to be_invalid
|
||||
end
|
||||
end
|
||||
|
||||
context '#closed?' do
|
||||
let(:subcreddit) { build(:subcreddit) }
|
||||
|
||||
context 'when a subcreddit is closed' do
|
||||
before(:each) { subcreddit.closed_at = Time.now }
|
||||
|
||||
it 'should return true' do
|
||||
expect(subcreddit.closed?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a subcreddit is open' do
|
||||
it 'should return false' do
|
||||
expect(subcreddit.closed?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue