这可以重构为单个数据库调用吗?

时间:2011-08-19 18:06:36

标签: ruby-on-rails database ruby-on-rails-3 refactoring

我在Events模型中列出了一系列事件。我需要做的是按城市/州的视觉分组活动。

从视觉上看,这将是这样的:

Tracking details

在这里,我想看看我正在做些什么来实现这一目标:

<% @number.events.locations.each do |location| %>
  <li>
    <h4><%= "#{location.city}, #{location.state}" %></h4>
    <ol>
    <% @number.events.city_state(location).each do |event| %>
      <li><%= event.status %></li>
    <% end %>
    </ol>
  </li>
<% end %>

以及相应的范围:

scope :locations, :group => 'city, state'
scope :city_state, lambda {|location| {:conditions => ["city = ? AND state = ?", location.city, location.state] }}

问题是,我正在为每个位置进行额外的数据库调用。

我可以将其归结为一个查询吗?

2 个答案:

答案 0 :(得分:1)

使用预先加载来预取数据库中的关联:

如果您同时提取所有活动,可以将其更改为预先加载活动的位置关联:

Event.all      # only loads Event objects from the database

#maybe try...

Event.includes(:locations)    # loads Event objects and their Locations

如果您需要某些其他对象的事件(例如您的@number对象),您可能希望在控制器中执行类似的操作,例如:

@number = Number.where(:id => whatever).includes(:events => :locations)

现在,在此示例中,您的@number对象将从数据库中提取,同时包含所有事件和所有这些事件的位置。

有关详细信息,请参阅Rails指南:http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations

更新了我在评论中的回答

根本不使用范围,您可以简单地使用Ruby为您完成工作,并将ActiveRecord和额外的数据库调用从循环中删除。

Enumerable#group_by将根据您提供的某个属性获取集合和分组。在这种情况下,您可以将所有事件,按城市和州分组,然后迭代它返回的哈希:

events = Event.all
events.group_by { |e| "#{e.city}, #{e.state}" }
# => {"Birmingham, AL" => [<event>, <event>, <event>], "Nashville, TN" => [<event>, <event>]}

这样做可能不如使用范围那么漂亮,但是它允许您进行一次数据库调用并将其留在那里。

答案 1 :(得分:0)

这是一个O(n ^ 2)解决方案,假设您的locations.each do |location|循环按宣传方式工作。应该将它降低到2分贝命中。

<% @number.events.locations.each do |location| %>
  <li>
    <h4><%= "#{location.city}, #{location.state}" -%></h4>
    <ol>
      <% @number.events.each do |event| %>
        <% if event.city == location.city and event.state == location.state %>
        <li><%= event.status -%></li>
        <% end %>
      <% end %>
    </ol>
  </li>
<% end %>

尚未测试,稍后会再次查看。