Activerecord rails 合并多个 select 结果并在视图中呈现行

Activerecord rails merging multiple select results and render rows in view

我是 Rails 的新手,正在测试求职面试。

接近预期的结果,下面是代码。

数据示例:

Bookings
id,room_id,start_date,end_date,number_of_guests
1,1,2015-06-01,2015-06-07,1
2,3,2015-06-01,2015-06-07,2
3,5,2015-06-01,2015-06-07,2
2001,6,2015-06-01,2015-06-07,2
...

Hosts
id,name,address
1,Mr Host 1,1 Camden 
2,Mr Host 2,2 Camden Street
3,Mr Host 3,3 Camden Street
4,Mr Host 4,4 Camden Street
...

Rooms
id,host_id,capacity
1,1,2
2,1,2
3,2,2
4,2,2
...

ActiveRecord:

class Room < ActiveRecord::Base
  belongs_to :host
  has_many :bookings
end

class Host < ActiveRecord::Base
  has_many :rooms
end

class Booking < ActiveRecord::Base
  belongs_to :room
end

控制器:

  start_date = '2015-06-01' # hardcoded value for testing purposes
  end_date = '2015-06-07'   # hardcoded value for testing purposes   

  @available_rooms = Room.select(:host_id, :name, :address, :id, :number_of_guests ,:capacity)
               .joins(:bookings, :host)
               .where("bookings.number_of_guests <> rooms.capacity 
                        AND ? < rooms.capacity 
                        AND ? <= bookings.start_date
                        AND ? <= bookings.end_date", params[:guests], start_date, end_date)

  @booked_rooms = Room.select(:host_id, :name, :address, :id, :number_of_guests, :capacity)
               .joins(:bookings, :host)

  @total_rooms = Room.select(:host_id, :name, :address, :id, "0 as number_of_guests", :capacity)
               .joins(:host)

  @rooms = ((@total_rooms - @booked_rooms) + @available_rooms).sort_by(&:id)

查看:

 <% if @rooms %>
<section class="rooms">
 <ul class="list-unstyled list-rooms">
  <% for room in @rooms %>
  <li class="clearfix">
    <article>
      <div class="description">
        <header>
          <h2>host#<%= "#{room.host_id}: #{room.name}" %></h2> 
          <h3><%= "#{room.address} "%></h3>
        </header>
        <p>room#<%= "#{room.id} is available (#{room.number_of_guests} booked, #{room.capacity - room.number_of_guests} free out of total #{room.capacity})" %></p>
      </div>
    </article>
  </li>
  <% end %>
</ul>
</section>
<% end %>

当前结果:

  host#1: Mr Host 1
  1 Camden Street
  room#1 is available (1 booked, 1 free out of 2 total)

  host#1: Mr Host 1
  1 Camden Street      
  room#2 is available (0 booked, 2 free out of 2 total)

  host#2: Mr Host 2
  2 Camden Street
  room#4 is available (0 booked, 2 free out of 2 total)

  host#4: Mr Host 4
  4 Camden Street
  room#8 is available (0 booked, 2 free out of 2 total)

  host#5: Mr Host 5
  5 Camden Street
  room#9 is available (1 booked, 1 free out of 2 total)

  host#5: Mr Host 5
  5 Camden Street
  room#10 is available (0 booked, 2 free out of 2 total)

预期结果:

  host#1: Mr Host 1
  1 Camden Street
  room#1 is available (1 booked, 1 free out of 2 total)
  room#2 is available (0 booked, 2 free out of 2 total)

  host#2: Mr Host 2
  2 Camden Street
  room#4 is available (0 booked, 2 free out of 2 total)

  host#4: Mr Host 4
  4 Camden Street
  room#8 is available (0 booked, 2 free out of 2 total)

  host#5: Mr Host 5
  5 Camden Street
  room#9 is available (1 booked, 1 free out of 2 total)
  room#10 is available (0 booked, 2 free out of 2 total)

我正在考虑使用 jquery 来获得预期的结果,但我必须有一种方法可以通过活动记录或在视图中调用某种帮助程序来实现这一点。提前致谢。

理想情况下,我想重写查询以检索如下行的结果:

  host_id, address, total_rooms, room_id[0], booked[0], capacity[0], ... , room_id[n], booked[n], capacity[n]

所以对于前两行,我们将有:

  1, 1 Camden Street, 2, 1, 1, 2, 2, 0, 2
  2, 2 Camden Street, 1, 4, 0, 2

相当于前端结果:

主持人#1:主持人先生 1 1卡姆登街 房间 #1 有空(1 个已预订,2 个房间中有 1 个免费) 房间 #2 有空(0 人预订,2 人中有 2 人免费)

主持人#2:主持人先生 2 2卡姆登街 4 号房有空房(0 人预订,2 人中有 2 人免费)

您无法在 SQL 级别执行此操作。 SQL return 的统一行,您可以根据自己的喜好对它们进行排序,但是 SQL 中的 分组 行行不通。您不能将它用于 return 一对多关系的部分行;它将 return 每行的完整连接记录。

如果您只想输出顶级记录(本例中的 "host")一次,然后多次输出相关记录("rooms"),您需要将结果分组前端:

#...
@rooms = ((@total_rooms - @booked_rooms) + @available_rooms).sort_by(&:id)

# Produce a mapping of hosts to their rooms
@rooms_by_host = @rooms.group_by do |room|
  { id: host_id, address: room.address, name: room.name }
end

group_by 将块 return 具有相同值的元素分组。在这种情况下,任何具有相同 host_idaddressname 的房间都将被分组到 returned 散列中的嵌套数组中。

在您看来,您现在将有两个循环:一个遍历主机 => 房间映射的外循环,以及一个遍历其房间的每个主机的内循环:

<section class="rooms">
  <ul class="list-unstyled list-rooms">
    <% @rooms_by_host.each do |host, rooms| %>
      <li class="clearfix">
        <article>
          <div class="description">
            <header>
              <h2>host#<%= host[:id] %> - <%= host[:name] %></h2> 
              <h3><%= host[:address] %></h3>
            </header>
            <ul class="rooms">
              <% rooms.each do |room %>
                <li>room#<%= room.id %> is available (<%= room.number_of_guests %> booked, <%= room.capacity - room.number_of_guests %> free out of total <%= room.capacity %>)</li>
              <% end %>
            </ul>
          </div>
        </article>
      </li>
    <% end %>
  </ul>
</section>

如果您担心自己在面试中的表现,请考虑以下几点:

  • 不要在 Ruby 中使用 for x in y。惯用的循环方式是 y.each do |x|。使用 for x in y 表示缺乏经验。
  • <% if @rooms %> 是一个无用的检查,@rooms 是一个数组,它永远不可能是一个虚假的值。您需要 if @rooms.present?if @rooms.any?
  • 不要同时使用 <%= %>"#{...}"。里面的 #{} 完全是多余的。你做过的每一个地方...

    <%= "#{room.address} "%>
    

    你应该这样做

    <%= room.address %>
    
  • 让 ActiveRecord 构建尽可能多的 SQL 会更干净。而不是这个...

    .where("bookings.number_of_guests <> rooms.capacity 
                                AND ? < rooms.capacity 
                                AND ? <= bookings.start_date
                                AND ? <= bookings.end_date", params[:guests], start_date, end_date)
    

    使用这个

    .where('bookings.number_of_guests <> rooms.capacity')
    .where('? < rooms.capactiy', params[:guests])
    .where('? <= bookings.start_date', start_date)
    .where('? <= bookings.end_date', end_date)
    

    它还能使您的参数更接近它们的使用位置。

  • 如果你想稍微清理一下你的视图,请使用 partials。最内层的循环可能是:

        <ul class="rooms">
          <%= render rooms %>
        </ul>
    

    然后将循环体移动到 app/views/rooms/_room.html.erb

    <li>room#<%= room.id %> is available (<%= room.number_of_guests %> booked, <%= room.capacity - room.number_of_guests %> free out of total <%= room.capacity %>)</li>