Rails - 按日期范围过滤 2 个模型

Rails - filter by date range with 2 models

我有两个型号:space和booking。 Space has_many bookings 和 Booking 有两个日期属性:check_in 和 check_out。

给定一个有效的日期范围,我想显示该范围内所有可用的 space

这是观点:

        <%= form_tag spaces_path, method: :get do %>
        <%= date_field_tag :query1,
        params[:query1],
        class: "form-control" %>
        <%= date_field_tag :query2,
        params[:query2],
        class: "form-control" %>
        <%= submit_tag "Search", class: "btn" %>
        <% end %>

这是Space控制器:

(...)
def index
    if params[:query1].present? && params[:query2].present?
      query1 = DateTime.parse(params[:query1])
      query2 = DateTime.parse(params[:query2])
      search = query1..query2

      bookings = Booking.all

      # returns the bookings that overlaps with the search
      overlapping_bookings = bookings.select do |booking|
        check_in = booking[:check_in]
        check_out = booking[:check_out]
        period = check_in..check_out
        search.overlaps?(booking.period)
      end

      # returns the spaces_id of the bookings that overlaps
      overlapping_space_ids = overlapping_bookings.select do |overlapping_booking|
        overlapping_booking[:space_id]
      end

      # remove the duplicates
      overlapping_space_ids.uniq!

      # remove the spaces with bookings that overlap with the search
      @spaces = Space.all.reject do |space|
        overlapping_space_ids.include? space[:id]
      end
    else
      @spaces = Space.all
    end
  end
(...)

我假设我的问题的根本原因是我将 Active Record Query Object 视为一个哈希数组,不确定它是否正确。我对此做了一些研究,但我还没有找到任何详尽的答案。

使用 SQL 子查询(例如在 PostgreSQL 中)你会这样做:

sql = <<SQL
SELECT *
FROM spaces
WHERE id in (
  SELECT space_id
    FROM bookings
   WHERE 
    (check_in, check_out) OVERLAPS (:from, :to)
  )
SQL;

Booking.find_by_sql([sql, {from: query1, to: query2})

希望对您有所帮助:)

我会先向 Booking 模型添加一个范围:

# in app/models/booking.rb
scope :overlapping, ->(from, to) {
  where(
    "(check_in, check_out) OVERLAPS (?, ?)", from, to
  )
}

然后将整个控制器方法更改为:

def index
  @spaces = Space.all

  if params[:query1].present? && params[:query2].present?
    from = DateTime.parse(params[:query1])
    to   = DateTime.parse(params[:query2])

    @space = @space.where.not(
      id: Booking.select(:space_id).overlapping(from, to)
    )
  end
end