diff --git a/app/controllers/page_collection_submissions_controller.rb b/app/controllers/page_collection_submissions_controller.rb index 311955fa..99a17aed 100644 --- a/app/controllers/page_collection_submissions_controller.rb +++ b/app/controllers/page_collection_submissions_controller.rb @@ -2,6 +2,8 @@ class PageCollectionSubmissionsController < ApplicationController before_action :set_page_collection, only: [:index] before_action :set_page_collection_submission, only: [:show, :edit, :update, :destroy, :approve, :pass] + before_action :require_collection_ownership, only: [:index, :edit, :update, :destroy, :pass, :approve] + # GET /page_collection_submissions def index @page_collection_submissions = @page_collection.page_collection_submissions.where(accepted_at: nil) @@ -83,6 +85,10 @@ class PageCollectionSubmissionsController < ApplicationController private + def require_collection_ownership + raise "Forbidden!" unless user_signed_in? && @page_collection.user_id == current_user.id + end + def set_page_collection @page_collection = PageCollection.find(params[:page_collection_id]) end diff --git a/app/controllers/page_collections_controller.rb b/app/controllers/page_collections_controller.rb index 69f3c2b1..a1dcfc46 100644 --- a/app/controllers/page_collections_controller.rb +++ b/app/controllers/page_collections_controller.rb @@ -7,6 +7,8 @@ class PageCollectionsController < ApplicationController before_action :set_page_collection, only: [:show, :edit, :by_user, :update, :destroy, :follow, :unfollow, :report] before_action :set_submittable_content, only: [:show, :by_user] + before_action :require_collection_ownership, only: [:edit, :update, :destroy] + layout 'tailwind', only: [:index, :show, :edit] # GET /page_collections @@ -153,6 +155,10 @@ class PageCollectionsController < ApplicationController private + def require_collection_ownership + raise "Forbidden!" unless user_signed_in? && @page_collection.user_id == current_user.id + end + # Use callbacks to share common setup or constraints between actions. def set_page_collection @page_collection = PageCollection.find_by(id: params[:id]) diff --git a/app/jobs/cache_attribute_word_count_job.rb b/app/jobs/cache_attribute_word_count_job.rb index fd1238dc..80caa5ad 100644 --- a/app/jobs/cache_attribute_word_count_job.rb +++ b/app/jobs/cache_attribute_word_count_job.rb @@ -5,9 +5,13 @@ class CacheAttributeWordCountJob < ApplicationJob attribute_id = args.shift attribute = Attribute.find_by(id: attribute_id) - return if attribute.nil? - return if attribute.value.nil? || attribute.value.blank? + # If we have a blank/null value, ezpz 0 words + if attribute.nil? || attribute.value.nil? || attribute.value.blank? + attribute.update_column(:word_count_cache, 0) + return + end + # If we actually have some content, use a smart WordCountAnalyzer instead of just splitting on spaces word_count = WordCountAnalyzer::Counter.new( ellipsis: 'no_special_treatment', hyperlink: 'count_as_one', @@ -25,6 +29,6 @@ class CacheAttributeWordCountJob < ApplicationJob stray_punctuation: 'ignore' ).count(attribute.value) - attribute.update!(word_count_cache: word_count) + attribute.update_column(:word_count_cache, word_count) end end diff --git a/app/jobs/cache_sum_attribute_word_count_job.rb b/app/jobs/cache_sum_attribute_word_count_job.rb index 63dad69a..37970c29 100644 --- a/app/jobs/cache_sum_attribute_word_count_job.rb +++ b/app/jobs/cache_sum_attribute_word_count_job.rb @@ -8,13 +8,24 @@ class CacheSumAttributeWordCountJob < ApplicationJob entity = entity_type.constantize.find_by(id: entity_id) return if entity.nil? - sum_attribute_word_count = Attribute.where(entity_type: entity_type, entity_id: entity_id).sum(:word_count_cache) + save_daily_word_count_changes(entity) + end + + def save_daily_word_count_changes(entity) + # Grab the latest total word count for this entity + sum_attribute_word_count = Attribute.where(entity_type: entity.class.name, entity_id: entity.id).sum(:word_count_cache) + + # Cache the total word count onto the model, too + entity.update(cached_word_count: sum_attribute_word_count) + + # Create or re-use an existing WordCountUpdate for today update = entity.word_count_updates.find_or_initialize_by( for_date: DateTime.current, ) update.word_count = sum_attribute_word_count update.user_id ||= entity.user_id + # Save! update.save! end end diff --git a/app/models/page_types/content_page.rb b/app/models/page_types/content_page.rb index d4bff185..54be87cc 100644 --- a/app/models/page_types/content_page.rb +++ b/app/models/page_types/content_page.rb @@ -4,7 +4,7 @@ class ContentPage < ApplicationRecord belongs_to :user belongs_to :universe - attr_accessor :favorite + attr_accessor :favorite, :cached_word_count include Authority::Abilities self.authorizer_name = 'ContentPageAuthorizer' @@ -39,6 +39,6 @@ class ContentPage < ApplicationRecord end def self.polymorphic_content_fields - [:id, :name, :favorite, :page_type, :user_id, :created_at, :updated_at, :deleted_at, :archived_at, :privacy] + [:id, :name, :favorite, :page_type, :user_id, :cached_word_count, :created_at, :updated_at, :deleted_at, :archived_at, :privacy] end end diff --git a/app/models/serializers/content_serializer.rb b/app/models/serializers/content_serializer.rb index 5a9684d0..5b4bc564 100644 --- a/app/models/serializers/content_serializer.rb +++ b/app/models/serializers/content_serializer.rb @@ -10,6 +10,8 @@ class ContentSerializer attr_accessor :raw_model attr_accessor :class_name, :class_color, :class_text_color, :class_icon + attr_accessor :cached_word_count + attr_accessor :data # name: 'blah, # categories: [ @@ -45,6 +47,8 @@ class ContentSerializer self.page_tags = content.page_tags.pluck(:tag) || [] self.documents = content.documents || [] + self.cached_word_count = content.cached_word_count + self.data = { name: content.try(:name), description: content.try(:description), diff --git a/app/views/content/show.html.erb b/app/views/content/show.html.erb index 09eebf25..f642e546 100644 --- a/app/views/content/show.html.erb +++ b/app/views/content/show.html.erb @@ -262,6 +262,16 @@ content: @serialized_content } %> + + diff --git a/db/migrate/20221202003053_add_cached_word_counts_to_worldbuilding_pages.rb b/db/migrate/20221202003053_add_cached_word_counts_to_worldbuilding_pages.rb new file mode 100644 index 00000000..30a1bdc6 --- /dev/null +++ b/db/migrate/20221202003053_add_cached_word_counts_to_worldbuilding_pages.rb @@ -0,0 +1,9 @@ +class AddCachedWordCountsToWorldbuildingPages < ActiveRecord::Migration[6.1] + def change + Rails.application.config.content_types[:all].each do |content_type| + add_column content_type.name.downcase.pluralize, :cached_word_count, :integer # Add column without default lock + change_column_default content_type.name.downcase.pluralize, :cached_word_count, from: nil, to: 0 # Add default value + change_column_null content_type.name.downcase.pluralize, :cached_word_count, true # Ensure null is still allowed (for now) + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 99fc2352..e1f5fd92 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_08_17_213907) do +ActiveRecord::Schema.define(version: 2022_12_02_003053) do create_table "active_storage_attachments", force: :cascade do |t| t.string "name", null: false @@ -277,6 +277,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["universe_id"], name: "index_buildings_on_universe_id" t.index ["user_id", "universe_id", "deleted_at"], name: "index_buildings_on_user_id_and_universe_id_and_deleted_at" t.index ["user_id"], name: "index_buildings_on_user_id" @@ -431,6 +432,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_characters_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_characters_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_characters_on_deleted_at_and_user_id" @@ -459,6 +461,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["universe_id"], name: "index_conditions_on_universe_id" t.index ["user_id", "universe_id", "deleted_at"], name: "index_conditions_on_user_id_and_universe_id_and_deleted_at" t.index ["user_id"], name: "index_conditions_on_user_id" @@ -626,6 +629,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id", "universe_id"], name: "index_continents_on_deleted_at_and_id_and_universe_id" t.index ["deleted_at", "id", "user_id"], name: "index_continents_on_deleted_at_and_id_and_user_id" t.index ["deleted_at", "id"], name: "index_continents_on_deleted_at_and_id" @@ -670,6 +674,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_countries_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_countries_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_countries_on_deleted_at_and_user_id" @@ -828,6 +833,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_creatures_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_creatures_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_creatures_on_deleted_at_and_user_id" @@ -873,6 +879,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_deities_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_deities_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_deities_on_deleted_at_and_user_id" @@ -1338,6 +1345,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_floras_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_floras_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_floras_on_deleted_at_and_user_id" @@ -1373,6 +1381,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["universe_id"], name: "index_foods_on_universe_id" t.index ["user_id", "universe_id", "deleted_at"], name: "index_foods_on_user_id_and_universe_id_and_deleted_at" t.index ["user_id"], name: "index_foods_on_user_id" @@ -1492,6 +1501,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_governments_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_governments_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_governments_on_deleted_at_and_user_id" @@ -1588,6 +1598,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_groups_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_groups_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_groups_on_deleted_at_and_user_id" @@ -1668,6 +1679,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_items_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_items_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_items_on_deleted_at_and_user_id" @@ -1690,6 +1702,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["universe_id"], name: "index_jobs_on_universe_id" t.index ["user_id", "universe_id", "deleted_at"], name: "index_jobs_on_user_id_and_universe_id_and_deleted_at" t.index ["user_id"], name: "index_jobs_on_user_id" @@ -1765,6 +1778,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_landmarks_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_landmarks_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_landmarks_on_deleted_at_and_user_id" @@ -1797,6 +1811,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_languages_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_languages_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_languages_on_deleted_at_and_user_id" @@ -1911,6 +1926,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_locations_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_locations_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_locations_on_deleted_at_and_user_id" @@ -2235,6 +2251,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.string "page_type", default: "Lore" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.integer "cached_word_count", default: 0 t.index ["universe_id"], name: "index_lores_on_universe_id" t.index ["user_id"], name: "index_lores_on_user_id" end @@ -2271,6 +2288,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_magics_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_magics_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_magics_on_deleted_at_and_user_id" @@ -2651,6 +2669,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_planets_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_planets_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_planets_on_deleted_at_and_user_id" @@ -2705,6 +2724,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_races_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_races_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_races_on_deleted_at_and_user_id" @@ -2779,6 +2799,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_religions_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_religions_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_religions_on_deleted_at_and_user_id" @@ -2845,6 +2866,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_scenes_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_scenes_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_scenes_on_deleted_at_and_user_id" @@ -2867,6 +2889,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["universe_id"], name: "index_schools_on_universe_id" t.index ["user_id", "universe_id", "deleted_at"], name: "index_schools_on_user_id_and_universe_id_and_deleted_at" t.index ["user_id"], name: "index_schools_on_user_id" @@ -2916,6 +2939,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["universe_id"], name: "index_sports_on_universe_id" t.index ["user_id", "universe_id", "deleted_at"], name: "index_sports_on_user_id_and_universe_id_and_deleted_at" t.index ["user_id"], name: "index_sports_on_user_id" @@ -2980,6 +3004,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_technologies_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_technologies_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_technologies_on_deleted_at_and_user_id" @@ -3474,6 +3499,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_towns_on_deleted_at_and_id" t.index ["deleted_at", "universe_id"], name: "index_towns_on_deleted_at_and_universe_id" t.index ["deleted_at", "user_id"], name: "index_towns_on_deleted_at_and_user_id" @@ -3495,6 +3521,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["universe_id"], name: "index_traditions_on_universe_id" t.index ["user_id", "universe_id", "deleted_at"], name: "index_traditions_on_user_id_and_universe_id_and_deleted_at" t.index ["user_id"], name: "index_traditions_on_user_id" @@ -3519,6 +3546,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["deleted_at", "id"], name: "index_universes_on_deleted_at_and_id" t.index ["deleted_at", "user_id"], name: "index_universes_on_deleted_at_and_user_id" t.index ["deleted_at"], name: "index_universes_on_deleted_at" @@ -3618,6 +3646,7 @@ ActiveRecord::Schema.define(version: 2022_08_17_213907) do t.datetime "archived_at" t.boolean "favorite", default: false t.boolean "columns_migrated_from_old_style", default: true + t.integer "cached_word_count", default: 0 t.index ["universe_id"], name: "index_vehicles_on_universe_id" t.index ["user_id", "universe_id", "deleted_at"], name: "index_vehicles_on_user_id_and_universe_id_and_deleted_at" t.index ["user_id"], name: "index_vehicles_on_user_id" diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake index d09c6e0f..1b06f1f0 100644 --- a/lib/tasks/cache.rake +++ b/lib/tasks/cache.rake @@ -37,4 +37,29 @@ namespace :cache do end end end + + desc "Recompute ALL attribute word count caches" + task recompute_all_attribute_word_counts: :environment do + Attribute.find_each do |attribute| + begin + CacheAttributeWordCountJob.perform_now(attribute.id) + + rescue ActiveRecord::RecordInvalid => e + puts "Something died for attribute ID=#{attribute.id} (#{attribute.entity_type} #{attribute.entity_id}) but we're carrying on." + puts e.inspect + end + end + end + + desc "Recompute ALL page word count caches" + task recompute_all_page_word_counts: :environment do + Rails.application.config.content_types[:all].each do |content_type| + puts "Recomputing #{content_type.name} word counts" + + content_type.order('updated_at DESC').find_each do |entity| + sum_attribute_word_count = Attribute.where(entity_type: content_type.name, entity_id: entity.id).sum(:word_count_cache) + entity.update_column(:cached_word_count, sum_attribute_word_count) + end + end + end end