lib/tag_columns.rb
require "active_support/concern"
require "active_support/core_ext/string/inflections"
require "tag_columns/version"
module TagColumns
extend ActiveSupport::Concern
module ClassMethods
def tag_columns_sanitize_list(values = [])
return [] if values.nil?
values.select(&:present?).map(&:to_s).uniq.sort
end
def tag_columns(*column_names)
@tag_columns ||= {}
tag_columns_sanitize_list(column_names).each do |column_name|
@tag_columns[column_name] ||= false
end
@tag_columns.each do |column_name, initialized|
next if initialized
column_name = column_name.to_s
method_name = column_name.downcase
define_singleton_method :"unique_#{method_name}" do |conditions = "true"|
unnest = Arel::Nodes::NamedFunction.new("unnest", [arel_table[column_name]])
query = distinct.select(unnest)
.where(conditions)
.where.not(arel_table[column_name].eq(nil))
.where.not(arel_table[column_name].eq("{}"))
connection.execute(query.to_sql).values.flatten.sort
end
define_singleton_method :"#{method_name}_cloud" do |conditions = "true"|
unnest = Arel::Nodes::NamedFunction.new("unnest", [arel_table[column_name]])
query = unscoped.select(unnest.as("tag"))
.where(conditions)
.where.not(arel_table[column_name].eq(nil))
.where.not(arel_table[column_name].eq("{}"))
from(query).group("tag").order("tag").pluck(Arel.sql("tag, count(*) as count"))
end
scope :"with_#{method_name}", -> {
where.not(arel_table[column_name].eq(nil)).where.not(arel_table[column_name].eq("{}"))
}
scope :"without_#{method_name}", -> {
where(arel_table[column_name].eq(nil)).or(where(arel_table[column_name].eq("{}")))
}
scope :"with_any_#{method_name}", ->(*tags) {
column_cast = Arel::Nodes::NamedFunction.new("CAST", [arel_table[column_name].as("text[]")])
value = Arel::Nodes::SqlLiteral.new(sanitize_sql_array(["ARRAY[?]", tag_columns_sanitize_list(tags)]))
value_cast = Arel::Nodes::NamedFunction.new("CAST", [value.as("text[]")])
overlap = Arel::Nodes::InfixOperation.new("&&", column_cast, value_cast)
where overlap
}
scope :"with_all_#{method_name}", ->(*tags) {
column_cast = Arel::Nodes::NamedFunction.new("CAST", [arel_table[column_name].as("text[]")])
value = Arel::Nodes::SqlLiteral.new(sanitize_sql_array(["ARRAY[?]", tag_columns_sanitize_list(tags)]))
value_cast = Arel::Nodes::NamedFunction.new("CAST", [value.as("text[]")])
contains = Arel::Nodes::InfixOperation.new("@>", column_cast, value_cast)
where contains
}
scope :"without_any_#{method_name}", ->(*tags) {
column_cast = Arel::Nodes::NamedFunction.new("CAST", [arel_table[column_name].as("text[]")])
value = Arel::Nodes::SqlLiteral.new(sanitize_sql_array(["ARRAY[?]", tag_columns_sanitize_list(tags)]))
value_cast = Arel::Nodes::NamedFunction.new("CAST", [value.as("text[]")])
overlap = Arel::Nodes::InfixOperation.new("&&", column_cast, value_cast)
where.not overlap
}
scope :"without_all_#{method_name}", ->(*tags) {
column_cast = Arel::Nodes::NamedFunction.new("CAST", [arel_table[column_name].as("text[]")])
value = Arel::Nodes::SqlLiteral.new(sanitize_sql_array(["ARRAY[?]", tag_columns_sanitize_list(tags)]))
value_cast = Arel::Nodes::NamedFunction.new("CAST", [value.as("text[]")])
contains = Arel::Nodes::InfixOperation.new("@>", column_cast, value_cast)
where.not contains
}
before_validation -> { self[column_name] = self.class.tag_columns_sanitize_list(self[column_name]) }
define_method :"has_any_#{method_name}?" do |*values|
values = self.class.tag_columns_sanitize_list(values)
existing = self.class.tag_columns_sanitize_list(self[column_name])
(values & existing).present?
end
define_method :"has_all_#{method_name}?" do |*values|
values = self.class.tag_columns_sanitize_list(values)
existing = self.class.tag_columns_sanitize_list(self[column_name])
(values & existing).size == values.size
end
alias_method :"has_#{method_name.singularize}?", :"has_all_#{method_name}?"
@tag_columns[column_name] = true
end
end
end
end