Model threadables

This commit is contained in:
Jose Farias
2024-10-23 14:10:04 -06:00
parent 06ea38a9f7
commit 35a31e3203
17 changed files with 156 additions and 89 deletions
+8 -23
View File
@@ -1,35 +1,20 @@
class Bubble::Thread
attr_reader :bubble
def initialize(bubble)
@bubble = bubble
end
def entries
sorted_entries.chunk_while { |a, b| consecutive_events?(a, b) }.map.with_index { |entries, index| roll_up(entries, index) }
@entries ||= bubble.thread_entries
end
def to_partial_path
"bubbles/threads/thread"
def latest_rollup
if entries.last&.rollup?
entries.last.rollup
else
Rollup.new bubble: bubble
end
end
private
delegate :events, :comments, to: :bubble, private: true
def sorted_entries
(events + comments).sort_by(&:created_at)
end
def consecutive_events?(a, b)
[ a, b ] in [ Event, Event ]
end
def roll_up(entries, position)
case entries.first
when Comment
entries.sole
when Event
Rollup.new self, entries, first_position: position.zero?
end
end
attr_reader :bubble
end
-60
View File
@@ -1,60 +0,0 @@
class Bubble::Thread::Rollup
def initialize(thread, entries, first_position: false)
@thread = thread
@entries = entries
@first_position = first_position
end
def body
collapsed_entries.map { |entry, chunk_size| summarize(entry, chunk_size) }.to_sentence.upcase_first
end
def to_partial_path
"bubbles/threads/rollup"
end
private
attr_reader :thread, :entries, :first_position
delegate :time_ago_in_words, to: "ApplicationController.helpers", private: true
def collapsed_entries
sorted_entries.chunk_while { |a, b| repeated_boosts?(a, b) }.map { |chunk| [ chunk.last, chunk.size ] }
end
def sorted_entries
entries.sort_by do |entry|
case entry.action
when "created" then [ 1, entry.created_at ]
when "assigned" then [ 2, entry.created_at ]
when "staged", "unstaged" then [ 3, entry.created_at ]
when "boosted" then [ 4, entry.creator, entry.created_at ]
end
end
end
def repeated_boosts?(a, b)
a.action == "boosted" && a.slice(:action, :creator_id) == b.slice(:action, :creator_id)
end
def summarize(entry, chunk_size)
case entry.action
when "created"
"added by #{entry.creator.name} #{time_ago_in_words(entry.created_at)} ago"
when "assigned"
summary = "assigned to #{entry.assignees.map(&:name).to_sentence}"
summary += " #{time_ago_in_words(entry.created_at)} ago" unless first_position?
summary
when "staged"
"#{entry.creator.name} moved this to '#{entry.stage.name}'"
when "unstaged"
"#{entry.creator.name} removed this from '#{entry.stage.name}'"
when "boosted"
"#{entry.creator.name} +#{chunk_size}"
end
end
def first_position?
first_position
end
end
+9 -1
View File
@@ -1,5 +1,13 @@
module Bubble::Threaded
extend ActiveSupport::Concern
included do
has_many :thread_entries, -> { chronologically }, dependent: :destroy
delegate :latest_rollup, to: :thread
end
def thread
Bubble::Thread.new self
@thread ||= Bubble::Thread.new self
end
end
+1 -1
View File
@@ -1,5 +1,5 @@
class Comment < ApplicationRecord
include Searchable
include Searchable, Threadable
belongs_to :bubble, touch: true
belongs_to :creator, class_name: "User", default: -> { Current.user }
View File
+11
View File
@@ -0,0 +1,11 @@
module Threadable
extend ActiveSupport::Concern
included do
has_one :thread_entry, as: :threadable, dependent: :destroy
after_create { create_thread_entry! bubble: bubble }
after_update { bubble.touch }
after_touch { bubble.touch }
end
end
+4 -3
View File
@@ -1,12 +1,13 @@
class Event < ApplicationRecord
THREADABLE_ACTIONS = %w[ assigned boosted created staged unstaged ]
include Assignments, Stages
belongs_to :creator, class_name: "User"
belongs_to :bubble, touch: true
belongs_to :rollup, default: -> { bubble.latest_rollup }
has_one :account, through: :creator
scope :threadable, -> { where action: THREADABLE_ACTIONS }
store_accessor :particulars, :creator_name
scope :chronologically, -> { order created_at: :asc, id: :desc }
end
+7
View File
@@ -0,0 +1,7 @@
class Rollup < ApplicationRecord
include Threadable
belongs_to :bubble
has_many :events, -> { chronologically }
end
+7
View File
@@ -0,0 +1,7 @@
class ThreadEntry < ApplicationRecord
belongs_to :bubble
delegated_type :threadable, types: %w[ Comment Rollup ]
scope :chronologically, -> { order created_at: :asc, id: :desc }
end
@@ -0,0 +1,10 @@
class CreateThreadEntries < ActiveRecord::Migration[8.0]
def change
create_table :thread_entries do |t|
t.references :bubble, null: false, foreign_key: true
t.references :threadable, polymorphic: true, null: false, index: { unique: true }
t.timestamps
end
end
end
@@ -0,0 +1,9 @@
class CreateRollups < ActiveRecord::Migration[8.0]
def change
create_table :rollups do |t|
t.references :bubble, null: false, foreign_key: true
t.timestamps
end
end
end
@@ -0,0 +1,6 @@
class AddRollupReferenceToEvents < ActiveRecord::Migration[8.0]
def change
# FIXME: add null: false in another migration after data migrations happen
add_reference :events, :rollup, foreign_key: true
end
end
Generated
+23 -1
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: 2024_10_18_223027) do
ActiveRecord::Schema[8.0].define(version: 2024_10_22_180133) do
create_table "accesses", force: :cascade do |t|
t.integer "bucket_id", null: false
t.integer "user_id", null: false
@@ -117,8 +117,10 @@ ActiveRecord::Schema[8.0].define(version: 2024_10_18_223027) do
t.string "action", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "rollup_id"
t.index ["bubble_id", "action"], name: "index_events_on_bubble_id_and_action"
t.index ["creator_id"], name: "index_events_on_creator_id"
t.index ["rollup_id"], name: "index_events_on_rollup_id"
end
create_table "pops", force: :cascade do |t|
@@ -130,6 +132,13 @@ ActiveRecord::Schema[8.0].define(version: 2024_10_18_223027) do
t.index ["user_id"], name: "index_pops_on_user_id"
end
create_table "rollups", force: :cascade do |t|
t.integer "bubble_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["bubble_id"], name: "index_rollups_on_bubble_id"
end
create_table "sessions", force: :cascade do |t|
t.integer "user_id", null: false
t.string "ip_address"
@@ -156,6 +165,16 @@ ActiveRecord::Schema[8.0].define(version: 2024_10_18_223027) do
t.index ["account_id"], name: "index_tags_on_account_id"
end
create_table "thread_entries", force: :cascade do |t|
t.integer "bubble_id", null: false
t.string "threadable_type", null: false
t.integer "threadable_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["bubble_id"], name: "index_thread_entries_on_bubble_id"
t.index ["threadable_type", "threadable_id"], name: "index_thread_entries_on_threadable", unique: true
end
create_table "users", force: :cascade do |t|
t.integer "account_id", null: false
t.string "name", null: false
@@ -187,11 +206,14 @@ ActiveRecord::Schema[8.0].define(version: 2024_10_18_223027) do
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 "bubbles", "workflow_stages", column: "stage_id"
add_foreign_key "events", "rollups"
add_foreign_key "pops", "bubbles"
add_foreign_key "pops", "users"
add_foreign_key "rollups", "bubbles"
add_foreign_key "sessions", "users"
add_foreign_key "taggings", "bubbles"
add_foreign_key "taggings", "tags"
add_foreign_key "thread_entries", "bubbles"
add_foreign_key "users", "accounts"
add_foreign_key "workflow_stages", "workflows"
add_foreign_key "workflows", "accounts"
+1
View File
@@ -8,6 +8,7 @@ logo_agreement_kevin:
body: Same, lets do it.
creator: kevin
bubble: logo
created_at: <%= 2.hours.ago %>
layout_overflowing_david:
body: The text is overflowing the container.
+12
View File
@@ -4,6 +4,7 @@ logo_created:
action: created
particulars: <%= { creator_name: "David" }.to_json %>
created_at: <%= 1.week.ago %>
rollup: logo_initial_activity
logo_assignment_jz:
creator: david
@@ -11,6 +12,7 @@ logo_assignment_jz:
action: assigned
particulars: <%= { assignee_names: [ "JZ" ], creator_name: "David" }.to_json %>
created_at: <%= 1.week.ago + 1.hour %>
rollup: logo_initial_activity
logo_boost_dhh:
creator: david
@@ -18,6 +20,7 @@ logo_boost_dhh:
action: boosted
particulars: <%= { creator_name: "David" }.to_json %>
created_at: <%= 1.week.ago + 2.hours %>
rollup: logo_initial_activity
logo_assignment_km:
creator: david
@@ -25,6 +28,7 @@ logo_assignment_km:
action: assigned
particulars: <%= { assignee_names: [ "Kevin" ], creator_name: "David" }.to_json %>
created_at: <%= 1.day.ago %>
rollup: logo_second_activity
logo_boost_km1:
creator: kevin
@@ -32,6 +36,7 @@ logo_boost_km1:
action: boosted
particulars: <%= { creator_name: "Kevin" }.to_json %>
created_at: <%= 1.day.ago + 1.hour %>
rollup: logo_second_activity
logo_boost_km2:
creator: kevin
@@ -39,6 +44,7 @@ logo_boost_km2:
action: boosted
particulars: <%= { creator_name: "Kevin" }.to_json %>
created_at: <%= 1.day.ago + 2.hours %>
rollup: logo_second_activity
logo_boost_jz1:
creator: jz
@@ -46,6 +52,7 @@ logo_boost_jz1:
action: boosted
particulars: <%= { creator_name: "JZ" }.to_json %>
created_at: <%= 1.day.ago + 3.hours %>
rollup: logo_second_activity
logo_boost_jz2:
creator: jz
@@ -53,6 +60,7 @@ logo_boost_jz2:
action: boosted
particulars: <%= { creator_name: "JZ" }.to_json %>
created_at: <%= 1.hour.ago %>
rollup: logo_third_activity
layout_created:
creator: david
@@ -60,6 +68,7 @@ layout_created:
action: created
particulars: <%= { creator_name: "David" }.to_json %>
created_at: <%= 1.week.ago %>
rollup: layout_initial_activity
layout_assignment_jz:
creator: david
@@ -67,6 +76,7 @@ layout_assignment_jz:
action: assigned
particulars: <%= { assignee_names: [ "JZ" ], creator_name: "David" }.to_json %>
created_at: <%= 1.hour.ago %>
rollup: layout_initial_activity
text_created:
creator: kevin
@@ -74,6 +84,7 @@ text_created:
action: created
particulars: <%= { creator_name: "Kevin" }.to_json %>
created_at: <%= 1.week.ago %>
rollup: text_initial_activity
shipping_created:
creator: kevin
@@ -81,3 +92,4 @@ shipping_created:
action: created
particulars: <%= { creator_name: "Kevin" }.to_json %>
created_at: <%= 1.week.ago %>
rollup: shipping_initial_activity
+20
View File
@@ -0,0 +1,20 @@
logo_initial_activity:
bubble: logo
created_at: <%= 1.week.ago %>
logo_second_activity:
bubble: logo
created_at: <%= 1.day.ago %>
logo_third_activity:
bubble: logo
created_at: <%= 1.hour.ago %>
layout_initial_activity:
bubble: layout
text_initial_activity:
bubble: text
shipping_initial_activity:
bubble: shipping
+28
View File
@@ -0,0 +1,28 @@
logo_1:
bubble: logo
threadable: logo_initial_activity (Rollup)
created_at: <%= 1.week.ago %>
logo_2:
bubble: logo
threadable: logo_agreement_jz (Comment)
created_at: <%= 2.days.ago %>
logo_3:
bubble: logo
threadable: logo_second_activity (Rollup)
created_at: <%= 1.day.ago %>
logo_4:
bubble: logo
threadable: logo_agreement_kevin (Comment)
created_at: <%= 2.hours.ago %>
logo_5:
bubble: logo
threadable: logo_third_activity (Rollup)
created_at: <%= 1.hour.ago %>
layout_1:
bubble: layout
threadable: layout_overflowing_david (Comment)