From 1751de2fcc2a86f201a629671e3ebd9c5e99b241 Mon Sep 17 00:00:00 2001
From: Mikhail Kilin <1kilinmv1@gmail.com>
Date: Sun, 29 Nov 2020 20:54:36 +0300
Subject: [PATCH 1/4] added stars plugin
---
app/assets/javascripts/rate/rating_file.js | 167 ++++++++++++++++++++
app/assets/javascripts/rate/rating_init.js | 16 ++
app/controllers/articles_controller.rb | 10 +-
app/controllers/ratings_controller.rb | 19 +++
app/decorators/article_decorator.rb | 6 +
app/models/article.rb | 1 +
app/models/rating.rb | 10 ++
app/policies/rating_policy.rb | 9 ++
app/views/articles/_form.html.slim | 8 +
app/views/articles/show.html.slim | 4 +
app/views/layouts/company.html.slim | 1 +
app/views/ratings/create.js.erb | 3 +
app/views/ratings/update.js.erb | 3 +
config/routes.rb | 2 +
db/migrate/20201129151451_create_ratings.rb | 9 ++
db/schema.rb | 9 +-
spec/factories/ratings.rb | 7 +
spec/features/user/ratings/create_spec.rb | 20 +++
spec/features/user/ratings/update_spec.rb | 18 +++
19 files changed, 319 insertions(+), 3 deletions(-)
create mode 100644 app/assets/javascripts/rate/rating_file.js
create mode 100644 app/assets/javascripts/rate/rating_init.js
create mode 100644 app/controllers/ratings_controller.rb
create mode 100644 app/models/rating.rb
create mode 100644 app/policies/rating_policy.rb
create mode 100644 app/views/articles/_form.html.slim
create mode 100644 app/views/ratings/create.js.erb
create mode 100644 app/views/ratings/update.js.erb
create mode 100644 db/migrate/20201129151451_create_ratings.rb
create mode 100644 spec/factories/ratings.rb
create mode 100644 spec/features/user/ratings/create_spec.rb
create mode 100644 spec/features/user/ratings/update_spec.rb
diff --git a/app/assets/javascripts/rate/rating_file.js b/app/assets/javascripts/rate/rating_file.js
new file mode 100644
index 00000000..0a67e9f6
--- /dev/null
+++ b/app/assets/javascripts/rate/rating_file.js
@@ -0,0 +1,167 @@
+const defaults = {
+ "value": Number($("#rating_rate").val()),
+ "stars": 5,
+ "half": false,
+ "emptyStar": "far fa-star",
+ "halfStar": "fas fa-star-half-alt",
+ "filledStar": "fas fa-star",
+ "color": "#fcd703",
+ "readonly": false,
+ "click": function (e) {
+ console.error("No click callback provided!");
+ }
+};
+
+jQuery.fn.extend({
+ rating: function (options = {}) {
+ return this.each(function () {
+ if ($(this).attr("rating")) {
+ $(this).empty();
+ }
+
+ this.stars = options.value ? options.value : defaults.value;
+ this.readonly = options.readonly ? options.readonly : defaults.readonly;
+
+ this.getStars = function () {
+ return $(this).find($("i"));
+ };
+
+ $(this).css({
+ "color": options.color ? options.color : defaults.color
+ })
+ .attr("rating", true);
+
+ if (!this.readonly) {
+ $(this).off('mousemove').on('mousemove', function (e) {
+ let halfStars = options.half ? options.half : defaults.half;
+
+ if (this.getStars().index(e.target) >= 0) {
+ if (!halfStars) {
+ $(this).find("i").attr("class", options.emptyStar ? options.emptyStar : defaults.emptyStar);
+ let index = this.getStars().index(e.target) + 1;
+
+ for (let i = 0; i < this.getStars().length; i++) {
+ if (i < index)
+ $(this.getStars()[i]).attr("class", options.filledStar ? options.filledStar : defaults.filledStar)
+ }
+
+ } else {
+ $(this).find("i").attr("class", options.emptyStar ? options.emptyStar : defaults.emptyStar);
+ let extra = 0.5;
+
+ $(this).find("i").css({
+ "width": $(this).find("i").outerWidth()
+ });
+
+ if (e.offsetX > ($(e.target).outerWidth() / 2))
+ extra = 1;
+
+ let index = this.getStars().index(e.target) + extra;
+ for (let i = 0; i < this.getStars().length; i++) {
+ if (i + 0.5 < index) {
+ $(this.getStars()[i]).attr("class", options.filledStar ? options.filledStar : defaults.filledStar)
+ } else if (i < index) {
+ $(this.getStars()[i]).attr("class", options.halfStar ? options.halfStar : defaults.halfStar)
+ }
+ }
+ }
+ }
+ });
+
+ $(this).off('mouseout').on('mouseout', function (e) {
+ this.printStars();
+ });
+
+ $(this).off('click').on('click', function (e) {
+ let halfStars = options.half ? options.half : defaults.half;
+ if (!halfStars) {
+ this.stars = this.getStars().index(e.target) + 1;
+ } else {
+ let extra = 0.5;
+ if (e.offsetX > ($(e.target).outerWidth() / 2))
+ extra = 1;
+
+ this.stars = this.getStars().index(e.target) + extra;
+ }
+
+ const callback = options.click ? options.click : defaults.click;
+ callback({
+ "stars": this.stars,
+ "event": e
+ });
+ });
+ }
+
+ // Add star elements to the element
+ const stars = options.stars ? options.stars : defaults.stars;
+ for (let i = 0; i < stars; i++) {
+ let star = $("")
+ .addClass(options.emptyStar ? options.emptyStar : defaults.emptyStar)
+ .appendTo($(this));
+
+ if (!this.readonly) {
+ star.css({
+ "cursor": "pointer"
+ })
+ }
+
+ if (i > 1000)
+ return;
+ }
+
+ this.printStars = function () {
+ let halfStars = options.half ? options.half : defaults.half;
+ if (!halfStars) {
+ $(this).find("i").attr("class", options.emptyStar ? options.emptyStar : defaults.emptyStar);
+ for (let i = 0; i < this.stars; i++) {
+ $(this.getStars()[i]).attr("class", options.filledStar ? options.filledStar : defaults.filledStar)
+ }
+ } else {
+ $(this).find("i").attr("class", options.emptyStar ? options.emptyStar : defaults.emptyStar);
+ for (let i = 0; i < this.stars; i++) {
+ if (i < this.stars - 0.5) {
+ $(this.getStars()[i]).attr("class", options.filledStar ? options.filledStar : defaults.filledStar)
+ } else {
+ $(this.getStars()[i]).attr("class", options.halfStar ? options.halfStar : defaults.halfStar)
+ }
+ }
+ }
+ };
+
+ if (this.stars > 0) {
+ this.printStars();
+
+ const callback = options.click ? options.click : defaults.click;
+ callback({
+ "stars": this.stars
+ });
+ }
+ });
+ }
+})
+;
+
+$(function () {
+ $("[data-rating-stars]").each(function () {
+ // Get all data-rating attributes
+ let d = {},
+ re_dataAttr = /^data-rating\-(.+)$/;
+
+ $.each($(this).get(0).attributes, function (index, attr) {
+ if (re_dataAttr.test(attr.nodeName)) {
+ let key = attr.nodeName.match(re_dataAttr)[1];
+ d[key] = attr.nodeValue;
+ }
+ });
+
+ // Create the click event handler
+ if (d.input != null) {
+ d.click = function (e) {
+ $(d.input).val(e.stars);
+ }
+ }
+
+ // Run the rating function on the element
+ $(this).rating(d);
+ });
+});
diff --git a/app/assets/javascripts/rate/rating_init.js b/app/assets/javascripts/rate/rating_init.js
new file mode 100644
index 00000000..e08dfb32
--- /dev/null
+++ b/app/assets/javascripts/rate/rating_init.js
@@ -0,0 +1,16 @@
+if ($("#review").length > 0) {
+
+ $("#review").rating({
+ "click":function (e) {
+ if (e.stars > 0 && e.stars <= 5) {
+ if ($("#new_rating").length > 0) {
+ $("#rating_rate").val(e.stars);
+ $("#new_rating").submit();
+ } else {
+ $("#rating_rate").val(e.stars);
+ $(".edit_rating").submit();
+ }
+ }
+ }
+ });
+}
diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb
index 4e471726..b2f115c7 100644
--- a/app/controllers/articles_controller.rb
+++ b/app/controllers/articles_controller.rb
@@ -2,6 +2,7 @@ class ArticlesController < ApplicationController
expose_decorated :article, -> { set_article }
expose :comment, -> { set_comment }
expose_decorated :company, -> { article.company }
+ expose :rating, -> { set_rating }
layout "company"
@@ -9,13 +10,18 @@ def show
authorize article
end
+ private
+
def set_article
Article.includes(:comments).find_by(id: params["id"])
end
- private
-
def set_comment
article.comments.new
end
+
+ def set_rating
+ Rating.find_by(article: article, user: current_user) ||
+ Rating.new(article: article, user: current_user)
+ end
end
diff --git a/app/controllers/ratings_controller.rb b/app/controllers/ratings_controller.rb
new file mode 100644
index 00000000..fe59bf91
--- /dev/null
+++ b/app/controllers/ratings_controller.rb
@@ -0,0 +1,19 @@
+class RatingsController < ApplicationController
+ expose :rating
+ expose_decorated :article, -> { rating.article }
+ before_action -> { authorize rating }
+
+ def create
+ rating.save
+ end
+
+ def update
+ rating.update rating_params
+ end
+
+ private
+
+ def rating_params
+ params.require(:rating).permit(:rate, :user_id, :article_id)
+ end
+end
diff --git a/app/decorators/article_decorator.rb b/app/decorators/article_decorator.rb
index 3eeb8cce..00f6a3d4 100644
--- a/app/decorators/article_decorator.rb
+++ b/app/decorators/article_decorator.rb
@@ -13,4 +13,10 @@ def author
def sorted_comments
object.comments.sorted_by_created_at
end
+
+ def rating
+ return "Nobody has rated on this article yet" if object.ratings.blank?
+
+ "#{object.ratings.average(:rate).round(2)}/5"
+ end
end
diff --git a/app/models/article.rb b/app/models/article.rb
index 95598af3..647e98f0 100644
--- a/app/models/article.rb
+++ b/app/models/article.rb
@@ -8,6 +8,7 @@ class Article < ApplicationRecord
belongs_to :user
belongs_to :company
has_many :comments, dependent: :destroy
+ has_many :ratings
paginates_per 5
diff --git a/app/models/rating.rb b/app/models/rating.rb
new file mode 100644
index 00000000..5aa01db9
--- /dev/null
+++ b/app/models/rating.rb
@@ -0,0 +1,10 @@
+class Rating < ApplicationRecord
+ include PgSearch
+
+ validates_associated :user
+ validates_associated :article
+ validates :rate, inclusion: { in: [1, 2, 3, 4, 5] }
+
+ belongs_to :user
+ belongs_to :article
+end
diff --git a/app/policies/rating_policy.rb b/app/policies/rating_policy.rb
new file mode 100644
index 00000000..777315fa
--- /dev/null
+++ b/app/policies/rating_policy.rb
@@ -0,0 +1,9 @@
+class RatingPolicy < ApplicationPolicy
+ def create?
+ user.present? && Rating.find_by(article: record.article, user: record.user).blank?
+ end
+
+ def update?
+ user.present?
+ end
+end
diff --git a/app/views/articles/_form.html.slim b/app/views/articles/_form.html.slim
new file mode 100644
index 00000000..efd16d92
--- /dev/null
+++ b/app/views/articles/_form.html.slim
@@ -0,0 +1,8 @@
+.row(id="rating_data")
+ .medium-6.columns
+ = "Rating: #{article.rating}"
+ = simple_form_for rating, remote: true do |f|
+ = f.input :id, as: :hidden
+ = f.input :rate, as: :hidden
+ = f.input :user_id, as: :hidden
+ = f.input :article_id, as: :hidden
diff --git a/app/views/articles/show.html.slim b/app/views/articles/show.html.slim
index c5648094..2a428010 100644
--- a/app/views/articles/show.html.slim
+++ b/app/views/articles/show.html.slim
@@ -12,6 +12,10 @@ article.showed
= "Published #{distance_of_time_in_words_to_now(article.updated_at)} ago"
div
= "#{article.author}"
+ - if policy(rating).update?
+ .medium-2.columns
+ div(id="review")
+ = render "form"
= render article.sorted_comments
diff --git a/app/views/layouts/company.html.slim b/app/views/layouts/company.html.slim
index 5ed0efb9..ab4669ae 100644
--- a/app/views/layouts/company.html.slim
+++ b/app/views/layouts/company.html.slim
@@ -11,6 +11,7 @@ html class="no-js" lang="en"
= csrf_meta_tags
= stylesheet_link_tag :application
+ = stylesheet_link_tag "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css"
= javascript_tag "window.App = {}"
diff --git a/app/views/ratings/create.js.erb b/app/views/ratings/create.js.erb
new file mode 100644
index 00000000..1af9eb12
--- /dev/null
+++ b/app/views/ratings/create.js.erb
@@ -0,0 +1,3 @@
+$("#rating_data").html("");
+
+$("#rating_data").append("<%= escape_javascript(render :partial => 'articles/form') %>");
diff --git a/app/views/ratings/update.js.erb b/app/views/ratings/update.js.erb
new file mode 100644
index 00000000..1af9eb12
--- /dev/null
+++ b/app/views/ratings/update.js.erb
@@ -0,0 +1,3 @@
+$("#rating_data").html("");
+
+$("#rating_data").append("<%= escape_javascript(render :partial => 'articles/form') %>");
diff --git a/config/routes.rb b/config/routes.rb
index d09ada2f..3d234210 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -6,6 +6,8 @@
resource :message, only: %i[new create]
end
+ resources :ratings, only: %i[create update]
+
namespace :v1 do
defaults format: :json do
resources :companies, only: :authors do
diff --git a/db/migrate/20201129151451_create_ratings.rb b/db/migrate/20201129151451_create_ratings.rb
new file mode 100644
index 00000000..62d8c9c0
--- /dev/null
+++ b/db/migrate/20201129151451_create_ratings.rb
@@ -0,0 +1,9 @@
+class CreateRatings < ActiveRecord::Migration[5.2]
+ def change
+ create_table :ratings do |t|
+ t.integer :article_id
+ t.integer :user_id
+ t.integer :rate
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f00dd893..f5bec1af 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_06_17_144204) do
+ActiveRecord::Schema.define(version: 2020_11_29_151451) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -61,6 +61,12 @@
t.index ["company_id"], name: "index_companies_users_on_company_id"
end
+ create_table "ratings", force: :cascade do |t|
+ t.integer "article_id"
+ t.integer "user_id"
+ t.integer "rate"
+ end
+
create_table "static_pages", force: :cascade do |t|
t.string "title"
t.text "content"
@@ -95,6 +101,7 @@
t.string "avatar"
t.integer "articles_count", default: 0, null: false
t.integer "comments_count", default: 0, null: false
+ t.integer "week_comments_count", default: 0, null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
diff --git a/spec/factories/ratings.rb b/spec/factories/ratings.rb
new file mode 100644
index 00000000..2b7e706f
--- /dev/null
+++ b/spec/factories/ratings.rb
@@ -0,0 +1,7 @@
+FactoryBot.define do
+ factory :rating do
+ user
+ article
+ rate { 5 }
+ end
+end
diff --git a/spec/features/user/ratings/create_spec.rb b/spec/features/user/ratings/create_spec.rb
new file mode 100644
index 00000000..3bf13e50
--- /dev/null
+++ b/spec/features/user/ratings/create_spec.rb
@@ -0,0 +1,20 @@
+require "rails_helper"
+
+feature "Create Rating" do
+ include_context "current user signed in"
+ let(:company) { create :company, owner: current_user }
+ let(:article) { create :article, :company, user: current_user, company: company }
+ let(:another_user) { create :user }
+ let!(:rating) { create :rating, user: another_user, article: article, rate: 5 }
+
+ background do
+ visit article_path(article)
+ end
+
+ scenario "User choose 1 point", js: true do
+ expect(page).to have_content("5.0/5")
+ star = find(:xpath, "//i[1]")
+ star.click
+ expect(page).to have_content("3.0/5")
+ end
+end
diff --git a/spec/features/user/ratings/update_spec.rb b/spec/features/user/ratings/update_spec.rb
new file mode 100644
index 00000000..285ba50b
--- /dev/null
+++ b/spec/features/user/ratings/update_spec.rb
@@ -0,0 +1,18 @@
+require "rails_helper"
+
+feature "Update Rating" do
+ include_context "current user signed in"
+ let!(:rating) { create :rating, user: current_user, article: article, rate: 4 }
+ let(:company) { create :company, owner: current_user }
+ let(:article) { create :article, :company, user: current_user, company: company }
+
+ background do
+ visit article_path(article)
+ end
+
+ scenario "User choose 3 points", js: true do
+ expect(page).to have_content("4.0/5")
+ find(:xpath, "//i[3]").click
+ expect(page).to have_content("3.0/5")
+ end
+end
From 804de4c8884c4b5683bdf201a135559c8735dbcc Mon Sep 17 00:00:00 2001
From: Mikhail Kilin <1kilinmv1@gmail.com>
Date: Sun, 29 Nov 2020 20:56:06 +0300
Subject: [PATCH 2/4] quality fix
---
spec/features/user/ratings/create_spec.rb | 2 +-
spec/features/user/ratings/update_spec.rb | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/spec/features/user/ratings/create_spec.rb b/spec/features/user/ratings/create_spec.rb
index 3bf13e50..56f73714 100644
--- a/spec/features/user/ratings/create_spec.rb
+++ b/spec/features/user/ratings/create_spec.rb
@@ -5,9 +5,9 @@
let(:company) { create :company, owner: current_user }
let(:article) { create :article, :company, user: current_user, company: company }
let(:another_user) { create :user }
- let!(:rating) { create :rating, user: another_user, article: article, rate: 5 }
background do
+ create :rating, user: another_user, article: article, rate: 5
visit article_path(article)
end
diff --git a/spec/features/user/ratings/update_spec.rb b/spec/features/user/ratings/update_spec.rb
index 285ba50b..1bbc5f7f 100644
--- a/spec/features/user/ratings/update_spec.rb
+++ b/spec/features/user/ratings/update_spec.rb
@@ -2,11 +2,11 @@
feature "Update Rating" do
include_context "current user signed in"
- let!(:rating) { create :rating, user: current_user, article: article, rate: 4 }
let(:company) { create :company, owner: current_user }
let(:article) { create :article, :company, user: current_user, company: company }
background do
+ create :rating, user: current_user, article: article, rate: 4
visit article_path(article)
end
From f49fc3d426864582c3230cb459f13988cdfeb968 Mon Sep 17 00:00:00 2001
From: Mikhail Kilin <1kilinmv1@gmail.com>
Date: Sat, 12 Dec 2020 19:05:09 +0300
Subject: [PATCH 3/4] fixes
---
app/controllers/articles_controller.rb | 5 ++---
app/controllers/ratings_controller.rb | 15 ++++++++++-----
app/decorators/article_decorator.rb | 2 +-
app/models/article.rb | 2 +-
app/models/rating.rb | 8 ++------
app/policies/rating_policy.rb | 9 ---------
app/views/articles/_form.html.slim | 5 +----
app/views/articles/show.html.slim | 5 ++---
config/routes.rb | 3 +--
db/migrate/20201129151451_create_ratings.rb | 8 +++++---
db/schema.rb | 11 ++++++++---
11 files changed, 33 insertions(+), 40 deletions(-)
delete mode 100644 app/policies/rating_policy.rb
diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb
index b2f115c7..afa2337e 100644
--- a/app/controllers/articles_controller.rb
+++ b/app/controllers/articles_controller.rb
@@ -13,7 +13,7 @@ def show
private
def set_article
- Article.includes(:comments).find_by(id: params["id"])
+ Article.includes(:comments, :ratings).find_by(id: params["id"])
end
def set_comment
@@ -21,7 +21,6 @@ def set_comment
end
def set_rating
- Rating.find_by(article: article, user: current_user) ||
- Rating.new(article: article, user: current_user)
+ Rating.find_or_initialize_by(article: article, user: current_user)
end
end
diff --git a/app/controllers/ratings_controller.rb b/app/controllers/ratings_controller.rb
index fe59bf91..07b6df5d 100644
--- a/app/controllers/ratings_controller.rb
+++ b/app/controllers/ratings_controller.rb
@@ -1,19 +1,24 @@
class RatingsController < ApplicationController
expose :rating
- expose_decorated :article, -> { rating.article }
- before_action -> { authorize rating }
+ before_action :authenticate_user!
+ expose_decorated :article
+ expose :rating, build: ->(rating_params) { Rating.new(rating_params) }
def create
- rating.save
+ redirect_to article_path(article) unless rating.save
end
def update
- rating.update rating_params
+ redirect_to article_path(article) unless rating.update rating_params
end
private
def rating_params
- params.require(:rating).permit(:rate, :user_id, :article_id)
+ {
+ user: current_user,
+ article_id: params[:article_id],
+ rate: params[:rating][:rate]
+ }
end
end
diff --git a/app/decorators/article_decorator.rb b/app/decorators/article_decorator.rb
index 00f6a3d4..08de3e79 100644
--- a/app/decorators/article_decorator.rb
+++ b/app/decorators/article_decorator.rb
@@ -15,7 +15,7 @@ def sorted_comments
end
def rating
- return "Nobody has rated on this article yet" if object.ratings.blank?
+ return "Nobody has rated on this article yet" unless object.ratings.any?
"#{object.ratings.average(:rate).round(2)}/5"
end
diff --git a/app/models/article.rb b/app/models/article.rb
index 647e98f0..e841ab01 100644
--- a/app/models/article.rb
+++ b/app/models/article.rb
@@ -8,7 +8,7 @@ class Article < ApplicationRecord
belongs_to :user
belongs_to :company
has_many :comments, dependent: :destroy
- has_many :ratings
+ has_many :ratings, dependent: :destroy
paginates_per 5
diff --git a/app/models/rating.rb b/app/models/rating.rb
index 5aa01db9..84342f9d 100644
--- a/app/models/rating.rb
+++ b/app/models/rating.rb
@@ -1,10 +1,6 @@
class Rating < ApplicationRecord
- include PgSearch
-
- validates_associated :user
- validates_associated :article
validates :rate, inclusion: { in: [1, 2, 3, 4, 5] }
- belongs_to :user
- belongs_to :article
+ belongs_to :user, required: true
+ belongs_to :article, required: true
end
diff --git a/app/policies/rating_policy.rb b/app/policies/rating_policy.rb
deleted file mode 100644
index 777315fa..00000000
--- a/app/policies/rating_policy.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class RatingPolicy < ApplicationPolicy
- def create?
- user.present? && Rating.find_by(article: record.article, user: record.user).blank?
- end
-
- def update?
- user.present?
- end
-end
diff --git a/app/views/articles/_form.html.slim b/app/views/articles/_form.html.slim
index efd16d92..2fe219a1 100644
--- a/app/views/articles/_form.html.slim
+++ b/app/views/articles/_form.html.slim
@@ -1,8 +1,5 @@
.row(id="rating_data")
.medium-6.columns
= "Rating: #{article.rating}"
- = simple_form_for rating, remote: true do |f|
- = f.input :id, as: :hidden
+ = simple_form_for [article, rating], remote: true do |f|
= f.input :rate, as: :hidden
- = f.input :user_id, as: :hidden
- = f.input :article_id, as: :hidden
diff --git a/app/views/articles/show.html.slim b/app/views/articles/show.html.slim
index 2a428010..ff584ce0 100644
--- a/app/views/articles/show.html.slim
+++ b/app/views/articles/show.html.slim
@@ -12,9 +12,8 @@ article.showed
= "Published #{distance_of_time_in_words_to_now(article.updated_at)} ago"
div
= "#{article.author}"
- - if policy(rating).update?
- .medium-2.columns
- div(id="review")
+ .medium-2.columns
+ div(id="review")
= render "form"
= render article.sorted_comments
diff --git a/config/routes.rb b/config/routes.rb
index 3d234210..fe8955e5 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -6,8 +6,6 @@
resource :message, only: %i[new create]
end
- resources :ratings, only: %i[create update]
-
namespace :v1 do
defaults format: :json do
resources :companies, only: :authors do
@@ -26,6 +24,7 @@
resources :articles, only: :show do
resources :comments, except: %i[index new]
+ resources :ratings, only: %i[create update]
end
namespace :admin_scope do
diff --git a/db/migrate/20201129151451_create_ratings.rb b/db/migrate/20201129151451_create_ratings.rb
index 62d8c9c0..f439137d 100644
--- a/db/migrate/20201129151451_create_ratings.rb
+++ b/db/migrate/20201129151451_create_ratings.rb
@@ -1,9 +1,11 @@
class CreateRatings < ActiveRecord::Migration[5.2]
def change
create_table :ratings do |t|
- t.integer :article_id
- t.integer :user_id
- t.integer :rate
+ t.references :article, foreign_key: true, null: false
+ t.references :user, foreign_key: true, null: false
+ t.integer :rate, default: 0, null: false
end
+
+ add_index :ratings, [:article_id, :user_id], unique: true
end
end
diff --git a/db/schema.rb b/db/schema.rb
index f5bec1af..de904122 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -62,9 +62,12 @@
end
create_table "ratings", force: :cascade do |t|
- t.integer "article_id"
- t.integer "user_id"
- t.integer "rate"
+ t.bigint "article_id", null: false
+ t.bigint "user_id", null: false
+ t.integer "rate", default: 0, null: false
+ t.index ["article_id", "user_id"], name: "index_ratings_on_article_id_and_user_id", unique: true
+ t.index ["article_id"], name: "index_ratings_on_article_id"
+ t.index ["user_id"], name: "index_ratings_on_user_id"
end
create_table "static_pages", force: :cascade do |t|
@@ -107,4 +110,6 @@
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
end
+ add_foreign_key "ratings", "articles"
+ add_foreign_key "ratings", "users"
end
From d983707fcfa12738d0bbeb4b76348231aae8180f Mon Sep 17 00:00:00 2001
From: Mikhail Kilin <1kilinmv1@gmail.com>
Date: Sat, 12 Dec 2020 19:09:24 +0300
Subject: [PATCH 4/4] fix
---
app/assets/javascripts/rate/rating_file.js | 168 +--------------------
1 file changed, 1 insertion(+), 167 deletions(-)
diff --git a/app/assets/javascripts/rate/rating_file.js b/app/assets/javascripts/rate/rating_file.js
index 0a67e9f6..43fa766b 100644
--- a/app/assets/javascripts/rate/rating_file.js
+++ b/app/assets/javascripts/rate/rating_file.js
@@ -1,167 +1 @@
-const defaults = {
- "value": Number($("#rating_rate").val()),
- "stars": 5,
- "half": false,
- "emptyStar": "far fa-star",
- "halfStar": "fas fa-star-half-alt",
- "filledStar": "fas fa-star",
- "color": "#fcd703",
- "readonly": false,
- "click": function (e) {
- console.error("No click callback provided!");
- }
-};
-
-jQuery.fn.extend({
- rating: function (options = {}) {
- return this.each(function () {
- if ($(this).attr("rating")) {
- $(this).empty();
- }
-
- this.stars = options.value ? options.value : defaults.value;
- this.readonly = options.readonly ? options.readonly : defaults.readonly;
-
- this.getStars = function () {
- return $(this).find($("i"));
- };
-
- $(this).css({
- "color": options.color ? options.color : defaults.color
- })
- .attr("rating", true);
-
- if (!this.readonly) {
- $(this).off('mousemove').on('mousemove', function (e) {
- let halfStars = options.half ? options.half : defaults.half;
-
- if (this.getStars().index(e.target) >= 0) {
- if (!halfStars) {
- $(this).find("i").attr("class", options.emptyStar ? options.emptyStar : defaults.emptyStar);
- let index = this.getStars().index(e.target) + 1;
-
- for (let i = 0; i < this.getStars().length; i++) {
- if (i < index)
- $(this.getStars()[i]).attr("class", options.filledStar ? options.filledStar : defaults.filledStar)
- }
-
- } else {
- $(this).find("i").attr("class", options.emptyStar ? options.emptyStar : defaults.emptyStar);
- let extra = 0.5;
-
- $(this).find("i").css({
- "width": $(this).find("i").outerWidth()
- });
-
- if (e.offsetX > ($(e.target).outerWidth() / 2))
- extra = 1;
-
- let index = this.getStars().index(e.target) + extra;
- for (let i = 0; i < this.getStars().length; i++) {
- if (i + 0.5 < index) {
- $(this.getStars()[i]).attr("class", options.filledStar ? options.filledStar : defaults.filledStar)
- } else if (i < index) {
- $(this.getStars()[i]).attr("class", options.halfStar ? options.halfStar : defaults.halfStar)
- }
- }
- }
- }
- });
-
- $(this).off('mouseout').on('mouseout', function (e) {
- this.printStars();
- });
-
- $(this).off('click').on('click', function (e) {
- let halfStars = options.half ? options.half : defaults.half;
- if (!halfStars) {
- this.stars = this.getStars().index(e.target) + 1;
- } else {
- let extra = 0.5;
- if (e.offsetX > ($(e.target).outerWidth() / 2))
- extra = 1;
-
- this.stars = this.getStars().index(e.target) + extra;
- }
-
- const callback = options.click ? options.click : defaults.click;
- callback({
- "stars": this.stars,
- "event": e
- });
- });
- }
-
- // Add star elements to the element
- const stars = options.stars ? options.stars : defaults.stars;
- for (let i = 0; i < stars; i++) {
- let star = $("")
- .addClass(options.emptyStar ? options.emptyStar : defaults.emptyStar)
- .appendTo($(this));
-
- if (!this.readonly) {
- star.css({
- "cursor": "pointer"
- })
- }
-
- if (i > 1000)
- return;
- }
-
- this.printStars = function () {
- let halfStars = options.half ? options.half : defaults.half;
- if (!halfStars) {
- $(this).find("i").attr("class", options.emptyStar ? options.emptyStar : defaults.emptyStar);
- for (let i = 0; i < this.stars; i++) {
- $(this.getStars()[i]).attr("class", options.filledStar ? options.filledStar : defaults.filledStar)
- }
- } else {
- $(this).find("i").attr("class", options.emptyStar ? options.emptyStar : defaults.emptyStar);
- for (let i = 0; i < this.stars; i++) {
- if (i < this.stars - 0.5) {
- $(this.getStars()[i]).attr("class", options.filledStar ? options.filledStar : defaults.filledStar)
- } else {
- $(this.getStars()[i]).attr("class", options.halfStar ? options.halfStar : defaults.halfStar)
- }
- }
- }
- };
-
- if (this.stars > 0) {
- this.printStars();
-
- const callback = options.click ? options.click : defaults.click;
- callback({
- "stars": this.stars
- });
- }
- });
- }
-})
-;
-
-$(function () {
- $("[data-rating-stars]").each(function () {
- // Get all data-rating attributes
- let d = {},
- re_dataAttr = /^data-rating\-(.+)$/;
-
- $.each($(this).get(0).attributes, function (index, attr) {
- if (re_dataAttr.test(attr.nodeName)) {
- let key = attr.nodeName.match(re_dataAttr)[1];
- d[key] = attr.nodeValue;
- }
- });
-
- // Create the click event handler
- if (d.input != null) {
- d.click = function (e) {
- $(d.input).val(e.stars);
- }
- }
-
- // Run the rating function on the element
- $(this).rating(d);
- });
-});
+!function(t){var e={};function r(a){if(e[a])return e[a].exports;var s=e[a]={i:a,l:!1,exports:{}};return t[a].call(s.exports,s,s.exports,r),s.l=!0,s.exports}r.m=t,r.c=e,r.d=function(t,e,a){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:a})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var a=Object.create(null);if(r.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var s in t)r.d(a,s,function(e){return t[e]}.bind(null,s));return a},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e){const r={value:Number($("#rating_rate").val()),stars:5,half:!1,emptyStar:"far fa-star",halfStar:"fas fa-star-half-alt",filledStar:"fas fa-star",color:"#fcd703",readonly:!1,click:function(t){console.error("No click callback provided!")}};jQuery.fn.extend({rating:function(t={}){return this.each((function(){$(this).attr("rating")&&$(this).empty(),this.stars=t.value?t.value:r.value,this.readonly=t.readonly?t.readonly:r.readonly,this.getStars=function(){return $(this).find($("i"))},$(this).css({color:t.color?t.color:r.color}).attr("rating",!0),this.readonly||($(this).off("mousemove").on("mousemove",(function(e){let a=t.half?t.half:r.half;if(this.getStars().index(e.target)>=0)if(a){$(this).find("i").attr("class",t.emptyStar?t.emptyStar:r.emptyStar);let a=.5;$(this).find("i").css({width:$(this).find("i").outerWidth()}),e.offsetX>$(e.target).outerWidth()/2&&(a=1);let s=this.getStars().index(e.target)+a;for(let e=0;e$(e.target).outerWidth()/2&&(t=1),this.stars=this.getStars().index(e.target)+t}else this.stars=this.getStars().index(e.target)+1;(t.click?t.click:r.click)({stars:this.stars,event:e})})));const e=t.stars?t.stars:r.stars;for(let a=0;a").addClass(t.emptyStar?t.emptyStar:r.emptyStar).appendTo($(this));if(this.readonly||e.css({cursor:"pointer"}),a>1e3)return}if(this.printStars=function(){if(t.half?t.half:r.half){$(this).find("i").attr("class",t.emptyStar?t.emptyStar:r.emptyStar);for(let e=0;e0){this.printStars();(t.click?t.click:r.click)({stars:this.stars})}}))}}),$((function(){$("[data-rating-stars]").each((function(){let t={},e=/^data-rating\-(.+)$/;$.each($(this).get(0).attributes,(function(r,a){if(e.test(a.nodeName)){let r=a.nodeName.match(e)[1];t[r]=a.nodeValue}})),null!=t.input&&(t.click=function(e){$(t.input).val(e.stars)}),$(this).rating(t)}))}))}]);
\ No newline at end of file