我的架构看起来像这样:
create_table "customers" do |t|
t.integer "customer_number"
end
create_table "past_payments" do |t|
t.integer "customer_number"
t.datetime "transaction_date"
t.integer "arbitrary_sequence_number"
end
create_table "payment_details" do |t|
t.datetime "transaction_date"
t.integer "arbitrary_sequence_number"
end
TL;来自架构的DR - 客户通过主/外键与past_payment相关联。并且,当其transaction_date AND arbitrary_sequence_number相等时,PastPayment与单个PaymentDetail相关联。付款和明细没有正式的主要/外键关系。
这给了我以下的ActiveRecord模型:
class Customer < ActiveRecord::Base
has_many :past_payments, foreign_key: :customer_number, primary_key: :customer_number
has_many :payment_details, through: :past_payments # unfortunately, broken
end
class PastPayment < ActiveRecord::Base
has_one :payment_detail, ->(past_payment) {
where(arbitrary_sequence_number: past_payment.arbitrary_sequence_number)
}, foreign_key: :transaction_date, primary_key: :transaction_date
end
由于Customer has_many :past_payments
和PastPayment has_one :payment_detail
,我认为有一个关联可以定义为Customer has_many :payment_details, through: :past_payments
,但由于范围我无法使其工作在has_one :payment_detail
协会上定义。
具体来说,调用Customer.payment_details
会提升NoMethodError: undefined method 'arbitrary_sequence_number' for #<Customer:0x2i8asdf3>
。因此,与PastPayment相反,客户似乎会被传递到我的范围。
是否可以在客户上定义has_many :payment_details
关联?我做错了吗?
要明确的是,我希望能够说出Customer.where(some_conditions).includes(:payment_details)
并执行两个查询,所以如果有办法在没有关联的情况下实现这一点,我就会接受它。
注意:我无法更改此数据库。它是一些其他应用程序写入的数据库,我需要从中读取它。
与我的问题无关,这是我目前正在使用的解决方法。如果没有办法正确使用关联,我很乐意批评这个解决方案:
class Customer < ActiveRecord::Base
attr_writer :payment_details
def payment_details
@payment_details ||= Array(self).with_payment_details.payment_details
end
module InjectingPaymentData
def with_payment_details
results = self.to_a
return self unless results.first.is_a?(Customer)
user_ids = results.collect(&:id)
# i've omitted the details of the query, but the idea is at the end of it
# we have a hash with the customer_number as a key pointing to an array
# of PaymentDetail objects
payment_details = PaymentDetails.joins().where().group_by(&:customer_number)
results.each do |customer|
customer.payment_details = Array(payment_details[customer.customer_number])
end
end
end
end
ActiveRecord::Relation.send(:include, Customer::InjectingPaymentData)
Array.send(:include, Customer::InjectingPaymentData)
然后我可以通过最少的查询来执行以下操作:
@customers = Customer.where(id: 0..1000).with_payment_details
@customers.each { |c| do_something_with_those_payment_details }
该方法存在问题?
答案 0 :(得分:1)
您可以利用此gem更好地处理&#34;复合主键&#34;在ActiveRecord中:https://github.com/composite-primary-keys/composite_primary_keys
class Customer < ActiveRecord::Base
has_many :past_payments, foreign_key: :customer_number, primary_key: :customer_number
has_many :payment_details, through: :past_payments # this works now
end
class PastPayment < ActiveRecord::Base
has_one :payment_detail, foreign_key: [:transaction_date, :arbitrary_sequence_number],
primary_key: [:transaction_date, :arbitrary_sequence_number]
end