简体   繁体   中英

Rails - filter by date range with 2 models

I have two models: space and booking. Space has_many bookings and Booking has two date attributes: check_in and check_out.

Given a valid date range, I want to show all spaces available during this range

This is the view:

        <%= 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 %>

This is the SpaceController:

(...)
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
(...)

I assume the root cause of my problem is that I'm treating the Active Record Query Object as an array of hashes, not sure if it's correct. I made some researches on this but I haven't found any exhaustive answer.

Using an SQL subquery (in PostgreSQL for example) you would do this:

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})

Hope that helps:)

I would add a scope to the Booking model first:

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

and would then change the whole controller method 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

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