Model threadables
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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,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 }
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
class Rollup < ApplicationRecord
|
||||
include Threadable
|
||||
|
||||
belongs_to :bubble
|
||||
|
||||
has_many :events, -> { chronologically }
|
||||
end
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Vendored
+1
@@ -8,6 +8,7 @@ logo_agreement_kevin:
|
||||
body: Same, let’s do it.
|
||||
creator: kevin
|
||||
bubble: logo
|
||||
created_at: <%= 2.hours.ago %>
|
||||
|
||||
layout_overflowing_david:
|
||||
body: The text is overflowing the container.
|
||||
|
||||
Vendored
+12
@@ -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
|
||||
|
||||
Vendored
+20
@@ -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
|
||||
Vendored
+28
@@ -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)
|
||||
Reference in New Issue
Block a user