fix: add voting functionality with votable concern and updated price report and vote models.
This commit is contained in:
parent
da586ae3d3
commit
5c4667ed02
8 changed files with 82 additions and 11 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
16
db/schema.rb
generated
|
|
@ -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
|
||||
|
|
|
|||
2
test/fixtures/price_reports.yml
vendored
2
test/fixtures/price_reports.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
21
test/fixtures/votes.yml
vendored
21
test/fixtures/votes.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue