To jest rozwiązanie Railsowe, które tworzy samoczynne sprzężenia dla przypadku AND
i proste zapytanie SQL dla przypadku OR
. Rozwiązanie zakłada Model o nazwie TopicTag, a co za tym idzie tabelę o nazwie topic_tags.
Metoda klasy Szukaj spodziewa 2 argumenty tablicę Etykiety i łańcuch zawierający albo „i” albo „lub”
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
Aby uzyskać trochę więcej pokrycia testowego danych została rozszerzona o dodatkowy rekord na szachy. Baza zaszczepiono Następujący kod
[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')}
Następujący kod z badań uzyskanych wyników przedstawiono
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
Testowane na szynach 2.3.8 wykorzystaniem SqlLite
EDIT
Jeśli chcesz użyć, jak to e OR
sprawa również staje się nieco bardziej złożona. Należy również pamiętać, że używanie LIKE z wiodącym "%" może mieć znaczący wpływ na wydajność, jeśli szukana tabela ma niebagatelny rozmiar.
Następująca wersja Model używa LIKE dla obu przypadków.
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
dlaczego nie masz model działalności, w których istnieje wiele do wielu relacji między działaniami i tematy? wtedy każda czynność miałaby identyfikator i mógłbyś poprosić o numer 'topic.activities.include? (" baseball ")' –