Как я могу сделать этот запрос, чтобы обнаружить несколько учетных записей более эффективно и хорошо играть w / has_many? - PullRequest
1 голос
/ 10 мая 2009

В моей модели «Пользователь» я хочу найти, есть ли у пользователя несколько учетных записей (иначе «multis»).

У пользователей есть сессии; сеансы настроены для установки

  1. creator_id = ID первого пользователя, в котором сеанс был зарегистрирован как, и
  2. updater_id = ID последнего пользователя, для которого сеанс был зарегистрирован как

(Оба столбца проиндексированы.)

Если при обнаружении сеанса я обнаруживаю, что они различаются, я сохраняю сеанс для потомков (и этот запрос), поскольку это означает, что пользователь вошел в систему как один, а затем вошел как другой - иначе они ' повторно мульти. (Чтобы поймать рекурсивный базовый случай, creator_id затем сбрасывается в текущем сеансе.)

Вот код, который делает это:

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

Следующий запрос работает.

Тем не менее, это уродливо, не очень эффективно и не очень хорошо работает с методами ассоциации Rails. Имейте в виду, что сессии - это очень большая и интенсивно используемая таблица, и пользователей почти столько же.

Я бы хотел изменить это на ассоциацию has_many (чтобы все ассоциативное волшебство работало); возможно :through => ассоциация :multi_sessions. Он должен охватывать оба направления множественности, а не одно, как нынешняя ассоциация.

Как это можно улучшить?

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

1 Ответ

0 голосов
/ 27 февраля 2011

Я думаю, что проблема, которую вы пытаетесь решить с помощью повторного использования сессии, странная. Вы намного лучше сопоставляете IP-адреса с уникальными именами входа, обрабатывая свои журналы для обнаружения марионеток или пользователей с несколькими учетными записями. Есть более надежные и простые способы решения вашей проблемы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...