Rails 3.2 Организация отчета на основе сложных отношений

Я разрабатываю программу для складской/экспедиторской компании со следующими отношениями данных. Суть отношений, приведенных ниже, заключается в том, что склад получает сырье (продукт) от различных перевозчиков (клиентов) и хранит их до тех пор, пока они не потребуются для отправки на завод-изготовитель. Когда партия покидает склад, производственное предприятие должно знать, от какой компании было получено каждое сырье. Взгляните на следующий ERD. введите здесь описание изображения

РЕДАКТИРОВАТЬ: Мои отношения в текстовой форме.

отгрузка.рб

has_many :product_shipments, :dependent => :destroy
has_many :products, :through => :product_shipments

product_shipment.rb

belongs_to :product
belongs_to :shipment

продукт.rb

has_many :product_shipments
has_many :shipments, :through => :product_shipments, :dependent => :destroy
belongs_to :client

клиент.rb

  has_many :products, :dependent => :destroy

У меня возникли проблемы с созданием запросов в формате, соответствующем требованиям. В отчете об отгрузке указывается дата, и он должен повторяться по каждому клиенту и перечислять продукты, отгруженные на производственное предприятие в указанную дату. Он должен быть сгенерирован динамически и отформатирован следующим образом.

Shipment Date: 2013-01-01

Client: 12 Name: ACME Widget Co

Product Name | Product Code | Qty Shipped
_________________________________________ 
nuts & bolts |      gj-3423 |          25
steel sheet  |      4g-2394 |          10
dc Motor     |      cj-c213 |           4


Client: 14 Name: Blah Blah, Inc

Product Name | Product Code | Qty Shipped
_________________________________________ 
spacers      |      gj-3423 |          15
epoxy        |      4g-2394 |          10
dc Motor     |      24-gj19 |           6

Client: 15 Name: Sample Co, Inc

Product Name | Product Code | Qty Shipped
_________________________________________ 
hot roll 48  |      cg-3423 |          15
welding wir  |      4g-2394 |          10
dc Motor     |      c2-1241 |           6
.
.
.
.

Проблема заключается в создании запросов с использованием ActiveRecord. Легко получить отгрузки и продукты на определенную дату, например, ниже. Нелегко захватить исходного клиента, от которого было получено сырье, а затем динамически повторять поставки на производственное предприятие для этого клиента.

ОБНОВЛЕНИЕ: теперь я могу группировать клиентов и поставки, как указано выше. Теперь мне нужно исключить клиентов, у которых НЕТ отгрузки в указанную дату. Приведенное ниже, хотя и несколько правильное, по-прежнему дает ужасный запрос O (n). Вот что у меня есть.

@clients = Client.includes(:products => :shipments)
@clients.each do |c|
  puts c.name
  c.products.each do |p|
    p.shipments.where("ship_date like '#{@ship_date.strftime("%Y-%m-%d")}%'").each do |s|
      s.product_shipments.joins(:product).each do |ps|
        puts s.bill_of_lading + " " + ps.product_id.to_s + " " + ps.product.product_name.to_s + " " + ps.product.product_code + " " +ps.qty_shipped.to_s + " " + s.ship_date.to_s
        end
    end
  end
end

Моя проблема заключается в том, как мне организовать запрос, чтобы начать с клиентов, а затем перечислить продукты, отправленные 30 июня 2012 года. С этой точки зрения запрос становится сумасшедшим. Я не уверен, как сгенерировать запрос с активной записью, когда отношения настолько удалены.


person ctilley79    schedule 11.01.2013    source источник


Ответы (2)


ОБНОВЛЕНИЕ: хорошо, глядя на результаты, которые вы ожидаете в отчете, значения из ProductShipment (например, атрибут количества) должны быть извлечены, поэтому product_shipments должен быть включен во вложенную ассоциацию, которую мы хотим загрузить, иначе ProductShipments не создается, он служит только в качестве таблицы соединения.

Поэтому вместо Client.includes(:products => shipments)... вы хотите:

@clients = Client.includes(:products => {:product_shipments => :shipment}).
                  where("shipments.ship='#{ship_date}'")

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

Таким образом, вы можете написать (уважительно к закону Деметры):

