Last active
August 6, 2024 07:00
-
-
Save mhenrixon/e2b86bfe4112bf79f676ca5b86ea7e35 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
module Taggable | |
extend ActiveSupport::Concern | |
TYPE_MATCHER = { string: "varchar", text: "text", integer: "integer", citext: "citext" }.freeze | |
class_methods do | |
def has_tags(tag_name) #rubocop:disable Metrics/BlockLength, Metrics/AbcSize, Metrics/MethodLength, Naming/PredicateName | |
tag_array_type_fetcher = -> { TYPE_MATCHER[columns_hash[tag_name.to_s].type] } | |
scope :"with_any_#{tag_name}", lambda { |tags| | |
where("#{table_name}.#{tag_name} && ARRAY[?]::#{tag_array_type_fetcher.call}[]", | |
parse_tags(tags), | |
) | |
} | |
scope :"with_all_#{tag_name}", lambda { |tags| | |
where("#{table_name}.#{tag_name} @> ARRAY[?]::#{tag_array_type_fetcher.call}[]", | |
parse_tags(tags), | |
) | |
} | |
scope :"without_any_#{tag_name}", lambda { |tags| | |
where.not( | |
"#{table_name}.#{tag_name} && ARRAY[?]::#{tag_array_type_fetcher.call}[]", parse_tags(tags) | |
) | |
} | |
scope :"without_all_#{tag_name}", lambda { |tags| | |
where.not( | |
"#{table_name}.#{tag_name} @> ARRAY[?]::#{tag_array_type_fetcher.call}[]", parse_tags(tags) | |
) | |
} | |
define_method :"#{tag_name}=" do |obj| | |
super(parse_tags(obj)) | |
end | |
define_singleton_method :"all_#{tag_name}" do |_options = {}, &block| | |
subquery_scope = unscoped.select("unnest(#{table_name}.#{tag_name}) as tag").distinct | |
subquery_scope = subquery_scope.instance_eval(&block) if block | |
unscope(where: :type).from(subquery_scope).pluck(:tag) | |
end | |
define_singleton_method :"#{tag_name}_cloud" do |_options = {}, &block| | |
subquery_scope = unscoped.select("unnest(#{table_name}.#{tag_name}) as tag") | |
subquery_scope = subquery_scope.instance_eval(&block) if block | |
unscope(where: :type).from(subquery_scope).group(:tag).order(:tag).count(:tag) | |
end | |
define_singleton_method :parse_tags do |obj| | |
case obj | |
when String | |
obj.split(/\s*,\s*/).compact_blank.uniq | |
when Array | |
obj.flatten.map(&:to_s).compact_blank.uniq | |
else | |
obj | |
end | |
end | |
class_eval do | |
define_method :parse_tags do |obj| | |
self.class.parse_tags(obj) | |
end | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
# spec/concerns/taggable_spec.rb | |
require "rails_helper" | |
RSpec.describe Taggable do | |
let(:author) { create(:super_admin) } | |
let(:article) { create(:article, author:) } | |
describe "scopes" do | |
before do | |
create(:article, author:, tags: %w[ruby rails]) | |
create(:article, author:, tags: %w[ruby javascript]) | |
create(:article, author:, tags: %w[python django]) | |
end | |
it "defines with_any_tags scope" do | |
expect(Article.with_any_tags(%w[ruby python]).count).to eq(3) | |
end | |
it "defines with_all_tags scope" do | |
expect(Article.with_all_tags(%w[ruby rails]).count).to eq(1) | |
end | |
it "defines without_any_tags scope" do | |
expect(Article.without_any_tags(["ruby"]).count).to eq(1) | |
end | |
it "defines without_all_tags scope" do | |
expect(Article.without_all_tags(%w[ruby rails]).count).to eq(2) | |
end | |
end | |
describe "#tags=" do | |
it "parses string input" do | |
article.tags = "ruby, rails, web" | |
expect(article.tags).to eq(%w[ruby rails web]) | |
end | |
it "handles array input" do | |
article.tags = %w[ruby rails web] | |
expect(article.tags).to eq(%w[ruby rails web]) | |
end | |
it "removes duplicates" do | |
article.tags = "ruby, rails, ruby, web" | |
expect(article.tags).to eq(%w[ruby rails web]) | |
end | |
it "removes blank entries" do | |
article.tags = "ruby, , rails, ,web" | |
expect(article.tags).to eq(%w[ruby rails web]) | |
end | |
end | |
describe ".all_tags" do | |
before do | |
create(:article, author:, tags: %w[ruby rails]) | |
create(:article, author:, tags: %w[ruby javascript]) | |
create(:article, author:, tags: %w[python django]) | |
end | |
it "returns all unique tags" do | |
expect(Article.all_tags).to match_array(%w[ruby rails javascript python django]) | |
end | |
end | |
describe ".tags_cloud" do | |
before do | |
create(:article, author:, tags: %w[ruby rails]) | |
create(:article, author:, tags: %w[ruby javascript]) | |
create(:article, author:, tags: %w[python django]) | |
end | |
it "returns tag counts" do | |
expected_cloud = { | |
"ruby" => 2, | |
"rails" => 1, | |
"javascript" => 1, | |
"python" => 1, | |
"django" => 1, | |
} | |
expect(Article.tags_cloud).to eq(expected_cloud) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment