Home > Software design >  How to get active record relation of polymorphic parents from child object?
How to get active record relation of polymorphic parents from child object?

Time:08-22

I have below structure:

class Transaction < ApplicationRecord
  belongs_to :transactionable, polymorphic: true
end

class TransactionSale < ApplicationRecord
  has_many :transactions, as: :transactionable
end

class TransactionRental < ApplicationRecord
  has_many :transactions, as: :transactionable
end

I want to get all the linked transactions (sale & rental) by querying the Transaction table only. For example:

Transaction.includes(:transactionable).where(project_id: project_id).map { |txn| txn.transactionable }

The above query returns both TransactionSale and TransactionRental objects combined and that is the exact result I wanna achieve. The only problem here is it returns a ruby array instead of ActiveRecord::Relation so I can't sort the transactions further in a single query or use other active record methods.

I have read all other answers that suggest plucking the ids and then apply where which is not possible here coz I don't know whether the table is rental or sale.

Is there any way to achieve the same result without losing the ActiveRecord relation?

CodePudding user response:

Apparantly i solved it myself,

# Get id and classes of all transaction types
locks = Transaction.where(project_id: self.id).pluck(:transactionable_id, :transactionable_type)
    
# Init empty active record relation
final = Transaction.none

# convert id and classes array to hash to group on polymorphic classes
locks.group_by { |s| s[1] }.each_pair do |k,v| 
      # extracts ids to make query
      ids = v.map { |d| d[0] }
      
      # make query from respective model
      data = eval(k).where(id: ids)

      # merge the array returned above with empty relation and rewrite 
      final = final.or(data)
end

The final contains the active record relation of all polymorphic records.

CodePudding user response:

No nice/easy way, no. You could do it by creating the SQL and running a UNION as a subquery but that's pretty ugly.

  • Related