Объединение нескольких областей, содержащих соединения () и select ()

Все, что я читаю, советует людям не допускать «сложных» запросов к контроллеру и помещать их в области видимости модели. Однако как вы порекомендуете сделать это с запросом, которому нужны данные из трех моделей при использовании joins. Например,

class User < ActiveRecord::Base
  belongs_to :company
  belongs_to :profile
end

Если нам нужны companies.name и profiles.city из User, мы могли бы вставить запрос в действие контроллера, что отлично работает.

User.joins(:company, :profile).select('users.*, companies.name as company_name, profiles.city as city').find(1)

Чтобы не допустить этого в контроллере, мы могли бы определить именованную область видимости, например

scope :include_company_name_and_profile_city, -> { joins(:company, :profile).select('users.*, companies.name as company_name, profiles.city as city') }

а в контроллере используйте User.include_company_name_and_profile_city.find(1).

Но что, если в разное время нам нужно только название компании или только город профиля? Можем ли мы определить две области и связать их?

scope :include_company_name, -> { joins(:company).select('users.*, companies.name as company_name') }
scope :include_profile_city, -> { joins(:profile).select('users.*, profiles.city as city') }

Вызов User.include_company_name.include_profile_city.find(1) даст запрос, который включает два users.* в предложении SELECT.

SELECT users.*, companies.name as company_name, profiles.city as city, users.*, ...

Каков рекомендуемый способ справиться с этим?

  1. Поместить все вызовы joins() и select() в контроллер?
  2. Создать одну область, которая загружает общие данные ассоциации и не заботится о возможных накладных расходах, если эти данные никогда не используются? (это ошибочно, см. ниже)
  3. Создать несколько именованных областей, не содержащих select('users.*'), и добавить select('users.*') либо в контроллере, либо в области по умолчанию?
  4. Имеет ли значение наличие нескольких users.* в предложении select?

Если люди рекомендуют вариант 2, как насчет ситуации, когда нам нужно больше (не очень распространенных) данных ассоциации в каком-то другом шаблоне? (т.е. User.include_common_association_data.include_non_common_association_data.find(1)). Это будет иметь ту же проблему, о которой говорилось выше (несколько users.* в предложении select).


person Feech    schedule 03.12.2013    source источник


Ответы (1)


Я не уверен, понимаю ли я реальный вопрос, но похоже, что вы пишете соединения и выбираете, когда ActiveRecord делает все это за вас?

Это твои модели?

class User < ActiveRecord::Base
  belongs_to :company
  belongs_to :profile
end
...
class Company 
  has_many :users
end

class Profile
  has_many :users
end

Итак, если вы пытаетесь это сделать:

Если нам нужны companies.name и profiles.city от пользователя, мы могли бы вставить запрос в действие контроллера, что отлично работает.

вы можете получить их с помощью отношения Active Record:

user = User.find(whatever).includes(:company, :profile)
user.company.name 
user.profile.city

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

scope :latest, order("created_at desc").limit(3)
scope :city_present, where("city is not null")

edit: если вы раньше не тратили на это время, интерфейс запросов activerecord руководство очень полезно. Кроме того, ActiveRecord очень умен в оптимизации ваших запросов, даже если вы не говорите ему, что делать, если вы хотите увидеть SQL, который он отображает, используйте консоль rails для просмотра.

person creativereason    schedule 03.12.2013
comment
user = User.find(1) выполнит один запрос, доступ к user.company.name выполнит второй (SELECT * FROM companies ...), user.profile.city выполнит третий (SELECT * FROM profiles ...). Однако вы можете оптимизировать это с помощью joins() и select(), используя User.joins(:company, :profile).select('users.*, companies.name as company_name, profiles.city as city').find(1), который добавит атрибуты company_name и city в пользовательский экземпляр при выполнении только одного запроса. - person Feech; 04.12.2013
comment
Да, но если вы выполняете активную загрузку объединенных объектов, он выполнит один запрос для всех трех, и у вас будут полностью загруженные объекты, вместо того, чтобы просто извлекать конкретную информацию из выборки. Active Record позволяет загружать любое количество ассоциаций с помощью одного вызова Model.find, используя массив, хэш или вложенный хеш массива / хеша с методом include. - person creativereason; 04.12.2013
comment
Неа. Я предполагаю, что люди пишут каждую область специально для тех данных, которые им нужны, но это приводит к дублированию, которое мне не нравится. - person Feech; 05.12.2013
comment
Вы видели мою точку зрения об использовании включений для одновременного запроса нескольких объектов (в одном запросе)? - person creativereason; 05.12.2013