feat(models): extend claim with Claim and Comment
This commit is contained in:
1 parent
4015436e50
commit
6a702a1ece
5 files changed
+156
No files matched your search
@@ -1,3 +1,84 @@
|
||||
require 'csv'
|
||||
|
||||
class Post < Prism::Orm::Model
|
||||
# Result of .import_csv below - `imported` are posts that actually made
|
||||
# it to disk, `skipped` are { title:, errors: } pairs for rows that
|
||||
# never did (blank title, or a slug collision that outlived
|
||||
# MAX_SLUG_ATTEMPTS).
|
||||
ImportResult = Struct.new(:imported, :skipped)
|
||||
|
||||
MAX_SLUG_ATTEMPTS = 20
|
||||
|
||||
belongs_to :user
|
||||
has_many :comments, dependent: :destroy
|
||||
has_many :likes, as: :likeable, dependent: :destroy
|
||||
# dependent: :destroy, not :nullify - same reasoning as comments/likes
|
||||
# above: Claim is paranoid too (see the model), so trashing this post
|
||||
# soft-deletes its claims along with it and #restore(recursive: true)
|
||||
# brings them back, rather than a plain trash permanently erasing the
|
||||
# record of who already won a giveaway slot.
|
||||
has_many :claims, dependent: :destroy
|
||||
|
||||
# A deleted post lands in the trash (see PostsController#destroy/#trash/
|
||||
# #restore) instead of vanishing outright - Post.all/#find already hide
|
||||
# it everywhere else in the app for free. Comment and Like are paranoid
|
||||
# too, so the `dependent: :destroy` cascades above soft-delete them the
|
||||
# moment a post is trashed instead of removing them for real, and
|
||||
# PostsController#restore(recursive: true) brings them all back together
|
||||
# with the post (see Associable#cascade_restore_dependent_associations).
|
||||
acts_as_paranoid
|
||||
|
||||
# Every post starts life as a draft (column default 0, see the
|
||||
# add_status_to_posts migration) - only #publish! on posts#show makes it
|
||||
# show up on the public index (see PostsController#index below).
|
||||
enum :status, { draft: 0, published: 1, archived: 2 }
|
||||
|
||||
# Prism::Orm::Attributes: casts the "featured" checkbox's form value
|
||||
# ("1"/"0", always a String off an HTTP request - see the hidden-field
|
||||
# trick in _form.erb) into a real TrueClass/FalseClass on write, instead
|
||||
# of leaving the literal string "1" sitting in the featured column. That
|
||||
# distinction is not cosmetic: `where(featured: true)` (see the
|
||||
# .featured scope below and PostsController#index's `?featured=1`
|
||||
# filter) does a plain Ruby `==` against whatever is actually stored,
|
||||
# so a stored String would never match a query for the Boolean `true`.
|
||||
attribute :featured, :boolean, default: false
|
||||
|
||||
# Same reasoning as TypeCasting.cast_integer's own comment: blank input
|
||||
# from the (optional) giveaway_slots form field casts to nil, not 0 - nil
|
||||
# means "not a giveaway post" (see #giveaway? below), 0 means "was a
|
||||
# giveaway, every slot already claimed", and those are not the same
|
||||
# state for #giveaway_sold_out? to report.
|
||||
attribute :giveaway_slots, :integer
|
||||
|
||||
scope :featured, -> { where(featured: true) }
|
||||
|
||||
# acts_as_list scoped by the featured flag itself: every post gets a
|
||||
# position within its own featured/non-featured group (the non-featured
|
||||
# group's numbers are never shown or reordered - see posts#index and
|
||||
# PostsController#move_up/#move_down, both gated on featured_only), so
|
||||
# toggling a post's own featured flag on/off cheaply hops it between two
|
||||
# already-maintained lists (append to the bottom of whichever group it
|
||||
# lands in) instead of needing a bespoke third state to track "not in
|
||||
# any list yet". Editors use #move_up/#move_down to curate which
|
||||
# featured posts lead the homepage instead of always showing the most
|
||||
# recently featured one first.
|
||||
acts_as_list column: :position, scope: :featured
|
||||
|
||||
# Backed by the range-capable secondary index on created_at
|
||||
# (db/migrate/20260907000004_add_index_on_posts_created_at.rb) - the
|
||||
# gte/lte hash this builds is exactly what
|
||||
# Query::ConditionNode#indexable_ranges recognizes, so PostsController#index
|
||||
# chaining this onto .published (see below) hits
|
||||
# Store#lookup_by_index_range with an O(log n) binary search over
|
||||
# sorted_values instead of a full table scan. See Store#lookup_by_index_range.
|
||||
scope :created_within, ->(days) { where(created_at: { gte: Time.now.utc - (days.to_i * 86_400) }) }
|
||||
|
||||
# Lets the author moderate comments (edit or remove) from the same form
|
||||
# used to edit the post itself - reject_if: :all_blank so a moderation
|
||||
# form that only ever submits EXISTING comment rows (no "add a comment"
|
||||
# field on this screen) never has to worry about accidentally building
|
||||
# a blank new one.
|
||||
accepts_nested_attributes_for :comments, allow_destroy: true, reject_if: :all_blank
|
||||
|
||||
validates :title, presence: true, length: { minimum: 3 }
|
||||
end
|
||||
Reference in new issue
Block a user