User#followed_articles->Article.cached_tagged_with (#13174)
* User#followed_articles->Article.cached_tagged_with This should improve performance on StoriesController#index * Make cached_* work like their tagged_with cousins * Swap to `cached_tagged_with_any` This was using `tagged_with(tags, any: true)` before so this commit swaps to using a scope that aligns with that method. * Allow for searching by Tag models * do the thing * abracadabra run the build * Add comment explaining Postgres regex anchors
This commit is contained in:
parent
18ce0c635c
commit
9533c77d7f
3 changed files with 115 additions and 2 deletions
|
|
@ -146,7 +146,37 @@ class Article < ApplicationRecord
|
|||
.tagged_with(tag_name)
|
||||
}
|
||||
|
||||
scope :cached_tagged_with, ->(tag) { where("cached_tag_list ~* ?", "^#{tag},| #{tag},|, #{tag}$|^#{tag}$") }
|
||||
scope :cached_tagged_with, lambda { |tag|
|
||||
case tag
|
||||
when String
|
||||
# In Postgres regexes, the [[:<:]] and [[:>:]] are equivalent to "start of
|
||||
# word" and "end of word", respectively. They're similar to `\b` in Perl-
|
||||
# compatible regexes (PCRE), but that matches at either end of a word.
|
||||
# They're more comparable to how vim's `\<` and `\>` work.
|
||||
where("cached_tag_list ~ ?", "[[:<:]]#{tag}[[:>:]]")
|
||||
when Array
|
||||
tag.reduce(self) { |acc, elem| acc.cached_tagged_with(elem) }
|
||||
when Tag
|
||||
cached_tagged_with(tag.name)
|
||||
else
|
||||
raise TypeError, "Cannot search tags for: #{tag.inspect}"
|
||||
end
|
||||
}
|
||||
|
||||
scope :cached_tagged_with_any, lambda { |tags|
|
||||
case tags
|
||||
when String
|
||||
cached_tagged_with(tags)
|
||||
when Array
|
||||
tags
|
||||
.map { |tag| cached_tagged_with(tag) }
|
||||
.reduce { |acc, elem| acc.or(elem) }
|
||||
when Tag
|
||||
cached_tagged_with(tag.name)
|
||||
else
|
||||
raise TypeError, "Cannot search tags for: #{tag.inspect}"
|
||||
end
|
||||
}
|
||||
|
||||
scope :cached_tagged_by_approval_with, ->(tag) { cached_tagged_with(tag).where(approved: true) }
|
||||
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ class User < ApplicationRecord
|
|||
|
||||
def followed_articles
|
||||
Article
|
||||
.tagged_with(cached_followed_tag_names, any: true).unscope(:select)
|
||||
.cached_tagged_with_any(cached_followed_tag_names).unscope(:select)
|
||||
.union(Article.where(user_id: cached_following_users_ids))
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -762,6 +762,89 @@ RSpec.describe Article, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
describe ".cached_tagged_with" do
|
||||
it "can search for a single tag" do
|
||||
included = create(:article, tags: "includeme")
|
||||
excluded = create(:article, tags: "lol, nope")
|
||||
|
||||
articles = described_class.cached_tagged_with("includeme")
|
||||
|
||||
expect(articles).to include included
|
||||
expect(articles).not_to include excluded
|
||||
expect(articles.to_a).to eq described_class.tagged_with("includeme").to_a
|
||||
end
|
||||
|
||||
it "can search among multiple tags" do
|
||||
included = [
|
||||
create(:article, tags: "omg, wtf"),
|
||||
create(:article, tags: "omg, lol"),
|
||||
]
|
||||
excluded = create(:article, tags: "nope, excluded")
|
||||
|
||||
articles = described_class.cached_tagged_with("omg")
|
||||
|
||||
expect(articles).to include(*included)
|
||||
expect(articles).not_to include excluded
|
||||
expect(articles.to_a).to include(*described_class.tagged_with("omg").to_a)
|
||||
end
|
||||
|
||||
it "can search for multiple tags" do
|
||||
included = create(:article, tags: "includeme, please, lol")
|
||||
excluded_partial_match = create(:article, tags: "excluded, please")
|
||||
excluded_no_match = create(:article, tags: "excluded, omg")
|
||||
|
||||
articles = described_class.cached_tagged_with(%w[includeme please])
|
||||
|
||||
expect(articles).to include included
|
||||
expect(articles).not_to include excluded_partial_match
|
||||
expect(articles).not_to include excluded_no_match
|
||||
expect(articles.to_a).to eq described_class.tagged_with(%w[includeme please]).to_a
|
||||
end
|
||||
end
|
||||
|
||||
describe ".cached_tagged_with_any" do
|
||||
it "can search for a single tag" do
|
||||
included = create(:article, tags: "includeme")
|
||||
excluded = create(:article, tags: "lol, nope")
|
||||
|
||||
articles = described_class.cached_tagged_with_any("includeme")
|
||||
|
||||
expect(articles).to include included
|
||||
expect(articles).not_to include excluded
|
||||
expect(articles.to_a).to eq described_class.tagged_with("includeme", any: true).to_a
|
||||
end
|
||||
|
||||
it "can search among multiple tags" do
|
||||
included = [
|
||||
create(:article, tags: "omg, wtf"),
|
||||
create(:article, tags: "omg, lol"),
|
||||
]
|
||||
excluded = create(:article, tags: "nope, excluded")
|
||||
|
||||
articles = described_class.cached_tagged_with_any("omg")
|
||||
expected = described_class.tagged_with("omg", any: true).to_a
|
||||
|
||||
expect(articles).to include(*included)
|
||||
expect(articles).not_to include excluded
|
||||
expect(articles.to_a).to include(*expected)
|
||||
end
|
||||
|
||||
it "can search for multiple tags" do
|
||||
included = create(:article, tags: "includeme, please, lol")
|
||||
included_partial_match = create(:article, tags: "includeme, omg")
|
||||
excluded_no_match = create(:article, tags: "excluded, omg")
|
||||
|
||||
articles = described_class.cached_tagged_with_any(%w[includeme please])
|
||||
expected = described_class.tagged_with(%w[includeme please], any: true).to_a
|
||||
|
||||
expect(articles).to include included
|
||||
expect(articles).to include included_partial_match
|
||||
expect(articles).not_to include excluded_no_match
|
||||
|
||||
expect(articles.to_a).to include(*expected)
|
||||
end
|
||||
end
|
||||
|
||||
context "when callbacks are triggered before save" do
|
||||
it "assigns path on save" do
|
||||
expect(article.path).to eq("/#{article.username}/#{article.slug}")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue