search: make order:random truly random; add random:N metatag.
Make the `order:random` metatag truly randomize the search. Add a `random:N` metatag that returns up to N random posts, like what `order:random` did before. `order:random` now returns the entire search in random order. Before it just returned a pageful of pseudorandom posts. This will be more accurate for small searches, but slower for large searches. If `order:random` times out, try `random:N` instead. The `random:N` metatag returns up to N pseudorandom posts. This is faster than `order:random` for large searches, but for small searches, it may return less than N posts, and the randomness may be biased. Some posts may be more likely than others to appear. N must be between 0 and 200. Also, `/posts?tags=touhou&random=1` now redirects to `/posts?tags=touhou+random:N`. Before the `random=1` param acted like a free `order:random` tag; now it redirects to a `random:N` search, so it counts against your tag limit.
This commit is contained in:
@@ -8,9 +8,13 @@ class PostsController < ApplicationController
|
|||||||
respond_with(@post) do |format|
|
respond_with(@post) do |format|
|
||||||
format.html { redirect_to(@post) }
|
format.html { redirect_to(@post) }
|
||||||
end
|
end
|
||||||
|
elsif params[:random].to_s.truthy?
|
||||||
|
post_set = PostSets::Post.new(params[:tags], params[:page], params[:limit], format: request.format.symbol, view: params[:view])
|
||||||
|
query = "#{post_set.normalized_query.to_s} random:#{post_set.per_page}".strip
|
||||||
|
redirect_to posts_path(tags: query, page: params[:page], limit: params[:limit], format: request.format.symbol, view: params[:view])
|
||||||
else
|
else
|
||||||
tag_query = params[:tags] || params.dig(:post, :tags)
|
tag_query = params[:tags] || params.dig(:post, :tags)
|
||||||
@post_set = PostSets::Post.new(tag_query, params[:page], params[:limit], random: params[:random], format: params[:format], view: params[:view])
|
@post_set = PostSets::Post.new(tag_query, params[:page], params[:limit], format: request.format.symbol, view: params[:view])
|
||||||
@posts = authorize @post_set.posts, policy_class: PostPolicy
|
@posts = authorize @post_set.posts, policy_class: PostPolicy
|
||||||
@post_set.log!
|
@post_set.log!
|
||||||
respond_with(@posts) do |format|
|
respond_with(@posts) do |format|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class PostQueryBuilder
|
|||||||
ordpool note comment commentary id rating source status filetype
|
ordpool note comment commentary id rating source status filetype
|
||||||
disapproved parent child search embedded md5 width height mpixels ratio
|
disapproved parent child search embedded md5 width height mpixels ratio
|
||||||
score upvotes downvotes favcount filesize date age order limit tagcount pixiv_id pixiv
|
score upvotes downvotes favcount filesize date age order limit tagcount pixiv_id pixiv
|
||||||
unaliased exif duration
|
unaliased exif duration random
|
||||||
] + COUNT_METATAGS + COUNT_METATAG_SYNONYMS + CATEGORY_COUNT_METATAGS
|
] + COUNT_METATAGS + COUNT_METATAG_SYNONYMS + CATEGORY_COUNT_METATAGS
|
||||||
|
|
||||||
ORDER_METATAGS = %w[
|
ORDER_METATAGS = %w[
|
||||||
@@ -218,6 +218,8 @@ class PostQueryBuilder
|
|||||||
user_subquery_matches(PostVote.active.positive.visible(current_user), value, field: :user)
|
user_subquery_matches(PostVote.active.positive.visible(current_user), value, field: :user)
|
||||||
when "downvoter", "downvote"
|
when "downvoter", "downvote"
|
||||||
user_subquery_matches(PostVote.active.negative.visible(current_user), value, field: :user)
|
user_subquery_matches(PostVote.active.negative.visible(current_user), value, field: :user)
|
||||||
|
when "random"
|
||||||
|
Post.all # handled in the `build` method
|
||||||
when *CATEGORY_COUNT_METATAGS
|
when *CATEGORY_COUNT_METATAGS
|
||||||
short_category = name.delete_suffix("tags")
|
short_category = name.delete_suffix("tags")
|
||||||
category = TagCategory.short_name_mapping[short_category]
|
category = TagCategory.short_name_mapping[short_category]
|
||||||
@@ -513,6 +515,11 @@ class PostQueryBuilder
|
|||||||
relation = search_order(relation, find_metatag(:order))
|
relation = search_order(relation, find_metatag(:order))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if count = find_metatag(:random)
|
||||||
|
count = Integer(count).clamp(0, PostSets::Post::MAX_PER_PAGE)
|
||||||
|
relation = relation.random(count)
|
||||||
|
end
|
||||||
|
|
||||||
relation
|
relation
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -688,6 +695,9 @@ class PostQueryBuilder
|
|||||||
when /(#{TagCategory.short_name_list.join("|")})tags_asc/
|
when /(#{TagCategory.short_name_list.join("|")})tags_asc/
|
||||||
relation = relation.order("posts.tag_count_#{TagCategory.short_name_mapping[$1]} ASC")
|
relation = relation.order("posts.tag_count_#{TagCategory.short_name_mapping[$1]} ASC")
|
||||||
|
|
||||||
|
when "random"
|
||||||
|
relation = relation.order("random()")
|
||||||
|
|
||||||
when "rank"
|
when "rank"
|
||||||
relation = relation.where("posts.score > 0 and posts.created_at >= ?", 2.days.ago)
|
relation = relation.where("posts.score > 0 and posts.created_at >= ?", 2.days.ago)
|
||||||
relation = relation.order(Arel.sql("log(3, posts.score) + (extract(epoch from posts.created_at) - extract(epoch from timestamp '2005-05-24')) / 35000 DESC"))
|
relation = relation.order(Arel.sql("log(3, posts.score) + (extract(epoch from posts.created_at) - extract(epoch from timestamp '2005-05-24')) / 35000 DESC"))
|
||||||
|
|||||||
@@ -7,16 +7,15 @@ module PostSets
|
|||||||
MAX_PER_PAGE = 200
|
MAX_PER_PAGE = 200
|
||||||
MAX_SIDEBAR_TAGS = 25
|
MAX_SIDEBAR_TAGS = 25
|
||||||
|
|
||||||
attr_reader :page, :random, :format, :tag_string, :query, :normalized_query, :view
|
attr_reader :page, :format, :tag_string, :query, :normalized_query, :view
|
||||||
delegate :post_count, to: :normalized_query
|
delegate :post_count, to: :normalized_query
|
||||||
|
|
||||||
def initialize(tags, page = 1, per_page = nil, user: CurrentUser.user, random: false, format: "html", view: "simple")
|
def initialize(tags, page = 1, per_page = nil, user: CurrentUser.user, format: "html", view: "simple")
|
||||||
@query = PostQueryBuilder.new(tags, user, tag_limit: user.tag_query_limit, safe_mode: CurrentUser.safe_mode?, hide_deleted_posts: user.hide_deleted_posts?)
|
@query = PostQueryBuilder.new(tags, user, tag_limit: user.tag_query_limit, safe_mode: CurrentUser.safe_mode?, hide_deleted_posts: user.hide_deleted_posts?)
|
||||||
@normalized_query = query.normalized_query
|
@normalized_query = query.normalized_query
|
||||||
@tag_string = tags
|
@tag_string = tags
|
||||||
@page = page
|
@page = page
|
||||||
@per_page = per_page
|
@per_page = per_page
|
||||||
@random = random.to_s.truthy?
|
|
||||||
@format = format.to_s
|
@format = format.to_s
|
||||||
@view = view.presence || "simple"
|
@view = view.presence || "simple"
|
||||||
end
|
end
|
||||||
@@ -92,25 +91,11 @@ module PostSets
|
|||||||
end
|
end
|
||||||
|
|
||||||
def max_per_page
|
def max_per_page
|
||||||
(format == "sitemap") ? 10_000 : MAX_PER_PAGE
|
(format.to_sym == :sitemap) ? 10_000 : MAX_PER_PAGE
|
||||||
end
|
|
||||||
|
|
||||||
def is_random?
|
|
||||||
random || query.find_metatag(:order) == "random"
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_random_posts
|
|
||||||
::Post.user_tag_match(tag_string).random(per_page)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def posts
|
def posts
|
||||||
@posts ||= begin
|
@posts ||= normalized_query.paginated_posts(page, includes: includes, count: post_count, search_count: !post_count.nil?, limit: per_page, max_limit: max_per_page).load
|
||||||
if is_random?
|
|
||||||
get_random_posts.paginate(page, search_count: false, limit: per_page, max_limit: max_per_page).load
|
|
||||||
else
|
|
||||||
normalized_query.paginated_posts(page, includes: includes, count: post_count, search_count: !post_count.nil?, limit: per_page, max_limit: max_per_page).load
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def hide_from_crawler?
|
def hide_from_crawler?
|
||||||
|
|||||||
@@ -36,7 +36,5 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% unless post_set.is_random? %>
|
<%= numbered_paginator(post_set.posts) %>
|
||||||
<%= numbered_paginator(post_set.posts) %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -309,18 +309,21 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
get posts_path, params: { tags: "order:random" }
|
get posts_path, params: { tags: "order:random" }
|
||||||
assert_response :success
|
assert_response :success
|
||||||
|
|
||||||
get posts_path, params: { random: "1" }
|
get posts_path(random: "1")
|
||||||
assert_response :success
|
assert_redirected_to posts_path(tags: "random:20", format: :html)
|
||||||
|
|
||||||
get posts_path(format: :json), params: { random: "1" }
|
get posts_path(random: "1"), as: :json
|
||||||
assert_response :success
|
assert_redirected_to posts_path(tags: "random:20", format: :json)
|
||||||
|
|
||||||
|
get posts_path(tags: "touhou", random: "true")
|
||||||
|
assert_redirected_to posts_path(tags: "touhou random:20", format: :html)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "render with multiple posts" do
|
should "render with multiple posts" do
|
||||||
@posts = create_list(:post, 2)
|
@posts = create_list(:post, 2)
|
||||||
|
|
||||||
get posts_path, params: { random: "1" }
|
get posts_path(random: "1")
|
||||||
assert_response :success
|
assert_redirected_to posts_path(tags: "random:20", format: :html)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "return all posts for a .json response" do
|
should "return all posts for a .json response" do
|
||||||
|
|||||||
@@ -1099,6 +1099,14 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
|||||||
assert_tag_match([], "exif:DNE")
|
assert_tag_match([], "exif:DNE")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
should "return posts for the random:<N> metatag" do
|
||||||
|
post = create(:post)
|
||||||
|
|
||||||
|
assert_tag_match([], "random:0")
|
||||||
|
assert_tag_match([post], "random:1")
|
||||||
|
assert_tag_match([post], "random:1000")
|
||||||
|
end
|
||||||
|
|
||||||
should "return posts ordered by a particular attribute" do
|
should "return posts ordered by a particular attribute" do
|
||||||
posts = (1..2).map do |n|
|
posts = (1..2).map do |n|
|
||||||
tags = ["tagme", "gentag1 gentag2 artist:arttag char:chartag copy:copytag"]
|
tags = ["tagme", "gentag1 gentag2 artist:arttag char:chartag copy:copytag"]
|
||||||
@@ -1210,6 +1218,12 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
should "return posts for order:random" do
|
||||||
|
post = create(:post)
|
||||||
|
|
||||||
|
assert_tag_match([post], "order:random")
|
||||||
|
end
|
||||||
|
|
||||||
should "return posts for a filesize search" do
|
should "return posts for a filesize search" do
|
||||||
post = create(:post, file_size: 1.megabyte)
|
post = create(:post, file_size: 1.megabyte)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user