Skip to content

Commit

Permalink
[#10] Setup vote model which belongs to a user a votable (which can e…
Browse files Browse the repository at this point in the history
…ither be an article or comment). Added upvote/downvote methods in article controller.
  • Loading branch information
paulmckissock committed Jun 26, 2024
1 parent b6ef777 commit 4078c6f
Show file tree
Hide file tree
Showing 11 changed files with 144 additions and 2 deletions.
25 changes: 24 additions & 1 deletion app/controllers/articles_controller.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
class ArticlesController < ApplicationController
before_action :authorized, only: [:new, :create]
before_action :set_article, only: [:show, :upvote, :downvote]
def index
@articles = Article.order(created_at: :desc).page(params[:page]).per(20)
end

def show
@article = Article.find(params[:id])
@comments = @article.comments.without_parent.order(created_at: :desc)
@comment = Comment.new
end

def upvote
vote(1)
redirect_to @article
end

def downvote
vote(-1)
redirect_to @article
end

def new
@article = Article.new
end
Expand All @@ -27,7 +37,20 @@ def create

private

def set_article
@article = Article.find(params[:id])
end

def article_params
params.require(:article).permit(:title, :link)
end

def vote(value)
@vote = @article.votes.find_or_initialize_by(user: current_user)
if @vote.value == value
@vote.update(value: 0)
else
@vote.update(value: value)
end
end
end
6 changes: 6 additions & 0 deletions app/models/article.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
class Article < ApplicationRecord
belongs_to :user
has_many :comments
has_many :votes, as: :votable

validates :title, presence: true
validates :link, presence: true

def score
votes.sum(:value)
end
end
2 changes: 2 additions & 0 deletions app/models/comment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ class Comment < ApplicationRecord
belongs_to :article
belongs_to :user
validates :text, presence: true
has_many :votes, as: :votable


belongs_to :parent, class_name: "Comment", optional: true
has_many :replies, class_name: "Comment", foreign_key: :parent_id, dependent: :destroy
Expand Down
1 change: 1 addition & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ class User < ApplicationRecord
has_secure_password
has_many :articles
has_many :comments
has_many :votes

validates :name, presence: true, uniqueness: true
validates :email, presence: true, uniqueness: true, format: {with: URI::MailTo::EMAIL_REGEXP, message: "must be a valid email address"}
Expand Down
6 changes: 6 additions & 0 deletions app/models/vote.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Vote < ApplicationRecord
belongs_to :user
belongs_to :votable, polymorphic: true

validates :value, inclusion: {in: [-1, 0, 1]}
end
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
root "articles#index"
resources :articles do
resources :comments, only: [:create, :show]
member do
post "upvote"
post "downvote"
end
end
resources :users, only: [:create, :show]
get "/signup", to: "users#new"
Expand Down
11 changes: 11 additions & 0 deletions db/migrate/20240626175659_create_votes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class CreateVotes < ActiveRecord::Migration[7.1]
def change
create_table :votes do |t|
t.references :user, null: false, foreign_key: true
t.references :votable, polymorphic: true, null: false
t.integer :value

t.timestamps
end
end
end
14 changes: 13 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions spec/controllers/articles_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,38 @@
Article.count
}.by(1)
end

describe "POST #upvote" do
it "sets vote value to 1 if user has not voted" do
expect {
post :upvote, params: {id: article.id}
}.to change { article.votes.count }.by(1)
expect(article.reload.votes.last.value).to eq(1)
end

it "sets vote value to 0 if user has already upvoted" do
create(:vote, votable: article, user: user, value: 1)
expect {
post :upvote, params: {id: article.id}
}.not_to change { article.votes.count }
expect(article.reload.votes.last.value).to eq(0)
end
end

describe "POST #downvote" do
it "sets vote value to -1 if user has not already downvoted" do
expect {
post :downvote, params: {id: article.id}
}.to change { article.votes.count }.by(1)
expect(article.reload.votes.last.value).to eq(-1)
end
it "sets vote value to 0 if user has already downvoted" do
create(:vote, votable: article, user: user, value: -1)
expect {
post :downvote, params: {id: article.id}
}.not_to change { article.votes.count }
expect(article.reload.votes.last.value).to eq(0)
end
end
end
end
7 changes: 7 additions & 0 deletions spec/factories/votes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FactoryBot.define do
factory :vote do
association :user, factory: :user
association :votable, factory: :article
value { 0 }
end
end
37 changes: 37 additions & 0 deletions spec/models/vote_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require "rails_helper"

RSpec.describe Vote, type: :model do
let(:user) { create(:user) }
let(:article) { create(:article, user: user) }
let(:comment) { create(:comment, article: article, user: user) }
let(:vote) {create(:vote, votable: article, user: user)}
let(:comment_vote) {create(:vote, votable: comment, user: user)}

it "should be valid with valid attributes" do
expect(vote.valid?).to be(true)
end

it "should be valid with comment as votable" do
expect(comment_vote.valid?).to be(true)
end

it "value should be present" do
vote.value = nil
expect(vote.valid?).to be(false)
end

it "should not be valid with a value other than -1, 0, or 1" do
vote.value = 2
expect(vote.valid?).to be(false)
end

it "should belong to a user" do
vote.user = nil
expect(vote.valid?).to be(false)
end

it "should belong to a votable" do
vote.votable = nil
expect(vote.valid?).to be(false)
end
end

0 comments on commit 4078c6f

Please sign in to comment.