about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--app/assets/stylesheets/main/entries.scss10
-rw-r--r--app/controllers/blogs_controller.rb48
-rw-r--r--app/models/blog.rb1
-rw-r--r--app/models/concerns/votable.rb43
-rw-r--r--app/models/vote.rb6
-rw-r--r--app/views/blogs/_blog.html.haml4
-rw-r--r--app/views/blogs/voted.js.erb12
-rw-r--r--config/routes.rb3
-rw-r--r--db/migrate/20231020194529_create_votes.rb11
-rw-r--r--db/migrate/20231020195330_make_blog_votable.rb8
-rw-r--r--db/schema.rb14
11 files changed, 159 insertions, 1 deletions
diff --git a/app/assets/stylesheets/main/entries.scss b/app/assets/stylesheets/main/entries.scss index edf1706..3b7215c 100644 --- a/app/assets/stylesheets/main/entries.scss +++ b/app/assets/stylesheets/main/entries.scss
@@ -416,3 +416,13 @@
416 font-weight: bold; 416 font-weight: bold;
417 } 417 }
418} 418}
419
420.post-vote {
421 float: right;
422 position: relative;
423 right: 0.5em;
424
425 a {
426 text-decoration: none;
427 }
428}
diff --git a/app/controllers/blogs_controller.rb b/app/controllers/blogs_controller.rb index 0d218ae..2f9df49 100644 --- a/app/controllers/blogs_controller.rb +++ b/app/controllers/blogs_controller.rb
@@ -38,4 +38,52 @@ class BlogsController < ApplicationController
38 }) 38 })
39 end 39 end
40 40
41 def upvote
42 @blog = Blog.find_by_slug(params[:slug])
43
44 raise ActiveRecord::RecordNotFound unless @blog
45 raise ActiveRecord::RecordNotFound unless @blog.published
46
47 respond_to do |format|
48 if @blog.upvote! request.remote_ip
49 format.html do
50 flash[:notice] = "You have upvoted the blog post \"#{@blog.title}\"."
51 redirect_to @blog
52 end
53 format.js { render "voted" }
54 format.xml { head :ok }
55 else
56 format.html do
57 flash[:notice] = "You have already voted on the blog post \"#{@blog.title}\"."
58 redirect_to @blog
59 end
60 format.xml { render :xml => { :error => "Someone from your IP address has already voted on this blog post."} }
61 end
62 end
63 end
64
65 def downvote
66 @blog = Blog.find_by_slug(params[:slug])
67
68 raise ActiveRecord::RecordNotFound unless @blog
69 raise ActiveRecord::RecordNotFound unless @blog.published
70
71 respond_to do |format|
72 if @blog.downvote! request.remote_ip
73 format.html do
74 flash[:notice] = "You have downvoted the blog post \"#{@blog.title}\"."
75 redirect_to @blog
76 end
77 format.js { render "voted" }
78 format.xml { head :ok }
79 else
80 format.html do
81 flash[:notice] = "You have already voted on the blog post \"#{@blog.title}\"."
82 redirect_to @blog
83 end
84 format.xml { render :xml => { :error => "Someone from your IP address has already voted on this blog post."} }
85 end
86 end
87 end
88
41end 89end
diff --git a/app/models/blog.rb b/app/models/blog.rb index 6db75ec..03643bf 100644 --- a/app/models/blog.rb +++ b/app/models/blog.rb
@@ -1,5 +1,6 @@
1class Blog < ApplicationRecord 1class Blog < ApplicationRecord
2 include Recordable 2 include Recordable
3 include Votable
3 4
4 acts_as_taggable 5 acts_as_taggable
5 6
diff --git a/app/models/concerns/votable.rb b/app/models/concerns/votable.rb new file mode 100644 index 0000000..ba6e6d5 --- /dev/null +++ b/app/models/concerns/votable.rb
@@ -0,0 +1,43 @@
1module Votable
2 extend ActiveSupport::Concern
3
4 included do
5 has_many :votes, as: :votable
6
7 def already_upvoted?(ip)
8 !votes.where(ip: ip, upvote: 1).empty?
9 end
10
11 def already_downvoted?(ip)
12 !votes.where(ip: ip, upvote: 0).empty?
13 end
14
15 def upvote!(ip)
16 return false if already_upvoted?(ip)
17
18 if already_downvoted?(ip)
19 votes.where(ip: ip, upvote: 0).first.delete
20 self.downvotes -= 1
21 save!
22 else
23 votes.create(ip: ip, upvote: 1).save
24 self.upvotes += 1
25 save!
26 end
27 end
28
29 def downvote!(ip)
30 return false if already_downvoted?(ip)
31
32 if already_upvoted?(ip)
33 votes.where(ip: ip, upvote: 1).first.delete
34 self.upvotes -= 1
35 save!
36 else
37 votes.create(ip: ip, upvote: 0).save
38 self.downvotes += 1
39 save!
40 end
41 end
42 end
43end
diff --git a/app/models/vote.rb b/app/models/vote.rb new file mode 100644 index 0000000..e2d8386 --- /dev/null +++ b/app/models/vote.rb
@@ -0,0 +1,6 @@
1class Vote < ApplicationRecord
2 belongs_to :votable, polymorphic: true
3
4 validates :upvote, presence: true, inclusion: { in: [0, 1] }
5 validates :ip, presence: true
6end
diff --git a/app/views/blogs/_blog.html.haml b/app/views/blogs/_blog.html.haml index ec61bb5..1f86ae8 100644 --- a/app/views/blogs/_blog.html.haml +++ b/app/views/blogs/_blog.html.haml
@@ -19,3 +19,7 @@
19 %strong= blog.user.login.capitalize 19 %strong= blog.user.login.capitalize
20 on 20 on
21 = blog.visible_date.strftime("%B #{blog.visible_date.day.ordinalize}, %Y at %-I:%M:%S%P") 21 = blog.visible_date.strftime("%B #{blog.visible_date.day.ordinalize}, %Y at %-I:%M:%S%P")
22 .post-vote{ id: "blog-vote-section-#{blog.id}" }
23 %span.vote-link{ id: "blog-upvote-link-#{blog.id}" }= link_to_unless (not blog.published or blog.already_upvoted?(request.remote_ip)), "👍", upvote_blog_path(blog), remote: true, rel: "nofollow", class: "blog-upvote-link", method: :post
24 %span.post-rating{ id: "blog-rating-#{blog.id}" }= blog.upvotes - blog.downvotes
25 %span.vote-link{ id: "blog-downvote-link-#{blog.id}" }= link_to_unless (not blog.published or blog.already_downvoted?(request.remote_ip)), "👎", downvote_blog_path(blog), remote: true, rel: "nofollow", class: "blog-downvote-link", method: :post
diff --git a/app/views/blogs/voted.js.erb b/app/views/blogs/voted.js.erb new file mode 100644 index 0000000..951c740 --- /dev/null +++ b/app/views/blogs/voted.js.erb
@@ -0,0 +1,12 @@
1$("#blog-rating-<%= @blog.id %>").html('<%= escape_javascript("#{@blog.upvotes - @blog.downvotes}") %>');
2
3<% if @blog.already_upvoted? request.remote_ip %>
4 $("#blog-upvote-link-<%= @blog.id %>").html("👍");
5<% elsif @blog.already_downvoted? request.remote_ip %>
6 $("#blog-downvote-link-<%= @blog.id %>").html("👎");
7<% else %>
8 $("#blog-upvote-link-<%= @blog.id %>").html('<%= escape_javascript(link_to("👍", upvote_blog_path(@blog), remote: true, rel: "nofollow", class: "blog-upvote-link", method: :post)) %>');
9 $("#blog-downvote-link-<%= @blog.id %>").html('<%= escape_javascript(link_to("👎", downvote_blog_path(@blog), remote: true, rel: "nofollow", class: "blog-downvote-link", method: :post)) %>');
10<% end %>
11
12$("#blog-vote-section-<%= @blog.id %>").effect('highlight', {}, 2000);
diff --git a/config/routes.rb b/config/routes.rb index 416e939..6363590 100644 --- a/config/routes.rb +++ b/config/routes.rb
@@ -38,6 +38,9 @@ Rails.application.routes.draw do
38 38
39 resources :blogs, only: [:index, :show], param: :slug, path: "blog" do 39 resources :blogs, only: [:index, :show], param: :slug, path: "blog" do
40 member do 40 member do
41 post 'upvote'
42 post 'downvote'
43
41 resources :comments, only: [:create] 44 resources :comments, only: [:create]
42 end 45 end
43 end 46 end
diff --git a/db/migrate/20231020194529_create_votes.rb b/db/migrate/20231020194529_create_votes.rb new file mode 100644 index 0000000..947652b --- /dev/null +++ b/db/migrate/20231020194529_create_votes.rb
@@ -0,0 +1,11 @@
1class CreateVotes < ActiveRecord::Migration[7.0]
2 def change
3 create_table :votes do |t|
4 t.references :votable, polymorphic: true
5 t.integer :upvote
6 t.string :ip
7
8 t.timestamps
9 end
10 end
11end
diff --git a/db/migrate/20231020195330_make_blog_votable.rb b/db/migrate/20231020195330_make_blog_votable.rb new file mode 100644 index 0000000..4d1e42a --- /dev/null +++ b/db/migrate/20231020195330_make_blog_votable.rb
@@ -0,0 +1,8 @@
1class MakeBlogVotable < ActiveRecord::Migration[7.0]
2 def change
3 change_table :blogs do |t|
4 t.integer :upvotes, default: 0, null: false
5 t.integer :downvotes, default: 0, null: false
6 end
7 end
8end
diff --git a/db/schema.rb b/db/schema.rb index 3a9e111..f8a8c49 100644 --- a/db/schema.rb +++ b/db/schema.rb
@@ -10,7 +10,7 @@
10# 10#
11# It's strongly recommended that you check this file into your version control system. 11# It's strongly recommended that you check this file into your version control system.
12 12
13ActiveRecord::Schema[7.0].define(version: 2023_10_17_153558) do 13ActiveRecord::Schema[7.0].define(version: 2023_10_20_195330) do
14 create_table "active_storage_attachments", force: :cascade do |t| 14 create_table "active_storage_attachments", force: :cascade do |t|
15 t.string "name", null: false 15 t.string "name", null: false
16 t.string "record_type", null: false 16 t.string "record_type", null: false
@@ -70,6 +70,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_17_153558) do
70 t.boolean "published", default: false, null: false 70 t.boolean "published", default: false, null: false
71 t.datetime "published_at", precision: nil 71 t.datetime "published_at", precision: nil
72 t.integer "user_id" 72 t.integer "user_id"
73 t.integer "upvotes", default: 0, null: false
74 t.integer "downvotes", default: 0, null: false
73 t.index ["user_id"], name: "index_blogs_on_user_id" 75 t.index ["user_id"], name: "index_blogs_on_user_id"
74 end 76 end
75 77
@@ -393,6 +395,16 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_17_153558) do
393 t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true 395 t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
394 end 396 end
395 397
398 create_table "votes", force: :cascade do |t|
399 t.string "votable_type"
400 t.integer "votable_id"
401 t.integer "upvote"
402 t.string "ip"
403 t.datetime "created_at", null: false
404 t.datetime "updated_at", null: false
405 t.index ["votable_type", "votable_id"], name: "index_votes_on_votable"
406 end
407
396 add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" 408 add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
397 add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" 409 add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
398 add_foreign_key "blogs", "users" 410 add_foreign_key "blogs", "users"