diff --git a/TODO.md b/TODO.md index 06e30de1..39b9c252 100644 --- a/TODO.md +++ b/TODO.md @@ -38,4 +38,4 @@ - [ ] fix for IDs that have a dot in them - e.g. https://refurrer.com/users/fa@jakke. - [ ] Rich inline links to e621 e.g. https://refurrer.com/posts/fa@60070060 - [ ] Find FaPost that have favs recorded but no scan / file, enqueue scan -- [ ] Bunch of posts with empty responses: posts = Domain::Post.joins(files: :log_entry).where(files: { http_log_entries: { response_sha256: BlobFile::EMPTY_FILE_SHA256 }}).limit(10) +- [x] Bunch of posts with empty responses: posts = Domain::Post.joins(files: :log_entry).where(files: { http_log_entries: { response_sha256: BlobFile::EMPTY_FILE_SHA256 }}).limit(10) diff --git a/app/jobs/domain/e621/job/scan_user_favs_job.rb b/app/jobs/domain/e621/job/scan_user_favs_job.rb index e2164e2f..4c91ae09 100644 --- a/app/jobs/domain/e621/job/scan_user_favs_job.rb +++ b/app/jobs/domain/e621/job/scan_user_favs_job.rb @@ -128,6 +128,10 @@ class Domain::E621::Job::ScanUserFavsJob < Domain::E621::Job::Base end end + # Use reset_counters to update the counter cache after using upsert_all + Domain::User.reset_counters(user.id, :user_post_favs) + logger.info("[reset user_post_favs counter cache for user: #{user.id}]") + logger.info( [ "[favs scanned: #{post_ids.size.to_s.bold}]", diff --git a/app/jobs/domain/fa/job/base.rb b/app/jobs/domain/fa/job/base.rb index d3639328..35a08c98 100644 --- a/app/jobs/domain/fa/job/base.rb +++ b/app/jobs/domain/fa/job/base.rb @@ -565,7 +565,8 @@ class Domain::Fa::Job::Base < Scraper::JobBase ).returns(T::Boolean) end def user_disabled_or_not_found?(user, response) - if response.status_code != 200 + # HTTP 400 is returned when the user is not found + if response.status_code != 200 && response.status_code != 400 fatal_error( "http #{response.status_code}, log entry #{response.log_entry.id}", ) diff --git a/app/jobs/domain/fa/job/favs_job.rb b/app/jobs/domain/fa/job/favs_job.rb index 98f0b2e1..4c4e9234 100644 --- a/app/jobs/domain/fa/job/favs_job.rb +++ b/app/jobs/domain/fa/job/favs_job.rb @@ -98,6 +98,14 @@ class Domain::Fa::Job::FavsJob < Domain::Fa::Job::Base unique_by: %i[user_id post_id], ) end + + # Use reset_counters to update the counter cache after using upsert_all + Domain::User.reset_counters(user.id, :user_post_favs) + logger.info( + format_tags( + make_tag("reset user_post_favs counter cache for user", user.id), + ), + ) end user.scanned_favs_at = Time.zone.now diff --git a/app/jobs/domain/fa/job/user_incremental_job.rb b/app/jobs/domain/fa/job/user_incremental_job.rb index 234a981a..f13108ea 100644 --- a/app/jobs/domain/fa/job/user_incremental_job.rb +++ b/app/jobs/domain/fa/job/user_incremental_job.rb @@ -77,6 +77,14 @@ module Domain::Fa::Job { user_id: user.id, post_id: post_id } end, ) + + # Use reset_counters to update the counter cache after using insert_all + Domain::User.reset_counters(user.id, :user_post_favs) + logger.info( + format_tags( + make_tag("reset user_post_favs counter cache for user", user.id), + ), + ) end if missing_fav_post_ids.include? recent_fav_post_ids.last @@ -125,6 +133,25 @@ module Domain::Fa::Job { from_id: watcher_id, to_id: user.id } end, ) + + # Use reset_counters to update follows_to for the current user + Domain::User.reset_counters(user.id, :user_user_follows_to) + logger.info( + format_tags( + make_tag( + "reset user_user_follows_to counter cache for user", + user.id, + ), + ), + ) + + # Update follows_from counts for watcher users + missing_watcher_ids.each_slice(100) do |batch_ids| + batch_ids.each do |watcher_id| + Domain::User.reset_counters(watcher_id, :user_user_follows_from) + end + end + logger.info("added #{num_missing.to_s.bold} new watchers") end @@ -140,22 +167,45 @@ module Domain::Fa::Job find_or_create_users_by_recent_users(recent_watched) raise("invariant") unless recent_watched_users.size == recent_watched.size recent_watched_user_ids = recent_watched_users.map { |m| T.must(m.id) } - known_watched_users = - user.followed_users.where(id: recent_watched_user_ids).to_a - missing_watched_users = recent_watched_users - known_watched_users + known_watched_user_ids = + user.followed_users.where(id: recent_watched_user_ids).pluck(:id) + missing_watched_users = + recent_watched_users.reject do |u| + known_watched_user_ids.include?(u.id) + end + missing_watched_user_ids = missing_watched_users.map { |m| T.must(m.id) } - if missing_watched_users.empty? + if missing_watched_user_ids.empty? logger.info("no new users watched") user.scanned_follows_at = Time.current return end - num_missing = missing_watched_users.size + num_missing = missing_watched_user_ids.size Domain::UserUserFollow.insert_all!( - missing_watched_users.map do |watched_user| - { from_id: user.id, to_id: watched_user.id } + missing_watched_user_ids.map do |watched_user_id| + { from_id: user.id, to_id: watched_user_id } end, ) + + # Use reset_counters to update follows_from for the current user + Domain::User.reset_counters(user.id, :user_user_follows_from) + logger.info( + format_tags( + make_tag( + "reset user_user_follows_from counter cache for user", + user.id, + ), + ), + ) + + # Update follows_to counts for users who were followed + missing_watched_user_ids.each_slice(100) do |batch_ids| + batch_ids.each do |watched_user_id| + Domain::User.reset_counters(watched_user_id, :user_user_follows_to) + end + end + logger.info("added #{num_missing.to_s.bold} new users watched") last_watched_user = recent_watched_users.last diff --git a/app/jobs/domain/fa/job/user_page_job.rb b/app/jobs/domain/fa/job/user_page_job.rb index b57e8b78..add74195 100644 --- a/app/jobs/domain/fa/job/user_page_job.rb +++ b/app/jobs/domain/fa/job/user_page_job.rb @@ -154,6 +154,15 @@ class Domain::Fa::Job::UserPageJob < Domain::Fa::Job::Base faved_posts.map(&:id).compact.map { |post_id| { post_id: } }, unique_by: %i[user_id post_id], ) + + # Use reset_counters to update the counter cache after upserting favs + Domain::User.reset_counters(user.id, :user_post_favs) + logger.info( + format_tags( + make_tag("reset user_post_favs counter cache for user", user.id), + ), + ) + user.scanned_favs_at = Time.current end end @@ -182,10 +191,31 @@ class Domain::Fa::Job::UserPageJob < Domain::Fa::Job::Base ) { |user| user.name = recent_user.name } end + watched_user_ids = watched_users.map(&:id).compact + user.user_user_follows_from.upsert_all( - watched_users.map(&:id).compact.map { |user_id| { to_id: user_id } }, + watched_user_ids.map { |user_id| { to_id: user_id } }, unique_by: %i[from_id to_id], ) + + # Use reset_counters to update follows_from for the current user + Domain::User.reset_counters(user.id, :user_user_follows_from) + logger.info( + format_tags( + make_tag( + "reset user_user_follows_from counter cache for user", + user.id, + ), + ), + ) + + # Update follows_to counts for users who were followed + watched_user_ids.each_slice(100) do |batch_ids| + batch_ids.each do |watched_user_id| + Domain::User.reset_counters(watched_user_id, :user_user_follows_to) + end + end + user.scanned_follows_at = Time.current elsif recent_watching.any? # if there are watchers, find the ones we've already recorded. if @@ -239,13 +269,31 @@ class Domain::Fa::Job::UserPageJob < Domain::Fa::Job::Base ) { |user| user.name = recent_user.name } end + watcher_ids = watched_by_users.map(&:id).compact + user.user_user_follows_to.upsert_all( - watched_by_users - .map(&:id) - .compact - .map { |user_id| { from_id: user_id } }, + watcher_ids.map { |user_id| { from_id: user_id } }, unique_by: %i[from_id to_id], ) + + # Use reset_counters to update follows_to for the current user + Domain::User.reset_counters(user.id, :user_user_follows_to) + logger.info( + format_tags( + make_tag( + "reset user_user_follows_to counter cache for user", + user.id, + ), + ), + ) + + # Update follows_from counts for watchers + watcher_ids.each_slice(100) do |batch_ids| + batch_ids.each do |watcher_id| + Domain::User.reset_counters(watcher_id, :user_user_follows_from) + end + end + user.scanned_followed_by_at = Time.current return end diff --git a/app/jobs/domain/post_file_thumbnail_job.rb b/app/jobs/domain/post_file_thumbnail_job.rb index 2e92ba4b..17cb5f70 100644 --- a/app/jobs/domain/post_file_thumbnail_job.rb +++ b/app/jobs/domain/post_file_thumbnail_job.rb @@ -1,6 +1,7 @@ # typed: strict class Domain::PostFileThumbnailJob < Scraper::JobBase queue_as :thumbnails + discard_on Vips::Error sig { override.returns(Symbol) } def self.http_factory_method diff --git a/app/lib/domain/migrate_to_domain.rb b/app/lib/domain/migrate_to_domain.rb index d6ae3a6c..27a426da 100644 --- a/app/lib/domain/migrate_to_domain.rb +++ b/app/lib/domain/migrate_to_domain.rb @@ -931,6 +931,12 @@ class Domain::MigrateToDomain ON CONFLICT (user_id, post_id) DO NOTHING SQL + # Use reset_counters to update the counter cache after inserting favs + Domain::User.reset_counters(user.id, :user_post_favs) + logger.info( + "Reset user_post_favs counter cache for user #{user.name} (ID: #{user.id})", + ) + if user.faved_posts.count != old_user.fav_posts.count logger.error( "favs mismatch for #{user.name}: (#{user.faved_posts.count} != #{old_user.fav_posts.count})", @@ -962,6 +968,22 @@ class Domain::MigrateToDomain ) end + # Use reset_counters to update follows_from for the current user + Domain::User.reset_counters(user.id, :user_user_follows_from) + logger.info( + "Reset user_user_follows_from counter cache for user #{user.name} (ID: #{user.id})", + ) + + # Update follows_to counts for users who were followed + new_user_ids.each_slice(1000) do |batch_ids| + batch_ids.each do |user_id| + Domain::User.reset_counters(user_id, :user_user_follows_to) + end + end + logger.info( + "Reset user_user_follows_to counter cache for #{new_user_ids.size} followed users", + ) + if new_user_ids.size != old_user.follows.count logger.error( "followers mismatch for #{user.name}: (#{user.followed_users.count} != #{old_user.follows.count})", @@ -987,6 +1009,12 @@ class Domain::MigrateToDomain ) end + # Use reset_counters to update the counter cache after upserting favs + Domain::User.reset_counters(new_user.id, :user_post_favs) + logger.info( + "Reset user_post_favs counter cache for user #{new_user.name} (ID: #{new_user.id})", + ) + if new_user.faved_posts.count != old_user.faved_posts.count logger.error( "favs mismatch for #{new_user.name}: (#{new_user.faved_posts.count} != #{old_user.faved_posts.count})", diff --git a/app/models/domain/user.rb b/app/models/domain/user.rb index f23c8112..5086049c 100644 --- a/app/models/domain/user.rb +++ b/app/models/domain/user.rb @@ -17,6 +17,27 @@ class Domain::User < ReduxApplicationRecord :class_has_followed_by_users, :due_at_timestamp_fields + # Counter cache methods with fallbacks + sig { returns(Integer) } + def user_post_creations_count + super || user_post_creations.count + end + + sig { returns(Integer) } + def user_post_favs_count + super || user_post_favs.count + end + + sig { returns(Integer) } + def user_user_follows_from_count + super || user_user_follows_from.count + end + + sig { returns(Integer) } + def user_user_follows_to_count + super || user_user_follows_to.count + end + sig(:final) { returns(T::Boolean) } def has_created_posts? class_has_created_posts.present? diff --git a/app/models/domain/user_post_creation.rb b/app/models/domain/user_post_creation.rb index 8a4c520b..51a862cc 100644 --- a/app/models/domain/user_post_creation.rb +++ b/app/models/domain/user_post_creation.rb @@ -10,5 +10,6 @@ class Domain::UserPostCreation < ReduxApplicationRecord inverse_of: :user_post_creations belongs_to :user, class_name: "::Domain::User", - inverse_of: :user_post_creations + inverse_of: :user_post_creations, + counter_cache: true end diff --git a/app/models/domain/user_post_fav.rb b/app/models/domain/user_post_fav.rb index e96101bc..3c78ebe4 100644 --- a/app/models/domain/user_post_fav.rb +++ b/app/models/domain/user_post_fav.rb @@ -3,7 +3,10 @@ class Domain::UserPostFav < ReduxApplicationRecord self.table_name = "domain_user_post_favs" self.primary_key = %i[user_id post_id] - belongs_to :user, class_name: "Domain::User", inverse_of: :user_post_favs + belongs_to :user, + class_name: "Domain::User", + inverse_of: :user_post_favs, + counter_cache: true belongs_to :post, class_name: "Domain::Post", inverse_of: :user_post_favs scope :for_post_type, diff --git a/app/models/domain/user_user_follow.rb b/app/models/domain/user_user_follow.rb index 7788a710..06d6c135 100644 --- a/app/models/domain/user_user_follow.rb +++ b/app/models/domain/user_user_follow.rb @@ -2,6 +2,10 @@ class Domain::UserUserFollow < ReduxApplicationRecord self.table_name = "domain_user_user_follows" self.primary_key = %i[from_id to_id] - belongs_to :from, class_name: "Domain::User" - belongs_to :to, class_name: "Domain::User" + belongs_to :from, + class_name: "Domain::User", + counter_cache: :user_user_follows_from_count + belongs_to :to, + class_name: "Domain::User", + counter_cache: :user_user_follows_to_count end diff --git a/db/migrate/20250321050628_add_user_counters_to_domain_users.rb b/db/migrate/20250321050628_add_user_counters_to_domain_users.rb new file mode 100644 index 00000000..c10a642d --- /dev/null +++ b/db/migrate/20250321050628_add_user_counters_to_domain_users.rb @@ -0,0 +1,31 @@ +class AddUserCountersToDomainUsers < ActiveRecord::Migration[7.2] + def up + add_column :domain_users, + :user_post_creations_count, + :integer, + default: nil, + null: true + add_column :domain_users, + :user_post_favs_count, + :integer, + default: nil, + null: true + add_column :domain_users, + :user_user_follows_from_count, + :integer, + default: nil, + null: true + add_column :domain_users, + :user_user_follows_to_count, + :integer, + default: nil, + null: true + end + + def down + remove_column :domain_users, :user_post_creations_count + remove_column :domain_users, :user_post_favs_count + remove_column :domain_users, :user_user_follows_from_count + remove_column :domain_users, :user_user_follows_to_count + end +end diff --git a/db/structure.sql b/db/structure.sql index 28a6c88e..716e3ab3 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -3160,7 +3160,11 @@ CREATE TABLE public.domain_users ( type public.domain_user_type NOT NULL, json_attributes jsonb DEFAULT '{}'::jsonb, created_at timestamp(6) without time zone NOT NULL, - updated_at timestamp(6) without time zone NOT NULL + updated_at timestamp(6) without time zone NOT NULL, + user_post_creations_count integer, + user_post_favs_count integer, + user_user_follows_from_count integer, + user_user_follows_to_count integer ); @@ -8910,6 +8914,7 @@ ALTER TABLE ONLY public.domain_twitter_tweets SET search_path TO "$user", public; INSERT INTO "schema_migrations" (version) VALUES +('20250321050628'), ('20250310001341'), ('20250310001005'), ('20250306021628'), diff --git a/lib/tasks/counters.rake b/lib/tasks/counters.rake new file mode 100644 index 00000000..8df5a777 --- /dev/null +++ b/lib/tasks/counters.rake @@ -0,0 +1,79 @@ +namespace :counters do + # Generic counter reset helper that works with any model + def reset_model_counters(model_class, counter_names, batch_size: 1000) + counter_names = Array(counter_names) + model_name = model_class.name.demodulize.underscore + counter_desc = + counter_names + .map { |n| n.to_s.sub("#{model_name}_", "").sub("user_", "") } + .join(" and ") + + total = model_class.count + puts "Resetting #{counter_desc} counter cache for #{total} #{model_name.pluralize}..." + + pb = ProgressBar.create(total: total, format: "%t: %c/%C %B %p%% %a %e") + + model_class + .pluck(:id) + .each_slice(batch_size) do |ids| + model_class.transaction do + ids.each do |id| + model_class.reset_counters(id, *counter_names) + pb.increment + end + end + end + + puts "Done!" + end + + namespace :domain do + namespace :user do + def reset_user_counters(counter_names) + reset_model_counters(Domain::User, counter_names) + end + + desc "Reset all user counter caches" + task all: :environment do + reset_user_counters( + %i[ + user_post_creations + user_post_favs + user_user_follows_from + user_user_follows_to + ], + ) + end + + desc "Reset post-related counter caches only (creations and favs)" + task posts: :environment do + reset_user_counters(%i[user_post_creations user_post_favs]) + end + + desc "Reset follow-related counter caches only (follows_from and follows_to)" + task follows: :environment do + reset_user_counters(%i[user_user_follows_from user_user_follows_to]) + end + + desc "Reset user_post_creations_count counter cache" + task user_post_creations_count: :environment do + reset_user_counters(:user_post_creations) + end + + desc "Reset user_post_favs_count counter cache" + task user_post_favs_count: :environment do + reset_user_counters(:user_post_favs) + end + + desc "Reset user_user_follows_from_count counter cache" + task user_user_follows_from_count: :environment do + reset_user_counters(:user_user_follows_from) + end + + desc "Reset user_user_follows_to_count counter cache" + task user_user_follows_to_count: :environment do + reset_user_counters(:user_user_follows_to) + end + end + end +end diff --git a/rake/blob_file.rake b/rake/blob_file.rake index 1ac8e05d..719aab8b 100644 --- a/rake/blob_file.rake +++ b/rake/blob_file.rake @@ -70,6 +70,9 @@ namespace :blob_file do puts "error saving blob file #{sha256_hex}: #{e}" end end + rescue => e + puts "error migrating blob entry: #{missing_sha256s.map { |sha256| HexUtil.bin2hex(sha256) }}" + raise e end num_migrated end diff --git a/sorbet/rbi/dsl/domain/user.rbi b/sorbet/rbi/dsl/domain/user.rbi index 39517dbe..addc5e76 100644 --- a/sorbet/rbi/dsl/domain/user.rbi +++ b/sorbet/rbi/dsl/domain/user.rbi @@ -1006,6 +1006,18 @@ class Domain::User sig { void } def restore_updated_at!; end + sig { void } + def restore_user_post_creations_count!; end + + sig { void } + def restore_user_post_favs_count!; end + + sig { void } + def restore_user_user_follows_from_count!; end + + sig { void } + def restore_user_user_follows_to_count!; end + sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) } def saved_change_to_created_at; end @@ -1048,6 +1060,30 @@ class Domain::User sig { returns(T::Boolean) } def saved_change_to_updated_at?; end + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_post_creations_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_post_creations_count?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_post_favs_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_post_favs_count?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_user_follows_from_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_user_follows_from_count?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_user_follows_to_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_user_follows_to_count?; end + sig { returns(T.untyped) } def type; end @@ -1148,6 +1184,186 @@ class Domain::User sig { void } def updated_at_will_change!; end + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_post_creations_count=(value); end + + sig { returns(T::Boolean) } + def user_post_creations_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_before_last_save; end + + sig { returns(T.untyped) } + def user_post_creations_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_post_creations_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_creations_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_creations_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_creations_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_creations_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_creations_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_was; end + + sig { void } + def user_post_creations_count_will_change!; end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_post_favs_count=(value); end + + sig { returns(T::Boolean) } + def user_post_favs_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_before_last_save; end + + sig { returns(T.untyped) } + def user_post_favs_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_post_favs_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_favs_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_favs_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_favs_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_favs_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_favs_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_was; end + + sig { void } + def user_post_favs_count_will_change!; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_user_follows_from_count=(value); end + + sig { returns(T::Boolean) } + def user_user_follows_from_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_before_last_save; end + + sig { returns(T.untyped) } + def user_user_follows_from_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_user_follows_from_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_from_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_from_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_from_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_from_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_from_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_was; end + + sig { void } + def user_user_follows_from_count_will_change!; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_user_follows_to_count=(value); end + + sig { returns(T::Boolean) } + def user_user_follows_to_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_before_last_save; end + + sig { returns(T.untyped) } + def user_user_follows_to_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_user_follows_to_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_to_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_to_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_to_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_to_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_to_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_was; end + + sig { void } + def user_user_follows_to_count_will_change!; end + sig { returns(T::Boolean) } def will_save_change_to_created_at?; end @@ -1168,6 +1384,18 @@ class Domain::User sig { returns(T::Boolean) } def will_save_change_to_updated_at?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_post_creations_count?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_post_favs_count?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_user_follows_from_count?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_user_follows_to_count?; end end module GeneratedRelationMethods diff --git a/sorbet/rbi/dsl/domain/user/e621_user.rbi b/sorbet/rbi/dsl/domain/user/e621_user.rbi index d690c5fc..4e9dcc0a 100644 --- a/sorbet/rbi/dsl/domain/user/e621_user.rbi +++ b/sorbet/rbi/dsl/domain/user/e621_user.rbi @@ -1341,6 +1341,18 @@ class Domain::User::E621User sig { void } def restore_updated_at!; end + sig { void } + def restore_user_post_creations_count!; end + + sig { void } + def restore_user_post_favs_count!; end + + sig { void } + def restore_user_user_follows_from_count!; end + + sig { void } + def restore_user_user_follows_to_count!; end + sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) } def saved_change_to_created_at; end @@ -1425,6 +1437,30 @@ class Domain::User::E621User sig { returns(T::Boolean) } def saved_change_to_updated_at?; end + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_post_creations_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_post_creations_count?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_post_favs_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_post_favs_count?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_user_follows_from_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_user_follows_from_count?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_user_follows_to_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_user_follows_to_count?; end + sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) } def scanned_favs_at; end @@ -1635,6 +1671,186 @@ class Domain::User::E621User sig { void } def updated_at_will_change!; end + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_post_creations_count=(value); end + + sig { returns(T::Boolean) } + def user_post_creations_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_before_last_save; end + + sig { returns(T.untyped) } + def user_post_creations_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_post_creations_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_creations_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_creations_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_creations_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_creations_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_creations_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_was; end + + sig { void } + def user_post_creations_count_will_change!; end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_post_favs_count=(value); end + + sig { returns(T::Boolean) } + def user_post_favs_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_before_last_save; end + + sig { returns(T.untyped) } + def user_post_favs_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_post_favs_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_favs_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_favs_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_favs_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_favs_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_favs_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_was; end + + sig { void } + def user_post_favs_count_will_change!; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_user_follows_from_count=(value); end + + sig { returns(T::Boolean) } + def user_user_follows_from_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_before_last_save; end + + sig { returns(T.untyped) } + def user_user_follows_from_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_user_follows_from_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_from_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_from_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_from_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_from_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_from_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_was; end + + sig { void } + def user_user_follows_from_count_will_change!; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_user_follows_to_count=(value); end + + sig { returns(T::Boolean) } + def user_user_follows_to_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_before_last_save; end + + sig { returns(T.untyped) } + def user_user_follows_to_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_user_follows_to_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_to_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_to_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_to_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_to_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_to_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_was; end + + sig { void } + def user_user_follows_to_count_will_change!; end + sig { returns(T::Boolean) } def will_save_change_to_created_at?; end @@ -1676,6 +1892,18 @@ class Domain::User::E621User sig { returns(T::Boolean) } def will_save_change_to_updated_at?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_post_creations_count?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_post_favs_count?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_user_follows_from_count?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_user_follows_to_count?; end end module GeneratedRelationMethods diff --git a/sorbet/rbi/dsl/domain/user/fa_user.rbi b/sorbet/rbi/dsl/domain/user/fa_user.rbi index 63f43a5a..b6e80510 100644 --- a/sorbet/rbi/dsl/domain/user/fa_user.rbi +++ b/sorbet/rbi/dsl/domain/user/fa_user.rbi @@ -2156,6 +2156,18 @@ class Domain::User::FaUser sig { void } def restore_url_name!; end + sig { void } + def restore_user_post_creations_count!; end + + sig { void } + def restore_user_post_favs_count!; end + + sig { void } + def restore_user_user_follows_from_count!; end + + sig { void } + def restore_user_user_follows_to_count!; end + sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) } def saved_change_to_account_status; end @@ -2366,6 +2378,30 @@ class Domain::User::FaUser sig { returns(T::Boolean) } def saved_change_to_url_name?; end + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_post_creations_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_post_creations_count?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_post_favs_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_post_favs_count?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_user_follows_from_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_user_follows_from_count?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_user_follows_to_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_user_follows_to_count?; end + sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) } def scanned_favs_at; end @@ -2896,6 +2932,186 @@ class Domain::User::FaUser sig { void } def url_name_will_change!; end + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_post_creations_count=(value); end + + sig { returns(T::Boolean) } + def user_post_creations_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_before_last_save; end + + sig { returns(T.untyped) } + def user_post_creations_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_post_creations_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_creations_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_creations_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_creations_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_creations_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_creations_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_was; end + + sig { void } + def user_post_creations_count_will_change!; end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_post_favs_count=(value); end + + sig { returns(T::Boolean) } + def user_post_favs_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_before_last_save; end + + sig { returns(T.untyped) } + def user_post_favs_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_post_favs_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_favs_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_favs_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_favs_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_favs_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_favs_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_was; end + + sig { void } + def user_post_favs_count_will_change!; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_user_follows_from_count=(value); end + + sig { returns(T::Boolean) } + def user_user_follows_from_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_before_last_save; end + + sig { returns(T.untyped) } + def user_user_follows_from_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_user_follows_from_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_from_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_from_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_from_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_from_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_from_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_was; end + + sig { void } + def user_user_follows_from_count_will_change!; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_user_follows_to_count=(value); end + + sig { returns(T::Boolean) } + def user_user_follows_to_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_before_last_save; end + + sig { returns(T.untyped) } + def user_user_follows_to_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_user_follows_to_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_to_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_to_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_to_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_to_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_to_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_was; end + + sig { void } + def user_user_follows_to_count_will_change!; end + sig { returns(T::Boolean) } def will_save_change_to_account_status?; end @@ -3000,6 +3216,18 @@ class Domain::User::FaUser sig { returns(T::Boolean) } def will_save_change_to_url_name?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_post_creations_count?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_post_favs_count?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_user_follows_from_count?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_user_follows_to_count?; end end module GeneratedRelationMethods diff --git a/sorbet/rbi/dsl/domain/user/inkbunny_user.rbi b/sorbet/rbi/dsl/domain/user/inkbunny_user.rbi index 96bee936..cede7398 100644 --- a/sorbet/rbi/dsl/domain/user/inkbunny_user.rbi +++ b/sorbet/rbi/dsl/domain/user/inkbunny_user.rbi @@ -1298,6 +1298,18 @@ class Domain::User::InkbunnyUser sig { void } def restore_updated_at!; end + sig { void } + def restore_user_post_creations_count!; end + + sig { void } + def restore_user_post_favs_count!; end + + sig { void } + def restore_user_user_follows_from_count!; end + + sig { void } + def restore_user_user_follows_to_count!; end + sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) } def saved_change_to_created_at; end @@ -1382,6 +1394,30 @@ class Domain::User::InkbunnyUser sig { returns(T::Boolean) } def saved_change_to_updated_at?; end + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_post_creations_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_post_creations_count?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_post_favs_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_post_favs_count?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_user_follows_from_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_user_follows_from_count?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_user_user_follows_to_count; end + + sig { returns(T::Boolean) } + def saved_change_to_user_user_follows_to_count?; end + sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) } def scanned_gallery_at; end @@ -1627,6 +1663,186 @@ class Domain::User::InkbunnyUser sig { void } def updated_at_will_change!; end + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_post_creations_count=(value); end + + sig { returns(T::Boolean) } + def user_post_creations_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_before_last_save; end + + sig { returns(T.untyped) } + def user_post_creations_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_post_creations_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_creations_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_creations_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_creations_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_creations_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_creations_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_post_creations_count_was; end + + sig { void } + def user_post_creations_count_will_change!; end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_post_favs_count=(value); end + + sig { returns(T::Boolean) } + def user_post_favs_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_before_last_save; end + + sig { returns(T.untyped) } + def user_post_favs_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_post_favs_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_favs_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_favs_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_favs_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_post_favs_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_post_favs_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_post_favs_count_was; end + + sig { void } + def user_post_favs_count_will_change!; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_user_follows_from_count=(value); end + + sig { returns(T::Boolean) } + def user_user_follows_from_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_before_last_save; end + + sig { returns(T.untyped) } + def user_user_follows_from_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_user_follows_from_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_from_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_from_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_from_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_from_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_from_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_from_count_was; end + + sig { void } + def user_user_follows_from_count_will_change!; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def user_user_follows_to_count=(value); end + + sig { returns(T::Boolean) } + def user_user_follows_to_count?; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_before_last_save; end + + sig { returns(T.untyped) } + def user_user_follows_to_count_before_type_cast; end + + sig { returns(T::Boolean) } + def user_user_follows_to_count_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_to_count_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_to_count_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_to_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def user_user_follows_to_count_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def user_user_follows_to_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def user_user_follows_to_count_was; end + + sig { void } + def user_user_follows_to_count_will_change!; end + sig { returns(T::Boolean) } def will_save_change_to_created_at?; end @@ -1668,6 +1884,18 @@ class Domain::User::InkbunnyUser sig { returns(T::Boolean) } def will_save_change_to_updated_at?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_post_creations_count?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_post_favs_count?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_user_follows_from_count?; end + + sig { returns(T::Boolean) } + def will_save_change_to_user_user_follows_to_count?; end end module GeneratedRelationMethods diff --git a/sorbet/rbi/dsl/scraper/generic_http_client_config.rbi b/sorbet/rbi/dsl/scraper/generic_http_client_config.rbi new file mode 100644 index 00000000..9fa769c5 --- /dev/null +++ b/sorbet/rbi/dsl/scraper/generic_http_client_config.rbi @@ -0,0 +1,16 @@ +# typed: true + +# DO NOT EDIT MANUALLY +# This is an autogenerated file for dynamic methods in `Scraper::GenericHttpClientConfig`. +# Please instead update this file by running `bin/tapioca dsl Scraper::GenericHttpClientConfig`. + + +class Scraper::GenericHttpClientConfig + sig { returns(ColorLogger) } + def logger; end + + class << self + sig { returns(ColorLogger) } + def logger; end + end +end diff --git a/spec/jobs/domain/fa/job/user_page_job_spec.rb b/spec/jobs/domain/fa/job/user_page_job_spec.rb index f0466464..b90d6cda 100644 --- a/spec/jobs/domain/fa/job/user_page_job_spec.rb +++ b/spec/jobs/domain/fa/job/user_page_job_spec.rb @@ -826,12 +826,12 @@ describe Domain::Fa::Job::UserPageJob do end end - context "user not found" do + shared_examples "user not found" do |status_code| let(:client_mock_config) do [ { uri: "https://www.furaffinity.net/user/onefatpokemon/", - status_code: 200, + status_code:, content_type: "text/html", contents: SpecUtil.read_fixture_file( @@ -855,4 +855,12 @@ describe Domain::Fa::Job::UserPageJob do expect(user.state).to eq("error") end end + + context "user not found with 200 status code" do + include_examples "user not found", 200 + end + + context "user not found with 400 status code" do + include_examples "user not found", 400 + end end diff --git a/spec/models/domain/user_counter_cache_spec.rb b/spec/models/domain/user_counter_cache_spec.rb new file mode 100644 index 00000000..8d3bb0ed --- /dev/null +++ b/spec/models/domain/user_counter_cache_spec.rb @@ -0,0 +1,145 @@ +# typed: false +require "rails_helper" + +RSpec.describe "Domain::User counter caches", type: :model do + let(:user) { create(:domain_user_fa_user) } + let(:post) { create(:domain_post_fa_post) } + + describe "user_post_creations_count" do + it "increments when a post creation is added" do + expect { + Domain::UserPostCreation.create!(user: user, post: post) + }.to change { user.reload.user_post_creations_count }.from(0).to(1) + end + + it "decrements when a post creation is removed" do + creation = Domain::UserPostCreation.create!(user: user, post: post) + expect { creation.destroy }.to change { + user.reload.user_post_creations_count + }.from(1).to(0) + end + + it "provides direct access to the counts" do + posts = create_list(:domain_post_fa_post, 3) + posts.each { |p| Domain::UserPostCreation.create!(user: user, post: p) } + + # Counter in database should be updated automatically + expect(user.reload.user_post_creations_count).to eq(3) + + # Calling the method should use the cached value + expect(user.user_post_creations_count).to eq(3) + end + + it "resets to the correct count" do + posts = create_list(:domain_post_fa_post, 3) + posts.each { |p| Domain::UserPostCreation.create!(user: user, post: p) } + + # Manually set to wrong value + user.update_column(:user_post_creations_count, 0) + expect(user.read_attribute(:user_post_creations_count)).to eq(0) + + # Reset counter + Domain::User.reset_counters(user.id, :user_post_creations) + expect(user.reload.user_post_creations_count).to eq(3) + end + end + + describe "user_post_favs_count" do + it "increments when a post fav is added" do + expect { Domain::UserPostFav.create!(user: user, post: post) }.to change { + user.reload.user_post_favs_count + }.from(0).to(1) + end + + it "decrements when a post fav is removed" do + fav = Domain::UserPostFav.create!(user: user, post: post) + expect { fav.destroy }.to change { + user.reload.user_post_favs_count + }.from(1).to(0) + end + + it "provides direct access to the counts" do + posts = create_list(:domain_post_fa_post, 3) + posts.each { |p| Domain::UserPostFav.create!(user: user, post: p) } + + # Counter in database should be updated automatically + expect(user.reload.user_post_favs_count).to eq(3) + + # Calling the method should use the cached value + expect(user.user_post_favs_count).to eq(3) + end + end + + describe "user_user_follows_from_count" do + let(:other_user) { create(:domain_user_fa_user) } + + it "increments when a follow is added" do + expect { + Domain::UserUserFollow.create!(from: user, to: other_user) + }.to change { user.reload.user_user_follows_from_count }.from(0).to(1) + end + + it "decrements when a follow is removed" do + follow = Domain::UserUserFollow.create!(from: user, to: other_user) + expect { follow.destroy }.to change { + user.reload.user_user_follows_from_count + }.from(1).to(0) + end + + it "provides direct access to the counts" do + users = create_list(:domain_user_fa_user, 3) + users.each { |u| Domain::UserUserFollow.create!(from: user, to: u) } + + # Counter in database should be updated automatically + expect(user.reload.user_user_follows_from_count).to eq(3) + + # Calling the method should use the cached value + expect(user.user_user_follows_from_count).to eq(3) + end + end + + describe "user_user_follows_to_count" do + let(:other_user) { create(:domain_user_fa_user) } + + it "increments when a follow is added" do + expect { + Domain::UserUserFollow.create!(from: other_user, to: user) + }.to change { user.reload.user_user_follows_to_count }.from(0).to(1) + end + + it "decrements when a follow is removed" do + follow = Domain::UserUserFollow.create!(from: other_user, to: user) + expect { follow.destroy }.to change { + user.reload.user_user_follows_to_count + }.from(1).to(0) + end + + it "provides direct access to the counts" do + users = create_list(:domain_user_fa_user, 3) + users.each { |u| Domain::UserUserFollow.create!(from: u, to: user) } + + # Counter in database should be updated automatically + expect(user.reload.user_user_follows_to_count).to eq(3) + + # Calling the method should use the cached value + expect(user.user_user_follows_to_count).to eq(3) + end + end + + describe "counter cache consistency" do + it "maintains separate counts for creations and favs" do + # Create 2 posts and fav 3 posts + created_posts = create_list(:domain_post_fa_post, 2) + faved_posts = create_list(:domain_post_fa_post, 3) + + created_posts.each do |p| + Domain::UserPostCreation.create!(user: user, post: p) + end + faved_posts.each { |p| Domain::UserPostFav.create!(user: user, post: p) } + + user.reload + expect(user.user_post_creations_count).to eq(2) + expect(user.user_post_favs_count).to eq(3) + end + end +end