2009-05-10 2 views
1

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

  1. creator_id = ID der erste Benutzer die Sitzung angemeldet wurde, in setzen individuelle und
  2. updater_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 
+0

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. –

+0

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

+0

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. –

Antwort

0

Ich denke, das Problem, das Sie versuchen, die Wiederverwendung der Sitzung zu lösen, ist eine seltsame. Es ist viel besser, IPs eindeutigen Logins zuzuordnen, indem Sie Ihre Logs verarbeiten, um Sockenpuppen oder Benutzer mit mehreren Accounts zu erkennen. Es gibt zuverlässigere und einfachere Wege, um Ihr Problem zu lösen.

Verwandte Themen