| @@ -11,9 +11,12 @@ class PostVersion < ApplicationRecord | |||||
| belongs_to :parent, class_name: 'Post', optional: true | belongs_to :parent, class_name: 'Post', optional: true | ||||
| belongs_to :created_by_user, class_name: 'User', optional: true | belongs_to :created_by_user, class_name: 'User', optional: true | ||||
| enum :event_type, create: 'create', update: 'update', discard: 'discard', restore: 'restore' | |||||
| enum :event_type, { create: 'create', | |||||
| update: 'update', | |||||
| discard: 'discard', | |||||
| restore: 'restore' }, prefix: true, validate: true | |||||
| validates :version_no, presence: true, numerically: { only_integer: true, greater_than: 0 } | |||||
| validates :version_no, presence: true, numericality: { only_integer: true, greater_than: 0 } | |||||
| validates :event_type, presence: true, inclusion: { in: event_types.keys } | validates :event_type, presence: true, inclusion: { in: event_types.keys } | ||||
| validates :url, presence: true | validates :url, presence: true | ||||
| @@ -3,7 +3,7 @@ class PostVersionRecorder | |||||
| new(post:, event_type:, created_by_user:).record! | new(post:, event_type:, created_by_user:).record! | ||||
| end | end | ||||
| def initialize post:, event_type:, created_user: | |||||
| def initialize post:, event_type:, created_by_user: | |||||
| @post = post | @post = post | ||||
| @event_type = event_type | @event_type = event_type | ||||
| @created_by_user = created_by_user | @created_by_user = created_by_user | ||||
| @@ -23,7 +23,7 @@ class PostVersionRecorder | |||||
| title: attrs[:title], | title: attrs[:title], | ||||
| url: attrs[:url], | url: attrs[:url], | ||||
| thumbnail_base: attrs[:thumbnail_base], | thumbnail_base: attrs[:thumbnail_base], | ||||
| tags: attrs[:url], | |||||
| tags: attrs[:tags], | |||||
| parent: attrs[:parent], | parent: attrs[:parent], | ||||
| original_created_from: attrs[:original_created_from], | original_created_from: attrs[:original_created_from], | ||||
| original_created_before: attrs[:original_created_before], | original_created_before: attrs[:original_created_before], | ||||
| @@ -1,5 +1,17 @@ | |||||
| class CreatePostVersions < ActiveRecord::Migration[8.0] | class CreatePostVersions < ActiveRecord::Migration[8.0] | ||||
| def change | |||||
| class Post < ApplicationRecord | |||||
| self.table_name = 'posts' | |||||
| end | |||||
| class PostTag < ApplicationRecord | |||||
| self.table_name = 'post_tags' | |||||
| end | |||||
| class PostVersion < ApplicationRecord | |||||
| self.table_name = 'post_versions' | |||||
| end | |||||
| def up | |||||
| create_table :post_versions do |t| | create_table :post_versions do |t| | ||||
| t.references :post, null: false, foreign_key: true | t.references :post, null: false, foreign_key: true | ||||
| t.integer :version_no, null: false | t.integer :version_no, null: false | ||||
| @@ -19,62 +31,45 @@ class CreatePostVersions < ActiveRecord::Migration[8.0] | |||||
| t.check_constraint "event_type IN ('create', 'update', 'discard', 'restore')" | t.check_constraint "event_type IN ('create', 'update', 'discard', 'restore')" | ||||
| end | end | ||||
| reversible do |dir| | |||||
| dir.up do | |||||
| execute <<~SQL | |||||
| INSERT INTO | |||||
| post_versions( | |||||
| post_id | |||||
| , version_no | |||||
| , event_type | |||||
| , title | |||||
| , url | |||||
| , thumbnail_base | |||||
| , tags | |||||
| , parent_id | |||||
| , original_created_from | |||||
| , original_created_before | |||||
| , created_at | |||||
| , created_by_user_id) | |||||
| SELECT | |||||
| posts.id | |||||
| , 1 | |||||
| , 'create' | |||||
| , posts.title | |||||
| , posts.url | |||||
| , posts.thumbnail_base | |||||
| , COALESCE(tag_snapshots.tags, '') | |||||
| , posts.parent_id | |||||
| , posts.original_created_from | |||||
| , posts.original_created_before | |||||
| , posts.created_at | |||||
| , posts.uploaded_user_id | |||||
| FROM | |||||
| posts | |||||
| LEFT JOIN | |||||
| ( | |||||
| SELECT | |||||
| post_tags.post_id | |||||
| , GROUP_CONCAT(tag_names.name ORDER BY tag_names.name SEPARATOR ' ') AS tags | |||||
| FROM | |||||
| post_tags | |||||
| INNER JOIN | |||||
| tags | |||||
| ON | |||||
| tags.id = post_tags.tag_id | |||||
| INNER JOIN | |||||
| tag_names | |||||
| ON | |||||
| tag_names.id = tags.tag_name_id | |||||
| WHERE | |||||
| post_tags.discarded_at IS NULL | |||||
| GROUP BY | |||||
| post_tags.post_id | |||||
| ) tag_snapshots | |||||
| ON | |||||
| tag_snapshots.post_id = posts.id | |||||
| SQL | |||||
| say_with_time 'Backfilling post_versions' do | |||||
| Post.find_in_batches(batch_size: 500) do |posts| | |||||
| post_ids = posts.map(&:id) | |||||
| tag_names_by_post_id = | |||||
| PostTag | |||||
| .joins('INNER JOIN tags ON tags.id = post_tags.tag_id') | |||||
| .joins('INNER JOIN tag_names ON tag_names.id = tags.tag_name_id') | |||||
| .where(post_id: post_ids) | |||||
| .where('post_tags.discarded_at IS NULL') | |||||
| .where('tags.discarded_at IS NULL') | |||||
| .where('tag_names.discarded_at IS NULL') | |||||
| .pluck('post_tags.post_id', 'tag_names.name') | |||||
| .each_with_object(Hash.new { |h, k| h[k] = [] }) do |(post_id, tag_name), h| | |||||
| h[post_id] << tag_name | |||||
| end | |||||
| rows = posts.map do |post| | |||||
| tags = tag_names_by_post_id[post.id].uniq.sort.join(' ') | |||||
| { post_id: post.id, | |||||
| version_no: 1, | |||||
| event_type: 'create', | |||||
| title: post.title, | |||||
| url: post.url, | |||||
| thumbnail_base: post.thumbnail_base, | |||||
| tags:, | |||||
| parent_id: post.parent_id, | |||||
| original_created_from: post.original_created_from, | |||||
| original_created_before: post.original_created_before, | |||||
| created_at: post.created_at, | |||||
| created_by_user_id: post.uploaded_user_id } | |||||
| end | |||||
| PostVersion.insert_all!(rows) if rows.present? | |||||
| end | end | ||||
| end | end | ||||
| end | end | ||||
| def down | |||||
| drop_table :post_versions | |||||
| end | |||||
| end | end | ||||
| @@ -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_03_29_034700) do | |||||
| ActiveRecord::Schema[8.0].define(version: 2026_04_09_123700) do | |||||
| create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | ||||
| t.string "name", null: false | t.string "name", null: false | ||||
| t.string "record_type", null: false | t.string "record_type", null: false | ||||
| @@ -132,6 +132,27 @@ ActiveRecord::Schema[8.0].define(version: 2026_03_29_034700) do | |||||
| t.index ["tag_id"], name: "index_post_tags_on_tag_id" | t.index ["tag_id"], name: "index_post_tags_on_tag_id" | ||||
| end | end | ||||
| create_table "post_versions", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | |||||
| t.bigint "post_id", null: false | |||||
| t.integer "version_no", null: false | |||||
| t.string "event_type", null: false | |||||
| t.string "title" | |||||
| t.string "url", limit: 768, null: false | |||||
| t.string "thumbnail_base", limit: 2000 | |||||
| t.text "tags", null: false | |||||
| t.bigint "parent_id" | |||||
| t.datetime "original_created_from" | |||||
| t.datetime "original_created_before" | |||||
| t.datetime "created_at", null: false | |||||
| t.bigint "created_by_user_id" | |||||
| t.index ["created_by_user_id"], name: "index_post_versions_on_created_by_user_id" | |||||
| t.index ["parent_id"], name: "index_post_versions_on_parent_id" | |||||
| t.index ["post_id", "version_no"], name: "index_post_versions_on_post_id_and_version_no", unique: true | |||||
| t.index ["post_id"], name: "index_post_versions_on_post_id" | |||||
| t.check_constraint "`event_type` in (_utf8mb4'create',_utf8mb4'update',_utf8mb4'discard',_utf8mb4'restore')" | |||||
| t.check_constraint "`version_no` > 0" | |||||
| end | |||||
| create_table "posts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | create_table "posts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | ||||
| t.string "title" | t.string "title" | ||||
| t.string "url", limit: 768, null: false | t.string "url", limit: 768, null: false | ||||
| @@ -362,6 +383,9 @@ ActiveRecord::Schema[8.0].define(version: 2026_03_29_034700) do | |||||
| add_foreign_key "post_tags", "tags" | add_foreign_key "post_tags", "tags" | ||||
| add_foreign_key "post_tags", "users", column: "created_user_id" | add_foreign_key "post_tags", "users", column: "created_user_id" | ||||
| add_foreign_key "post_tags", "users", column: "deleted_user_id" | add_foreign_key "post_tags", "users", column: "deleted_user_id" | ||||
| add_foreign_key "post_versions", "posts" | |||||
| add_foreign_key "post_versions", "posts", column: "parent_id" | |||||
| add_foreign_key "post_versions", "users", column: "created_by_user_id" | |||||
| add_foreign_key "posts", "posts", column: "parent_id" | add_foreign_key "posts", "posts", column: "parent_id" | ||||
| add_foreign_key "posts", "users", column: "uploaded_user_id" | add_foreign_key "posts", "users", column: "uploaded_user_id" | ||||
| add_foreign_key "settings", "users" | add_foreign_key "settings", "users" | ||||