Files
danbooru/app/models/saved_search.rb
2019-08-29 00:51:52 -05:00

162 lines
4.1 KiB
Ruby

class SavedSearch < ApplicationRecord
REDIS_EXPIRY = 3600
QUERY_LIMIT = 1000
def self.enabled?
Danbooru.config.redis_url.present?
end
concerning :Redis do
class_methods do
extend Memoist
def redis
::Redis.new(url: Danbooru.config.redis_url)
end
memoize :redis
def post_ids_for(user_id, label: nil)
label = normalize_label(label) if label
queries = queries_for(user_id, label: label)
post_ids = Set.new
update_count = 0
queries.each do |query|
redis_key = "search:#{query}"
if redis.exists(redis_key)
sub_ids = redis.smembers(redis_key).map(&:to_i)
post_ids.merge(sub_ids)
redis.expire(redis_key, REDIS_EXPIRY)
elsif CurrentUser.is_gold? && update_count < 5
SavedSearch.populate(query)
sub_ids = redis.smembers(redis_key).map(&:to_i)
post_ids.merge(sub_ids)
update_count += 1
else
PopulateSavedSearchJob.perform_later(query)
end
end
post_ids.to_a.sort.last(QUERY_LIMIT)
end
end
end
concerning :Labels do
class_methods do
def normalize_label(label)
label.
to_s.
strip.
downcase.
gsub(/[[:space:]]/, "_")
end
def search_labels(user_id, params)
labels = labels_for(user_id)
if params[:label].present?
query = Regexp.escape(params[:label]).gsub("\\*", ".*")
query = ".*#{query}.*" unless query.include?("*")
query = /\A#{query}\z/
labels = labels.grep(query)
end
labels
end
def labels_for(user_id)
SavedSearch.
where(user_id: user_id).
order("label").
pluck(Arel.sql("distinct unnest(labels) as label"))
end
end
def normalize_labels
self.labels = labels.
map {|x| SavedSearch.normalize_label(x)}.
reject {|x| x.blank?}
end
def label_string
labels.join(" ")
end
def label_string=(val)
self.labels = val.to_s.split(/[[:space:]]+/)
end
def labels=(labels)
labels = labels.map { |label| SavedSearch.normalize_label(label) }
super(labels)
end
end
concerning :Search do
class_methods do
def populate(query)
CurrentUser.as_system do
redis_key = "search:#{query}"
return if redis.exists(redis_key)
post_ids = Post.tag_match(query, read_only: Rails.env.production?).limit(QUERY_LIMIT).pluck(:id)
redis.sadd(redis_key, post_ids)
redis.expire(redis_key, REDIS_EXPIRY)
end
end
end
end
concerning :Queries do
class_methods do
def queries_for(user_id, label: nil, options: {})
SavedSearch.
where(user_id: user_id).
labeled(label).
pluck(:query).
map {|x| Tag.normalize_query(x, sort: true)}.
sort.
uniq
end
end
def normalized_query
Tag.normalize_query(query, sort: true)
end
def normalize_query
self.query = Tag.normalize_query(query, sort: false)
end
end
attr_accessor :disable_labels
belongs_to :user
validates :query, presence: true
validate :validate_count
before_create :update_user_on_create
after_destroy :update_user_on_destroy
before_validation :normalize_query
before_validation :normalize_labels
scope :labeled, ->(label) { label.present? ? where("labels @> string_to_array(?, '~~~~')", label) : where("true") }
def validate_count
if user.saved_searches.count + 1 > user.max_saved_searches
self.errors[:user] << "can only have up to #{user.max_saved_searches} " + "saved search".pluralize(user.max_saved_searches)
end
end
def update_user_on_create
if !user.has_saved_searches?
user.update(has_saved_searches: true)
end
end
def update_user_on_destroy
if user.saved_searches.count == 0
user.update(has_saved_searches: false)
end
end
def disable_labels=(value)
CurrentUser.update(disable_categorized_saved_searches: true) if value.to_s.truthy?
end
end