Ruby on Rails 3: объединение результатов нескольких ассоциаций has_many или has_many_through - PullRequest
6 голосов
/ 03 ноября 2010

У меня есть следующие модели.У пользователей есть UserActions, и одним из возможных UserAction может быть ContactAction (UserAction - это полиморфизм).Существуют и другие действия, такие как LoginAction и т. Д. Так

 class User < AR::Base
  has_many :contact_requests, :class_name => "ContactAction"
  has_many :user_actions
  has_many_polymorphs :user_actionables, :from => [:contact_actions, ...], :through => :user_actions
 end

class UserAction < AR::Base
 belongs_to :user
 belongs_to :user_actionable, :polymorphic => true
end

class ContactAction < AR::Base
 belongs_to :user
 named_scope :pending, ...
 named_scope :active, ...
end

Идея состоит в том, что ContactAction объединяет двух пользователей (с другими последствиями в приложении) и всегда имеет принимающую и отправляющую стороны.В то же время у ContactAction могут быть разные состояния, например, истек срок действия, ожидается и т. Д.

Я могу сказать @user.contact_actions.pending или @user.contact_requests.expired, чтобы перечислить все ожидающие / просроченные запросы, которые пользователь отправил или получил.Это прекрасно работает.

Теперь мне нужен способ присоединить к обоим типам ContactAction.Т.е. @user.contact_actions_or_requests.Я попробовал следующее:

class User

 def contact_actions_or_requests
  self.contact_actions + self.contact_requests
 end

 # or
 has_many :contact_actions_or_requests, :finder_sql => ..., :counter_sql => ...

end

, но у всех них есть проблема в том, что невозможно использовать дополнительные искатели или named_scopes поверх ассоциации, например, @user.contact_actions_or_requests.find(...) или @user.contact_actions_or_requests.expired.

По сути, мне нужен способ выразить связь 1: n, которая имеет два разных пути.Один User -> ContactAction.user_id, другой User -> UserAction.user_id -> UserAction.user_actionable_id -> ContactAction.id.А затем объедините результаты (ContactActions) в один список для дальнейшей обработки с named_scopes и / или искателями.

Поскольку мне нужна эта связь буквально в десятках мест, было бы очень сложно написать (и поддерживать!) пользовательский SQL для каждого случая.

Я бы предпочел решить эту проблему в Rails, но я также открыт для других предложений (например, процедура PostgreSQL 8.3 или что-то подобное).Важно то, что, в конце концов, я могу использовать удобные функции Rails, как с любой другой ассоциацией, и, что более важно, также вкладывать их.

Любые идеи будут очень признательны.

Спасибо!


Чтобы дать своего рода ответ на мой собственный вопрос:

Я, вероятно, решу это, используя представление базы данных и добавив соответствующие ассоциации по мере необходимости.Для вышеизложенного я могу

  • использовать SQL в finder_sql для создания представления,
  • назвать его "contact_actions_or_requests",
  • изменить предложение SELECT, чтобы добавитьстолбец user_id,
  • добавить приложение / models / ContactActionsOrRequests.rb,
  • , а затем добавить "has_many: contact_actions_or_requests" в user.rb.

Я не знаюПока не знаю, как я буду справляться с обновлением записей - кажется, это невозможно сделать с помощью представления - но, возможно, это первый запуск.

1 Ответ

2 голосов
/ 08 января 2013

Метод, который вы ищете, это слияние. Если у вас есть два ActiveRecord :: Relations, r1 и r2, вы можете вызвать r1.merge (r2), чтобы получить новый объект ActiveRecord :: Relation, который объединяет два.

Если это сработает для вас, во многом зависит от того, как настроены ваши области действия, и от того, сможете ли вы изменить их для получения значимого результата. Давайте рассмотрим несколько примеров:

Предположим, у вас есть модель страницы. Он имеет нормальные атрибуты create_at и updated_at, поэтому у нас могут быть такие области: : обновлено -> {где ('создал_кат! = обновлен_at')} : not_updated -> {где ('made_at = updated_at')}

Если вы извлечете это из базы данных, вы получите:

r1 = Page.updated # SELECT `pages`.* FROM `pages` WHERE (created_at != updated_at)
r2 = Page.not_updated # SELECT `pages`.* FROM `pages` WHERE (created_at = updated_at)
r1.merge(r2) # SELECT `pages`.* FROM `pages` WHERE (created_at != updated_at) AND (created_at = updated_at)
=> []

Так что это действительно объединило два отношения, но не осмысленно. Еще один:

r1 = Page.where( :name => "Test1" ) # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test1'
r2 = Page.where( :name => "Test2" ) # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test2'
r1.merge(r2) # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test2'

Так, это может сработать для вас, но, возможно, нет, в зависимости от вашей ситуации.

Другой рекомендуемый способ сделать это - создать новую область видимости для вашей модели:

class ContactAction < AR::Base
  belongs_to :user
  scope :pending, ...
  scope :active, ...
  scope :actions_and_requests, pending.active # Combine existing logic
  scope :actions_and_requests, -> { ... } # Or, write a new scope with custom logic
end

Это объединяет различные черты, которые вы хотите собрать в одном запросе ...

...