class ProductShipment < AR
  def report_details
    s = shipment; p = product
    "#{s.bill_of_lading} #{p.id} #{p.name} #{p.code} #{quantity} #{s.shipped_on}"
  end
end

А вот и сложная часть: как написано, :products => {:product_shipments => :shipment} Rails понимает

product_shipments.shipment но не product_shipment.products

Последнее на самом деле вызовет вызов БД... (чего мы пытаемся избежать). К счастью, у Rails есть еще одна хитрость:

class ProductShipment < ActiveRecord::Base
  belongs_to :product, inverse_of: :product_shipments
end

class Product < ActiveRecord::Base
  has_many :product_shipments, inverse_of: :product
end

Застраховав зеркалирование ассоциаций, вы теперь можете получать product_shipments по продуктам и получать отчет без O(n) обращений к БД:

@client.map {|c| c.products.map {|p| p.product_shipments} }.flatten.each do |ps|
  puts ps.details
end

ОБНОВЛЕНИЕ КОНЕЦ


Вы должны с нетерпением загрузить связанные модели, иначе вы получите печально известный запрос O (n).

Shipment.where(:date => someday).includes({:product => :client}).each do |shipmt|
  puts shipmt.date

  shipmt.product.group_by(&:client).each do |client, products|
    puts client

    products.each do |product|
      puts product.details
    end
  end
end

Кстати, rails выполняет соединение напрямую от отгрузки к продукту, предполагая, что здесь у вас есть ассоциация has_many :through или has_and_belongs_to_many, поэтому нет необходимости использовать таблицу соединений (также известную как product_shipment).

person charlysisto    schedule 11.01.2013
comment
Вот как я храню количество для отгрузки в этой таблице поиска. - person ctilley79; 11.01.2013
comment
Я понимаю, что не совсем точно ответил на ваш вопрос. Но запускается ли запрос через клиент, нацеленный на тот же результат, что и вы? - person charlysisto; 11.01.2013
comment
Вышеприведенное не соответствует форматированию того, чего я хотел бы достичь. Предоставленный вами код будет выводить дату для каждого товара в отгрузках. Посмотрите внимательно на приведенный выше результат, которого я пытаюсь достичь. Дата будет отображаться только один раз во всем отчете. - person ctilley79; 12.01.2013

Вот как я это сделал. Может быть, не лучший способ сделать это, но это близко. Спасибо за @charlysisto за то, что направил меня в правильном направлении с легкой загрузкой отношений. Я по-прежнему получаю O (n) в запросе на соединение для таблицы поиска, но могло быть и хуже. Любые уточнения, пожалуйста, прокомментируйте, чтобы я мог улучшить ответ.

@clients = Client.order("clients.id ASC").includes(:products => :shipments).where("shipments.ship_date like '#{ship_date}'")
@clients.each do |c|
  puts c.name
  c.products.each do |p|
    p.shipments.each do |s|
      s.product_shipments.joins(:product).each do |ps|
        puts shipment and products stuff
      end
    end
end
person ctilley79    schedule 13.01.2013
comment
Есть способ избежать .. подождите, на самом деле есть 2 способа избежать проблемы с запросом O (n): если для каждого клиента может быть только одна отправка на дату или если клиент может получить несколько поставок одного и того же продукта в один и тот же день. , Первое позволит элегантное решение, второе означает больше манипуляций с хэшем. Скажи мне... - person charlysisto; 13.01.2013
comment
продукция клиента поступает на склад в виде чека. Затем наш склад отправляет продукцию на предприятие MFG. Отгрузка состоит из продуктов на поддонах, которые могут поместиться на прицепе. Иногда в трейлере может быть смесь двух разных продуктов. Таким образом, может быть несколько отгрузок одного и того же продукта в день. Предприятию MFG необходимо знать, какой поддон пришел от какого клиента. - person ctilley79; 13.01.2013
comment
Большое обновление моего ответа! это был интересный вызов. Все еще не уверен, что клиент ACME может получить тот же день: гайки и болты 25... гайки и болты 12... В любом случае, надеюсь, мое объяснение достаточно ясно - person charlysisto; 15.01.2013