简体   繁体   中英

Rails complex query with multi level associations

I have a deeply nested layout as follows:

Contract -> has many Packages -> has many Services

Payment -> belongs_to Invoice -> belongs_to Contract

class Contract < ActiveRecord::Base
  has_many :invoices
  has_many :contract_packages
  has_many :packages, through: :contract_packages
end

class Package < ActiveRecord::Base
  has_many :services
  has_many :contract_packages
  has_many :contracts, through: :contract_packages
end

class ContractPackage < ActiveRecord::Base
  belongs_to :contract
  belongs_to :package
end

class Service < ActiveRecord::Base
  belongs_to :package
end

class Invoice < ActiveRecord::Base
  belongs_to :contract
end

class Payment < ActiveRecord::Base
  belongs_to :invoice
end

I want to find what Services, and how many times were invoiced in a certain period of time, based on payment date. Invoice date may not be the same as payment date.

I know hot to do it by pure SQL, and it works, but I am stuck if I want to do it the rails way.

Any ideas?

Edit:

The pure sql query:

select s.[name], count(*), s.[price] from payments p 
left join invoices i on p.invoice_id=i.id
left join contracts c on i.[contract_id]=c.id
left join contract_packages cp on cp.contract_id=c.id
left join packages pk on cp.[package_id]=pk.id
left join services s on s.package_id=pk.id
where ... conditions
group by s.id
order by s.id asc

In my original question I left out, for brevity, a join table, because a package may belong to many contracts. The sql here includes the join table. I updated the models also.

Doing joins in in activerecord is quite straight forward as long as you have defined the relationships in your models. You just pass a hash to joins and it figures out what keys to use. Adding the where conditions can be done in similar fashion.

I noticed that there was no has_many :payments in your invoice is this by design? In that case why?

The select clause I have written will give all Service objects created with this query an extra method count where you will find your value.

Service.select('*, count(*) as count')
.joins({ 
  package: { 
    contract: {
      invoices: :payment
    } 
  }
})
.where(conditions_hash)
.group('services.id asc')
.order(:id)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM