In meinem Benutzermodell möchte ich suchen, ob ein Benutzer mehrere Konten hat (auch bekannt als "Multis").Wie kann ich diese Abfrage machen, um mehrere Konten effizienter zu finden und gut mit has_many spielen?
Benutzer haben Sitzungen; Sitzungen werden
creator_id
= ID der erste Benutzer die Sitzung angemeldet wurde, in setzen individuelle undupdater_id
= ID des letzten Benutzers wurde die Sitzung als
(Beide Säulen angemeldet sind indexiert.) Wenn ich feststelle, dass die beiden beim Speichern einer Sitzung unterschiedlich sind, speichere ich die Sitzung für die Nachwelt (und diese Abfrage), weil der Benutzer sich als einer angemeldet und dann als der andere eingeloggt hat - aka sie sind ein Multi. (Um die rekursive Basisfall zu fangen, wird die creator_id
dann zurückgesetzt auf der aktuellen Sitzung.)
Hier ist der Code, der es tut:
class Session < ActiveRecord::SessionStore::Session
attr_accessor :skip_setters
before_save :set_ip
before_save :set_user
def set_user
return true if self.skip_setters
# First user on this session
self.creator_id ||= self.data[:user_id]
# Last user on this session
self.updater_id = self.data[:user_id] if self.data[:user_id]
if self.creator_id and self.updater_id and
self.creator_id != self.updater_id
logger.error "MULTI LOGIN: User #{self.creator.login} and \
#{self.updater.login} from #{self.ip}"
# Save a copy for later inspection
backup = Session.new {|dup_session|
dup_session.attributes = self.attributes
# overwrite the session_id so we don't conflict with the
# current one & it can't be used to log in
dup_session.session_id = ActiveSupport::SecureRandom.hex(16)
dup_session.skip_setters = true
}
backup.save
# Set this session to be single user again.
# Updater is what the user looks for;
# creator is the one that's there to trigger this.
self.creator_id = self.updater_id
end
end
# etc... e.g. log IP
end
Die folgende Abfrage funktioniert.
Allerdings ist es hässlich, nicht sehr effizient und spielt nicht gut mit den Assoziationsmethoden von Rails. Denken Sie daran, dass Sitzungen eine extrem große und häufig verwendete Tabelle sind und die Benutzer fast genauso viel.
Ich möchte dies in eine has_many-Assoziation ändern (damit alle Assoziationsmagie funktioniert); möglicherweise :through =>
eine :multi_sessions
Assoziation. Es sollte beide Richtungen der Multihood erfassen, nicht nur eine wie die aktuelle Assoziation.
Wie kann dies verbessert werden?
class User < ActiveRecord::Base
has_many :sessions, :foreign_key => 'updater_id'
# This association is only unidirectional; it won't catch the reverse case
# (i.e. someone logged in first as this user and then as the other)
has_many :multi_sessions, :foreign_key => 'updater_id',
:conditions => 'sessions.updater_id != sessions.creator_id',
:class_name => 'Session'
has_many :multi_users, :through => :multi_sessions,
:source => 'creator', :class_name => 'User'
...
# This does catch both, but is pretty ugly :(
def multis
# Version 1
User.find_by_sql "SELECT DISTINCT users.* FROM users \
INNER JOIN sessions \
ON (sessions.updater_id = #{self.id} XOR sessions.creator_id = {self.id}) AND \
(sessions.updater_id = users.id XOR sessions.creator_id = users.id) \
WHERE users.id != #{self.id}"
# Version 2
User.find(sessions.find(:all, :conditions => 'creator_id != updater_id',
:select => 'DISTINCT creator_id, updater_id').map{|x|
[x.creator_id, x.updater_id]}.flatten.uniq - [self.id])
end
end
Warum lässt eine Sitzung die Benutzer-ID im Verlauf ändern? Angenommen, es gibt einen guten Grund dafür (ein Grund, der sich mir gerade entzieht), warum verfolgen Sie nicht einfach, dass eine Sitzung die Benutzer-ID ändert, wenn sie sich ändert? Dies rückwirkend zu verkomplizieren scheint das Leben unnötig zu erschweren - und Ihre Anfrage ist langsam und unzuverlässig. –
Zum Beispiel möchte ich möglicherweise Flash-Nachrichten über eine Anmeldung/Abmeldung erhalten; Dies erfordert eine persistente Sitzung. (Oder alternativ, persistierenden Teil der Daten über zwei "verschiedene" Sitzungen, die wirklich nicht viel Gewinn ist.) Doing es rückwirkend bedeutet, dass ich später auf die Sitzungsdaten gehen kann (z. B. wenn es etwas Missbrauch gab), kennen Sie das relative Timing usw. Dh, Auditing-Zwecke. Plus - Ich * * * verfolgen, dass es ID ändert, wenn es sich ändert; Dafür ist die After_Save-Falle gedacht. :-P – Sai
OK: also machen Sie die 'after_save' Trap Aufzeichnung, dass die Änderung aufgetreten ist, so dass Sie nicht um das DBMS für die Information tappen müssen; notieren Sie, was Sie brauchen, wenn es passiert. Ich nehme dein Wort, dass der anhaltende Blitz korrekt ist - es klingt meistens plausibel, aber es ist nicht mein Fachgebiet. –