FOLLOWER_IDS_CACHE_KEY = "FOLLOWER_IDS".freeze card_accessor :followers event :cache_expired_for_type_change, :store, on: :update, changed: %i[type_id name] do act_card&.schedule_preference_expiration # FIXME: expire (also?) after save Card.follow_caches_expired end def schedule_preference_expiration @expire_preferences_scheduled = true end def expire_preferences? @expire_preferences_scheduled end event :expire_preferences_cache, :finalize, when: :expire_preferences? do Card::Rule.clear_preference_cache end # follow cache methods on Card class module ClassMethods def follow_caches_expired Card.clear_follower_ids_cache Card::Rule.clear_preference_cache end def follower_ids_cache Card.cache.read(FOLLOWER_IDS_CACHE_KEY) || {} end def write_follower_ids_cache hash Card.cache.write FOLLOWER_IDS_CACHE_KEY, hash end def clear_follower_ids_cache Card.cache.write FOLLOWER_IDS_CACHE_KEY, nil end end def write_follower_ids_cache user_ids hash = Card.follower_ids_cache hash[id] = user_ids Card.write_follower_ids_cache hash end def read_follower_ids_cache Card.follower_ids_cache[id] end def follower_names followers.map(&:name) end def followers follower_ids.map do |id| Card.fetch(id) end end def follower_ids @follower_ids = read_follower_ids_cache || begin result = direct_follower_ids + indirect_follower_ids write_follower_ids_cache result result end end def followers_count follower_ids.size end def indirect_follower_ids result = ::Set.new left_card = left while left_card result += left_card.direct_follower_ids if left_card.followed_field? self left_card = left_card.left end result end # all users (cards) that "directly" follow this card # "direct" means there is a follow rule that applies explicitly to this card. # one can also "indirectly" follow cards by following parent cards or other # cards that nest this one. def direct_followers direct_follower_ids.map do |id| Card.fetch(id) end end def direct_follower_ids &block ids = ::Set.new set_names.each do |set_name| direct_follower_ids_for_set setcard_from_name(set_name), ids, &block end ids end def setcard_from_name set_name Card.fetch set_name, new: { type_id: SetID } end def direct_follower_ids_for_set set_card, ids set_card.all_user_ids_with_rule_for(:follow).each do |user_id| next if ids.include?(user_id) || !(option = follow_rule_option user_id) yield user_id, set_card, option if block_given? ids << user_id end end def each_direct_follower_id_with_reason direct_follower_ids do |user_id, set_card, follow_option| reason = follow_option.gsub(/[\[\]]/, "") yield user_id, set_card: set_card, option: reason end end