Esta es una solución de Rails que crea combinaciones autorreferenciales para el caso AND
y un simple SQL para el caso OR
. La solución supone un Modelo llamado TopicTag y, en consecuencia, una tabla llamada topic_tags.
El método de la clase Búsqueda espera 2 argumentos una matriz de etiquetas y una cadena que contiene ya sea "y" u "o"
class TopicTag < ActiveRecord::Base
def self.search(tags, andor)
# Ensure tags are unique or you will get duplicate table names in the SQL
tags.uniq!
if andor.downcase == "and"
first = true
sql = ""
tags.each do |tag|
if first
sql = "SELECT DISTINCT topic_tags.topic_id FROM topic_tags "
first = false
else
sql += " JOIN topic_tags as tag_#{tag} ON tag_#{tag}.topic_id = \
topic_tags.topic_id AND tag_#{tag}.tag = '#{tag}'"
end
end
sql += " WHERE topic_tags.tag = '#{tags[0]}'"
TopicTag.find_by_sql(sql)
else
TopicTag.find(:all, :select => 'DISTINCT topic_id',
:conditions => { :tag => tags})
end
end
end
Con el fin de conseguir un poco más cobertura de la prueba los datos se amplió para incluir un extra registro de ajedrez. La base de datos se sembró con el código siguiente
[1,2].each {|i| TopicTag.create(:topic_id => i, :tag => 'football')}
[1,3].each {|i| TopicTag.create(:topic_id => i, :tag => 'cricket')}
[2,3,4].each {|i| TopicTag.create(:topic_id => i, :tag => 'basketball')}
[4,5].each {|i| TopicTag.create(:topic_id => i, :tag => 'chess')}
El siguiente código de prueba produjo los resultados mostrados
tests = [
%w[football cricket],
%w[chess],
%w[chess cricket basketball]
]
tests.each do |test|
%w[and or].each do |op|
puts test.join(" #{op} ") + " = " +
(TopicTag.search(test, op).map(&:topic_id)).join(', ')
end
end
football and cricket = 1
football or cricket = 1, 2, 3
chess = 4, 5
chess = 4, 5
chess and cricket and basketball =
chess or cricket or basketball = 1, 2, 3, 4, 5
Probado en Rails 2.3.8 utilizando SqlLite
EDITAR
Si desea utilizar como entonces th La caja e OR
también se vuelve un poco más compleja. También debe tener en cuenta que usar LIKE con un '%' inicial podría tener un impacto significativo en el rendimiento si la tabla que está buscando es de un tamaño no trivial.
La siguiente versión del modelo usa LIKE para ambos casos.
class TopicTag < ActiveRecord::Base
def self.search(tags, andor)
tags.uniq!
if andor.downcase == "and"
first = true
first_name = ""
sql = ""
tags.each do |tag|
if first
sql = "SELECT DISTINCT topic_tags.topic_id FROM topic_tags "
first = false
else
sql += " JOIN topic_tags as tag_#{tag} ON tag_#{tag}.topic_id = \
topic_tags.topic_id AND tag_#{tag}.tag like '%#{tag}%'"
end
end
sql += " WHERE topic_tags.tag like '%#{tags[0]}%'"
TopicTag.find_by_sql(sql)
else
first = true
tag_sql = ""
tags.each do |tag|
if first
tag_sql = " tag like '%#{tag}%'"
first = false
else
tag_sql += " OR tag like '%#{tag}%'"
end
end
TopicTag.find(:all, :select => 'DISTINCT topic_id',
:conditions => tag_sql)
end
end
end
tests = [
%w[football cricket],
%w[chess],
%w[chess cricket basketball],
%w[chess ll],
%w[ll]
]
tests.each do |test|
%w[and or].each do |op|
result = TopicTag.search(test, op).map(&:topic_id)
puts (test.size == 1 ? "#{test}(#{op})" : test.join(" #{op} ")) +
" = " + result.join(', ')
end
end
football and cricket = 1
football or cricket = 1, 2, 3
chess(and) = 4, 5
chess(or) = 4, 5
chess and cricket and basketball =
chess or cricket or basketball = 1, 2, 3, 4, 5
chess and ll = 4
chess or ll = 1, 2, 3, 4, 5
ll(and) = 1, 2, 3, 4
ll(or) = 1, 2, 3, 4
¿por qué no tener un modelo de actividad donde hay una relación muchos a muchos entre las actividades y temas? entonces cada actividad tendría una identificación y usted podría pedir 'topic.activities.include? (" baseball ")' –