From f6f618c9d533b2f37051e933a730f61fc4fc7b61 Mon Sep 17 00:00:00 2001 From: Emi Matchu Date: Tue, 19 Nov 2024 15:52:52 -0800 Subject: [PATCH] Add `Item.is{_not}_modeled` scopes, for use in search later We're now caching `predicted_fully_modeled?` on the database record, so we can query by it in the database! I'm moving on from the model I did in Impress 2020, of writing really big fancy single-source-of-truth queries based on the assets themselves. I see the merit of that in terms of theoretical reliability, but in practice I think it will be *more* reliable to have one *in-code* definition of modeling status (which we need anyway for generating the homepage modeling requests), and just save that in a queryable way. --- app/controllers/outfits_controller.rb | 3 ++- app/models/item.rb | 20 ++++++++++++++++--- ...cached_predicted_fully_modeled_to_items.rb | 16 +++++++++++++++ db/schema.rb | 3 ++- spec/models/item_spec.rb | 16 +++++++-------- 5 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 db/migrate/20241119214543_add_cached_predicted_fully_modeled_to_items.rb diff --git a/app/controllers/outfits_controller.rb b/app/controllers/outfits_controller.rb index ecc8b76f..9e61e929 100644 --- a/app/controllers/outfits_controller.rb +++ b/app/controllers/outfits_controller.rb @@ -52,7 +52,8 @@ class OutfitsController < ApplicationController newest_items = Item.newest. select(:id, :name, :updated_at, :thumbnail_url, :rarity_index, - :is_manually_nc, :cached_compatible_body_ids) + :is_manually_nc, :cached_compatible_body_ids, + :cached_predicted_fully_modeled) .limit(18) @newest_modeled_items, @newest_unmodeled_items = newest_items.partition(&:predicted_fully_modeled?) diff --git a/app/models/item.rb b/app/models/item.rb index 51fab75a..e25bf6d8 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -65,6 +65,12 @@ class Item < ApplicationRecord where('description NOT LIKE ?', '%' + sanitize_sql_like(PAINTBRUSH_SET_DESCRIPTION) + '%') } + scope :is_modeled, -> { + where(cached_predicted_fully_modeled: true) + } + scope :is_not_modeled, -> { + where(cached_predicted_fully_modeled: false) + } scope :occupies, ->(zone_label) { Zone.matching_label(zone_label). map { |z| occupies_zone_id(z.id) }.reduce(none, &:or) @@ -263,12 +269,19 @@ class Item < ApplicationRecord end def update_cached_fields - # Reload our associations, so they include any new records. + # First, clear out some cached instance variables we use for performance, + # to ensure we recompute the latest values. + @predicted_body_ids = nil + @predicted_missing_body_ids = nil + + # We also need to reload our associations, so they include any new records. swf_assets.reload - # Then, compute and save our cached fields. + # Finally, compute and save our cached fields. self.cached_occupied_zone_ids = occupied_zone_ids self.cached_compatible_body_ids = compatible_body_ids(use_cached: false) + self.cached_predicted_fully_modeled = + predicted_fully_modeled?(use_cached: false) self.save! end @@ -387,7 +400,8 @@ class Item < ApplicationRecord body_ids_by_species_by_color end - def predicted_fully_modeled? + def predicted_fully_modeled?(use_cached: true) + return cached_predicted_fully_modeled? if use_cached predicted_missing_body_ids.empty? end diff --git a/db/migrate/20241119214543_add_cached_predicted_fully_modeled_to_items.rb b/db/migrate/20241119214543_add_cached_predicted_fully_modeled_to_items.rb new file mode 100644 index 00000000..a0779b7c --- /dev/null +++ b/db/migrate/20241119214543_add_cached_predicted_fully_modeled_to_items.rb @@ -0,0 +1,16 @@ +class AddCachedPredictedFullyModeledToItems < ActiveRecord::Migration[7.2] + def change + add_column :items, :cached_predicted_fully_modeled, :boolean, + default: false, null: false + + reversible do |direction| + direction.up do + puts "Updating cached item fields for all items…" + Item.includes(:swf_assets).find_in_batches.with_index do |items, batch| + puts "Updating item batch ##{batch+1}…" + items.each(&:update_cached_fields) + end + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 5ba19453..6c888b83 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[7.2].define(version: 2024_11_16_041926) do +ActiveRecord::Schema[7.2].define(version: 2024_11_19_214543) do create_table "alt_styles", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.integer "species_id", null: false t.integer "color_id", null: false @@ -139,6 +139,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_11_16_041926) do t.integer "dyeworks_base_item_id" t.string "cached_occupied_zone_ids", default: "" t.text "cached_compatible_body_ids", default: "" + t.boolean "cached_predicted_fully_modeled", default: false, null: false t.index ["dyeworks_base_item_id"], name: "index_items_on_dyeworks_base_item_id" t.index ["modeling_status_hint", "created_at", "id"], name: "items_modeling_status_hint_and_created_at_and_id" t.index ["modeling_status_hint", "created_at"], name: "items_modeling_status_hint_and_created_at" diff --git a/spec/models/item_spec.rb b/spec/models/item_spec.rb index c76ff167..1eacb8b7 100644 --- a/spec/models/item_spec.rb +++ b/spec/models/item_spec.rb @@ -41,21 +41,21 @@ RSpec.describe Item do it("predicts no more compatible bodies") do expect(item.predicted_missing_body_ids).to be_empty end - pending("appears in Item.is_modeled") do - expect(Item.is_modeled.find(item.id)).to be_present + it("appears in Item.is_modeled") do + expect(Item.is_modeled.find_by_id(item.id)).to be_present end - pending("does not appear in Item.is_not_modeled") do - expect(Item.is_not_modeled.find(item.id)).to be_nil + it("does not appear in Item.is_not_modeled") do + expect(Item.is_not_modeled.find_by_id(item.id)).to be_nil end end shared_examples "a not-fully-modeled item" do it("is not fully modeled") { should_not be_predicted_fully_modeled } - pending("does not appear in Item.is_modeled") do - expect(Item.is_modeled.find(item.id)).to be_nil + it("does not appear in Item.is_modeled") do + expect(Item.is_modeled.find_by_id(item.id)).to be_nil end - pending("appears in in Item.is_not_modeled") do - expect(Item.is_not_modeled.find(item.id)).to be_present + it("appears in Item.is_not_modeled") do + expect(Item.is_not_modeled.find_by_id(item.id)).to be_present end end