fix: add voting functionality with votable concern and updated price report and vote models.

This commit is contained in:
Mohammad Kanaan 2026-05-30 23:51:16 +03:00
parent da586ae3d3
commit 5c4667ed02
8 changed files with 82 additions and 11 deletions

View file

@ -2,3 +2,6 @@ ts=2026-05-30T17:25:04.225Z type=context scope=developer-profile content="Develo
ts=2026-05-30T17:25:27.520Z type=learning scope=developer-profile content="mini-spec project at /Users/abdoachkar/programming/personal/ruby/mini-spec reveals coding style: functional instincts (Proc as matcher instead of matcher object), hashes over objects for known shapes, explicit self on methods unnecessarily, parentheses on if conditions. Good: blocks, yield, module DSL design, rescue handling, method chaining. These are the Ruby idioms to correct as we go." tags=ruby,style,gaps
ts=2026-05-30T17:25:32.838Z type=context scope=price-point-api content="price-point-api: Rails 8 API-only, Ruby 3.4.4. Models: User, Chain (has_many stores), Store (belongs_to chain, has_many price_reports), Product (has_many price_reports), PriceReport (belongs_to product/store/user, polymorphic moderation_logs/votes), Vote (polymorphic votable), ModerationLog (polymorphic moderatable). Active Storage on Chain/Store/Product. No auth wired up (password_digest exists, no has_secure_password). No service objects, concerns, serializers, or background jobs. Scaffold-style controllers and tests." tags=architecture,rails,models
ts=2026-05-30T17:25:34.868Z type=preference scope=developer-profile content="Teaching approach: guide through building features on price-point-api, not isolated drills. Socratic method — hints and questions before solutions. Correct Ruby idioms as they arise naturally. Priority: auth first (unblocks everything, hits multiple growth areas)." tags=teaching,approach
ts=2026-05-30T20:44:39.127Z type=decision scope=price-point-api content="price-point-api: Added Votable concern (app/models/concerns/votable.rb). PriceReport includes Votable. Methods: upvote!(user), downvote!(user) with toggle logic (same direction = destroy vote, opposite direction = switch), upvote_count/downvote_count via query (votes.where query). Rejected counter cache — directional counts (upvotes/downvotes) make Rails' built-in counter_cache unsuitable. Stale upvotes_count/downvotes_count columns need migration to drop." tags=voting,concerns,architecture
ts=2026-05-30T20:44:41.808Z type=learning scope=developer-profile content="Learned: ActiveSupport::Concern syntax (extend + included do), find_by(user:) shorthand, ! vs no-! consistency in method calls, counter_cache limitations with polymorphic + directional counts, parentheses optional in Ruby when no args. Built Votable concern from scratch with toggle logic." tags=ruby,rails,concerns,counter-cache
ts=2026-05-30T20:45:19.960Z type=preference scope=developer-profile content="Teaching style correction: Don't recommend patterns without first fully analyzing fit. Counter cache example — recommended before realizing directional counts break it. Verify the pattern actually solves the user's concrete problem before suggesting it. If unsure, say so and let the user decide." tags=teaching,style,lessons

View file

@ -4,10 +4,8 @@
#
# id :bigint not null, primary key
# discount_price :decimal(, )
# downvotes :integer
# price :decimal(, )
# reported_at :datetime
# upvotes :integer
# created_at :datetime not null
# updated_at :datetime not null
# product_id :integer not null
@ -27,10 +25,11 @@
# fk_rails_... (user_id => users.id)
#
class PriceReport < ApplicationRecord
include Votable
belongs_to :product
belongs_to :store
belongs_to :user
has_many :moderation_logs, as: :moderatable, dependent: :destroy
has_many :votes, as: :votable, dependent: :destroy
end

View file

@ -1,6 +1,27 @@
# == Schema Information
#
# Table name: votes
#
# id :bigint not null, primary key
# direction :integer
# votable_type :string not null
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint not null
# votable_id :bigint not null
#
# Indexes
#
# index_votes_on_user_id (user_id)
# index_votes_on_votable (votable_type,votable_id)
#
# Foreign Keys
#
# fk_rails_... (user_id => users.id)
#
class Vote < ApplicationRecord
belongs_to :user
belongs_to :votable, polymorphic: true
belongs_to :votable, polymorphic: true, counter_cache: true
enum :direction, {upvote: 0, downvote: 1}

16
db/schema.rb generated
View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2026_05_30_141612) do
ActiveRecord::Schema[8.0].define(version: 2026_05_30_204821) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
@ -62,8 +62,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_05_30_141612) do
create_table "price_reports", force: :cascade do |t|
t.decimal "price"
t.decimal "discount_price"
t.integer "upvotes"
t.integer "downvotes"
t.datetime "reported_at"
t.integer "product_id", null: false
t.integer "store_id", null: false
@ -103,6 +101,17 @@ ActiveRecord::Schema[8.0].define(version: 2026_05_30_141612) do
t.index ["email"], name: "index_users_on_email", unique: true
end
create_table "votes", force: :cascade do |t|
t.bigint "user_id", null: false
t.string "votable_type", null: false
t.bigint "votable_id", null: false
t.integer "direction"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_votes_on_user_id"
t.index ["votable_type", "votable_id"], name: "index_votes_on_votable"
end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "moderation_logs", "users", column: "admin_id"
@ -110,4 +119,5 @@ ActiveRecord::Schema[8.0].define(version: 2026_05_30_141612) do
add_foreign_key "price_reports", "stores"
add_foreign_key "price_reports", "users"
add_foreign_key "stores", "chains"
add_foreign_key "votes", "users"
end

View file

@ -6,10 +6,8 @@
#
# id :bigint not null, primary key
# discount_price :decimal(, )
# downvotes :integer
# price :decimal(, )
# reported_at :datetime
# upvotes :integer
# created_at :datetime not null
# updated_at :datetime not null
# product_id :integer not null

View file

@ -1,5 +1,26 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
# == Schema Information
#
# Table name: votes
#
# id :bigint not null, primary key
# direction :integer
# votable_type :string not null
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint not null
# votable_id :bigint not null
#
# Indexes
#
# index_votes_on_user_id (user_id)
# index_votes_on_votable (votable_type,votable_id)
#
# Foreign Keys
#
# fk_rails_... (user_id => users.id)
#
one:
user: one
votable=references{polymorphic}: MyString

View file

@ -4,10 +4,8 @@
#
# id :bigint not null, primary key
# discount_price :decimal(, )
# downvotes :integer
# price :decimal(, )
# reported_at :datetime
# upvotes :integer
# created_at :datetime not null
# updated_at :datetime not null
# product_id :integer not null

View file

@ -1,3 +1,24 @@
# == Schema Information
#
# Table name: votes
#
# id :bigint not null, primary key
# direction :integer
# votable_type :string not null
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint not null
# votable_id :bigint not null
#
# Indexes
#
# index_votes_on_user_id (user_id)
# index_votes_on_votable (votable_type,votable_id)
#
# Foreign Keys
#
# fk_rails_... (user_id => users.id)
#
require "test_helper"
class VoteTest < ActiveSupport::TestCase