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: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: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-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
|
# id :bigint not null, primary key
|
||||||
# discount_price :decimal(, )
|
# discount_price :decimal(, )
|
||||||
# downvotes :integer
|
|
||||||
# price :decimal(, )
|
# price :decimal(, )
|
||||||
# reported_at :datetime
|
# reported_at :datetime
|
||||||
# upvotes :integer
|
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# product_id :integer not null
|
# product_id :integer not null
|
||||||
|
|
@ -27,10 +25,11 @@
|
||||||
# fk_rails_... (user_id => users.id)
|
# fk_rails_... (user_id => users.id)
|
||||||
#
|
#
|
||||||
class PriceReport < ApplicationRecord
|
class PriceReport < ApplicationRecord
|
||||||
|
include Votable
|
||||||
|
|
||||||
belongs_to :product
|
belongs_to :product
|
||||||
belongs_to :store
|
belongs_to :store
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
has_many :moderation_logs, as: :moderatable, dependent: :destroy
|
has_many :moderation_logs, as: :moderatable, dependent: :destroy
|
||||||
has_many :votes, as: :votable, dependent: :destroy
|
|
||||||
|
|
||||||
end
|
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
|
class Vote < ApplicationRecord
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :votable, polymorphic: true
|
belongs_to :votable, polymorphic: true, counter_cache: true
|
||||||
|
|
||||||
enum :direction, {upvote: 0, downvote: 1}
|
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.
|
# 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
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pg_catalog.plpgsql"
|
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|
|
create_table "price_reports", force: :cascade do |t|
|
||||||
t.decimal "price"
|
t.decimal "price"
|
||||||
t.decimal "discount_price"
|
t.decimal "discount_price"
|
||||||
t.integer "upvotes"
|
|
||||||
t.integer "downvotes"
|
|
||||||
t.datetime "reported_at"
|
t.datetime "reported_at"
|
||||||
t.integer "product_id", null: false
|
t.integer "product_id", null: false
|
||||||
t.integer "store_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
|
t.index ["email"], name: "index_users_on_email", unique: true
|
||||||
end
|
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_attachments", "active_storage_blobs", column: "blob_id"
|
||||||
add_foreign_key "active_storage_variant_records", "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"
|
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", "stores"
|
||||||
add_foreign_key "price_reports", "users"
|
add_foreign_key "price_reports", "users"
|
||||||
add_foreign_key "stores", "chains"
|
add_foreign_key "stores", "chains"
|
||||||
|
add_foreign_key "votes", "users"
|
||||||
end
|
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
|
# id :bigint not null, primary key
|
||||||
# discount_price :decimal(, )
|
# discount_price :decimal(, )
|
||||||
# downvotes :integer
|
|
||||||
# price :decimal(, )
|
# price :decimal(, )
|
||||||
# reported_at :datetime
|
# reported_at :datetime
|
||||||
# upvotes :integer
|
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# product_id :integer 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
|
# 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:
|
one:
|
||||||
user: one
|
user: one
|
||||||
votable=references{polymorphic}: MyString
|
votable=references{polymorphic}: MyString
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,8 @@
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# discount_price :decimal(, )
|
# discount_price :decimal(, )
|
||||||
# downvotes :integer
|
|
||||||
# price :decimal(, )
|
# price :decimal(, )
|
||||||
# reported_at :datetime
|
# reported_at :datetime
|
||||||
# upvotes :integer
|
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# product_id :integer 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"
|
require "test_helper"
|
||||||
|
|
||||||
class VoteTest < ActiveSupport::TestCase
|
class VoteTest < ActiveSupport::TestCase
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue