【问题标题】:Model scope with has_one relationship具有 has_one 关系的模型范围
【发布时间】:2021-12-12 05:51:56
【问题描述】:

在 Rails 中,如何创建过滤相关 has_one 属性的范围?我有两个模型,PatientAppointmentPatientAppointments 上声明has_many 关系。现在我添加了next_appointment 关系:

class Appointment < ActiveRecord::Base

end

class Patient < ActiveRecord::Base
  has_many :appointments, class_name: "::Appointment", foreign_key: :patient_id, inverse_of: :patient

  has_one(
    :next_appointment,
    -> { where("appointment_date >= now()").order(:appointment_date).limit(1) },
    class_name: "::Appointment",
    foreign_key: :patient_id
  )

  scope :by_range_next_appointment_date, lambda { |from, to|
    where(...)
  }
end

现在我想创建一个范围,返回在给定范围内进行下一次预约的所有患者。如何填写where() 来完成此操作?

这里有一个例子来说明:

假设布鲁斯·班纳的预约日期为 2021 年 11 月 2 日,彼得·帕克的预约日期为 2021 年 10 月 27 日和 2021 年 11 月 3 日。现在,我希望此范围返回下一次预约(截至 2021 年 10 月 26 日)的所有患者,该预约时间介于 2021 年 11 月 1 日和 201 年 11 月 7 日之间。这应该只返回布鲁斯班纳,因为他的下一次约会在那个范围内。彼得帕克的下一次约会是明天,所以他不应该被包括在内。

【问题讨论】:

  • 下一个 - 你的意思是“下一个即将到来”吗?例如,假设我一周内有一个约会,三周内有另一个约会,那么我是否应该包含在 1.day.from_now..2.weeks.from.now 的查询中?
  • @max 是的,我的意思是“下一个即将到来”。所以在你的例子中,你应该被包括在内。但是,如果您明天有约会并且我过滤了1.week.from_now..2.weeks.from.now,那么您将不会被包括在内。

标签: ruby-on-rails ruby model scope


【解决方案1】:

一个简单的多语言解决方案是使用一个共同相关的查询:

Patient.where(
  "(
     SELECT MIN(a.appointment_date)
     FROM   appointments a
     WHERE  a.appointment_date >= NOW() 
     AND    a.patient_id = patients.id
  ) BETWEEN ? AND ?", to, from
)

在 Postgres 上,您可以使用横向连接作为性能更好的替代方案:

Patient.joins(%q{
  JOIN LATERAL(
    SELECT appointment_date
    FROM appointments a
    WHERE a.patient_id = patients.id -- lateral reference
    AND   a.appointment_date >= NOW()
    ORDER BY a.appointment_date
    LIMIT 1
  ) aa ON true
}).where("aa.next_appointment_date" => to..from)

【讨论】:

  • 感谢您的选择。我正在使用 Postgres,所以我会更仔细地研究第二个选项。
  • 您还可以使用 ActiveRecord/Arel 组合子查询。 stackoverflow.com/a/60568914/544825
  • 谢谢。找了一些时间回来看看这些小细节。现在将这个简化且有些人为的示例重新合并到实际的代码和数据库模式中......
【解决方案2】:

由于appointment_date 在另一个表中,因此需要.join 才能访问此列。只需将Range 传递给where 的哈希参数即可查询一系列值(它将使用BETWEEN 运算符生成SQL 子句)。

  scope :by_range_next_appointment_date, lambda { |from, to|
    joins(:appointments).where(appointment_date: from..to)
  }

这应该返回在给定日期范围内预约的所有患者。如果您只想要未来的约会 - 您可以链接其他 .where

  scope :by_range_next_appointment_date, lambda { |from, to|
    joins(:appointments)
      .where(appointment_date: from..to)
      .where("appointment_date >= now()")
  }

注意传递给.where 的值类型 - 如果列类型为datetime,则将Date 值转换为.to_datetime

【讨论】:

  • 有没有办法使用has_many 关系,所以我不必重复.where("appointment_date &gt;= now()")
  • 另外,这段代码并不完全符合我的要求。让我举个例子来澄清一下。假设 Bruce Banner 在 2021 年 11 月 2 日有约会,而 Peter Parker 在 2021 年 10 月 27 日和 2021 年 11 月 3 日有约会。现在,我希望此范围返回下一次预约(截至 2021 年 10 月 26 日)的所有患者,该预约时间介于 2021 年 11 月 1 日和 201 年 11 月 7 日之间。这应该只返回布鲁斯班纳,因为他的下一次约会在那个范围内。彼得帕克的下一次约会是明天,所以他不应该被包括在内。您的代码将返回两个患者。
  • 感谢有关使用.join() 和日期转换的提示。转换实际上比日期与日期时间更复杂,但与这个问题无关。
猜你喜欢
  • 1970-01-01
  • 2015-03-16
  • 1970-01-01
  • 2012-12-28
  • 2021-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多