Подтвердить что ты не робот

Запрос ActiveRecord через несколько соединений

У меня есть такая схема.

database schema

managers  
    has_many :emails  
    has_many :stores

emails  
    belongs_to :manager

stores  
    belongs_to :manager  
    belongs_to :region

regions  
    has_many :stores  
    has_many :readings

readings  
    belongs_to :regions

Я хочу получить показания для менеджера. В SQL я бы сделал что-то вроде этого.

SELECT * FROM managers  
    JOIN stores ON stores.manager_id = managers.id  
    JOIN regions ON stores.region_id = regions.id  
    JOIN readings ON readings.region_number = regions.number  
WHERE  
    manager.name = 'John Smith'  
AND  
    regions.number = '1234567'
LIMIT 100

Я не могу понять, как это сделать в activerecord. Я пытаюсь понять http://guides.rubyonrails.org/active_record_querying.html и http://guides.rubyonrails.org/association_basics.html но он не погружается. Мне кажется, мне просто нужно увидеть его с другой точки зрения.

Я думал, что буду обращаться к данным вроде этого, но я думаю, что просто не понимаю, как это работает.

managers.name  
managers.stores.name  
managers.stores.regions.readings.limit(10)

Мне нужно было что-то подобное, что намного уродливее.

managers.first.stores.first.regions.first.readings.limit(10)
4b9b3361

Ответ 1

Рассмотрим следующие модели (и использование has_many через):

class Reading < ActiveRecord::Base
  belongs_to :region,
    inverse_of: :readings
end

class Region < ActiveRecord::Base
  has_many :readings,
    inverse_of: :region

  has_many :stores,
    inverse_of: :region    
end

class Store < ActiveRecord::Base
  belongs_to :region,
    inverse_of: :stores

  belongs_to :manager,
    inverse_of: :stores
end

class Manager < ActiveRecord::Base
  has_many :stores,
    inverse_of: :region

  has_many :emails,
    inverse_of: :manager

  has_many :regions,
    through: :stores

  has_many :readings,
    through: :regions
end

class Email < ActiveRecord::Base
  belongs_to :manager,
    inverse_of: :emails
end

Теперь ваш вопрос немного неоднозначен, потому что вы говорите, что хотите получить показания для менеджера, но ваш SQL вообще не выбирает показания, а также предписывает область.

Предполагая, что вы хотите, чтобы все чтения соответствовали заданному диспетчеру и региону:

@readings = Reading.joins(region: { stores: :manager }).where(
  manager: { name: 'John Smith' },
  region: { id: 1234567 })

Предполагая, что вы также хотите загружать регионы, магазины и менеджеры, чтобы избежать запросов 1 + N:

@readings = Reading.includes(region: { stores: :manager }).where(
  manager: { name: 'John Smith' },
  region: { id: 1234567 })

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

@manager = Manager.where(name: 'John Smith').first!
@readings = manager.readings

Все приведенные выше примеры запросов возвращают ActiveRecord::Relation, которые могут быть дополнительно привязаны к условиям where или joins, limit, group, having и order и т.д.

Вы также должны учитывать различия методов joins, includes, preload, eager_load и references. Здесь есть краткая информация . Я бы посоветовал вам читать документы, руководства и блоги об Arel, поскольку он поддерживает объединения и псевдонимы.

После использования ActiveRecord в гневе какое-то время я пришел к выводу, что Sequel/SequelModel намного лучше DB/ORM, чем ActiveRecord. Никакое неуважение к разработчикам, но я обнаружил, что Sequel - лучший инструмент. У Arel есть тонкая документация в течение многих лет, и у ActiveRecord/Arel есть недостатки в ряде областей, таких как условия соединения, контроль типов соединений и нетерпеливая загрузка, объединения, пересечения, рекурсивные запросы, списки деревьев/смежности и многие другие функции SQL, которые Sequel покрывает.

Поскольку вы, кажется, только начинаете с AR, возможно, захотите вместо этого начать с Sequel, чем бороться со слабыми местами и разочарованиями запросов ActiveRecord, включая разрозненное использование AR и Arel, отношений с ассоциациями и странностей запросов, это продолжается и продолжается. Нет ничего более неприятного, чем знание SQL, которое вы хотите, но ActiveRecord/Arel сговорились, чтобы остановить вас, поэтому вы вынуждены использовать маршрутируемый маршрут эвакуации и "просто использовать фрагмент строки SQL", и вы получите результат, который не может быть привязан, но остальная часть вашего кода ожидает отношения! (например, постраничные результаты)

Ответ 2

Предполагая, что managers является именем модели

 managers.find(:all,
               :joins=>" JOIN stores ON stores.manager_id = managers.id  
                         JOIN regions ON stores.region_id = regions.id  
                         JOIN readings ON readings.region_number = regions.number"
               :conditions=>"manager.name = 'John Smith' AND regions.number = '1234567'"
               :limit=>100)

Ответ 3

попробуйте что-то вроде этого:

managers.joins(stores: {regions: :readings}).where('managers.name = ? AND regions.number = ?', 'John Smith', '1234567').limit(100) 

Ответ 4

Я хотел прокомментировать комментарий от user1757006 4 окт в 13:39, но у меня недостаточно очков. В любом случае, это касается более глубоких сценариев вложенности. Я добавил модели планет и стран, чтобы показать, как работает синтаксис, когда нужно подключать дополнительные модели.

@readings = Reading.includes(planet: [ country: [ region: [ {stores:
:manager}]]]).where(
manager: { name: 'John Smith' },   
region: {id: 1234567 })