Отношения HABTM и таблица соединений - PullRequest
2 голосов
/ 01 февраля 2010

Я пытаюсь соединить значения двух таблиц соединения, которые у меня есть, и показать результаты на основе условных отношений ... и у меня возникли некоторые проблемы

У меня есть модель пользователей (: имя,: пароль,: электронная почта), модель событий (: имя,: и т. Д.) И модель интересов (: имя)

Я создал около 5 записей в каждой модели.

Затем я создал две таблицы соединения -> UsersInterests и EventsInterests; каждый из которых не содержит первичного ключа и состоит только из user_id / Interest_ID и Event_ID / Interest_id соответственно.

Затем я добавил в файлы модели HABTM Relationship

users => has_and_belongs_to_many :interests
events => has_and_belongs_to_many :interests
interests => has_and_belongs_to_many :users
         has_and_belongs_to_many :events

Теперь я хотел создать контроллер, который бы находил только события, в которых интересы пользователей соответствуют интересам событий

Работая над этим некоторое время, я понял, что мне нужно что-то в области

@Events = Event.User.find([condition])
[condition] = where users.interest == event.interest

или что-то в этом роде ... Я в некотором роде потерян .. Как вы заявляете условие поиска? ... Я знаю, как выполнить внутреннее соединение в SQL, но я ищу элегантный способ Rails сделать это ... какие-нибудь советы, ребята?

1 Ответ

7 голосов
/ 01 февраля 2010

Элегантный рубиновый способ сделать это с помощью именованных областей . Однако, поскольку вы решили использовать отношения has_and_belongs_to_many вместо has_many: через отношения вам нужно будет определить соединение с использованием необработанного SQL, что не очень элегантно. И из-за того, как Rails обрабатывает генерацию SQL, вам придется создать область для использования с одним пользователем и вторую именованную область для использования со многими пользователями.

Class Event < ActiveRecord::Base
  ...

  #find events that share an interest with a single user
  named_scope :shares_interest_with_user, lambda {|user|
    { :joins => "LEFT JOIN events_interests ei ON ei.event_id = events.id " +
         "LEFT JOIN users_intersets ui ON ui.interest_id = ei.interest_id",
      :conditions => ["ui.user_id = ?", user], :group_by => "events.id"
    }

  #find events that share an interest with a list of users
  named_scope :shares_interest_with_users, lambda {|users|
    { :joins => "LEFT JOIN events_interests ei ON ei.event_id = events.id " +
         "LEFT JOIN users_intersets ui ON ui.interest_id = ei.interest_id",
      :conditions => ["ui.user_id IN ?", users], :group_by => "events.id"
    }    
  }

  #find events that share an interest with any user
  named_scope :shares_interest_with_any_user, lambda {
    { :joins => "LEFT JOIN events_interests ei ON ei.event_id = events.id " +
         "JOIN users_intersets ui ON ui.interest_id = ei.interest_id",
      :conditions => "ui.user_id IS NOT NULL", :group_by => "events.id"
    }    
  }

end

Теперь вы можете сделать это, чтобы получить все события, которые могут заинтересовать пользователя:

@events = Event.shares_interest_with_user(@user)

Или это, чтобы получить все события, в которых может заинтересоваться список пользователей:

@events = Event.shares_interest_with_users(@users)

Но, как я предупреждал, это не совсем элегантно .

Вы можете значительно упростить объединения, если вы переопределите свои отношения, чтобы иметь отношение has_many, посредством отношений с надлежащими моделями соединений вместо отношений HABTM. В вашем случае потребуется, чтобы плагин имел множество сквозных плагинов , чтобы это работало. Нотабене Вы должны будете добавить соответствующие операторы has_many / own_to во все другие модели. Даже модели объединения.

Class Event < ActiveRecord::Base
  has_many :event_interests
  has_many :interests, :through => :event_interests
  has_many :user_interests, :through => :interests
  has_many :users, :through => :user_interests
  ...   

  #find events that share an interest with a list of users
  named_scope :shares_interest_with_users, lambda {|user|
    { :joins => :user_interests, :group_by => "events.id",
      :conditions => {:user_interests => {:user_id => user}}
    }    
  }

  #find events that share an interest with any user
  named_scope :shares_interest_with_any_user, lambda {
    { :joins => :user_interests, :group_by => "events.id",
      :conditions => "user_interests.user_id IS NOT NULL"
    }

end

Теперь будет работать следующее.

@user = User.first; @users = User.find(1,2,3)

# @events = all events a single user would be interested in
@events = Event.shares_interest_with_users(@user)

# @events = all events any of the listed users would be interested in.
@events = Event.shares_interest_with_users(@user)

Вы даже можете определить именованную область видимости, чтобы выбрать события, которые еще не произошли, и объединить их в цепочку:

named_scope :future_events, lambda {
   { :conditions => ["start_time > ?", Time.now]}
}

Events.future_events #=> Events that haven't started yet.

# Events that a user would be interested in but only choose those 
# that haven't started yet.
Events.future_events.shares_interest_with_user(@user) 
...