Rails 4 - 在依赖选择上使用急切加载导致错误 (Rails4/Active Admin)

9
我有一个带有从属下拉菜单的活动面板,即第一个下拉菜单的选择会影响第二个下拉菜单的显示内容。
几个月前一切都运行得非常完美,但我昨天才意识到它不再工作。
我找到了造成错误的原因:如果我移除贪婪加载("include"),那么它就又可以工作了。 当前版本无法正常工作:
@deal_subsectors = DealSector.find(params[:deal_sector_id],
   include: :deal_subsectors).dealsubsectors

我从 Chrome 开发工具的控制台得到了以下错误信息:
GET http://localhost:3000/admin/deals/deal_subsectors_by_deal_sector?deal_sector_id=2 404 (Not Found)    
send @ jquery.js?body=1:9660    
jQuery.extend.ajax @ jquery.js?body=1:9211    
jQuery.(anonymous function) @ jquery.js?body=1:9357    
jQuery.extend.getJSON @ jquery.js?body=1:9340    
update_deal_subsector @ active_admin.js?body=1:19    
(anonymous function) @ active_admin.js?body=1:12    
jQuery.event.dispatch @ jquery.js?body=1:4666    
elemData.handle @ jquery.js?body=1:4334    
ListPicker._handleMouseUp @ about:blank:632

当我删除“include” / 急切加载时,工作原理如下:

@deal_subsectors = DealSector.find(params[:deal_sector_id]).deal_subsectors

在这种情况下,它完美地运作。

但我真的想要急切地加载交易子行业,所以我想知道是什么导致了这个错误,自从它工作以来发生了什么变化。我有一些假设,但找不到罪魁祸首。

  • Rails 4是否改变了我应该如何使用find(params[:id]..)或我应该如何使用eager loading的方式

  • active Admin是否改变了它处理eager loading的方式:也许它只在索引页面上工作,而不是在编辑页面上...

  • turbolinks现在在Rails 4上是否改变了我必须急切加载的方式?

以下是代码:

- 在Active Admin上

ActiveAdmin.register Deal do

# controller for the multi-select sector/subsector in the form
# does 2 things at same time: creates method and automatically defines the route of the method defined in controller
    if params[:deal_sector_id] # pass the id
      @deal_subsectors = DealSector.find(params[:deal_sector_id], include: :deal_subsectors).dealsubsectors
    else
      @deal_subsectors = []
    end

    render json: @deal_subsectors
  end   

end

- 带有2个依赖选择器的表单

form do |f|

f.inputs "Client" do


      f.input :deal_sector_id,
        :label      => "Select industry:",
        :as         => :select,
        :prompt     => true,
        :collection => DealSector.order("name").all.to_a
      f.input :deal_subsector_id,
        :label      => "Select subindustry:",
        :as         => :select,
        :prompt     => true,
        :collection => DealSubsector.order("name").all.to_a        
    end

end

- 驱动它的JavaScript代码:

        // for edit page
        var deal_subsector = { };

       $(document).ready(function() {
         $('#deal_deal_sector_id').change(function() {
            update_deal_subsector();
         });
       });

       function update_deal_subsector() {
           deal_sector_id = $('#deal_deal_sector_id').val(); //get the value of sector id
           url = '/admin/deals/deal_subsectors_by_deal_sector?deal_sector_id=' + deal_sector_id; //make a query to the url passing the deal sector id as a parameter
          $.getJSON(url, function(deal_subsectors) {
              console.log(deal_subsectors);
                  $('#deal_deal_subsector_id').html("") //just blanks the id, blank it before populating it again if sector changes
              for( i = 0; i < deal_subsectors.length; i++) {
                 console.log(deal_subsectors[i]);
                 $('#deal_deal_subsector_id').append("<option value=" + deal_subsectors[i].id + ">" + deal_subsectors[i].name + "</option>")
      };
    }); //pass the url and function to get subsector ids and all we get is assigned to the variable subsector_id
  };

          // for index page (filters)
          $(document).ready(function() {
              $('#q_deal_sector_id').change(function() {
                 update_deal_subsector_filter();
              });
              });

          function update_deal_subsector_filter() {
               deal_sector_id = $('#q_deal_sector_id').val(); //get the value of sector id
               url = '/admin/deals/deal_subsectors_by_deal_sector?deal_sector_id=' + deal_sector_id; //make a query to the url passing the deal sector id as a parameter
               $.getJSON(url, function(deal_subsectors) {
                      console.log(deal_subsectors);
                $('#q_deal_subsector_id').html("") //just blanks the id, blank it before populating it again if sector changes
                 for( i = 0; i < deal_subsectors.length; i++) {
                     console.log(deal_subsectors[i]);
                     $('#q_deal_subsector_id').append("<option value=" + deal_subsectors[i].id + ">" + deal_subsectors[i].name + "</option>")
      };
    }); //pass the url and function to get subsector ids and all we get is assigned to the variable subsector_id
        };

新增文件

class DealSector < ActiveRecord::Base
  has_many    :deal_subsectors
end

class DealSubsector < ActiveRecord::Base    
  belongs_to  :deal_sector,   :foreign_key => 'deal_sector_id'
end
3个回答

2
当您调用ActiveRecord::find时,这意味着您希望从数据库中获取一个单个模型。然后,您引用此模型并调用dealsubsectors - 我假设它是与您的模型相关的has_many关系。它会产生2个查询:第一个查询原始DealSelector,第二个查询所有相关的DealSubsectors。 除非dealsubsectors是您模型中的自定义方法而不是关系,否则您无法对其进行优化。 如果您发现某些内容正在使用查询命中您的数据库,请检查其他地方。比如说您的表单-您每个表单只显示一个客户端吗?如果不是,那么它将为每个新客户重新迭代并提取所有DealSector和DealSubsector。请尝试提供更多代码。

是的,它有一个has_many关系。当你说没有什么可以优化的时候,我不认为这是正确的:我认为急切加载恰好意味着在一次调用中加载相关资源,以使查询更有效率。所以在这里,我想要使用游戏部门急切加载相关的子部门。我将在模型关系上添加一些代码。告诉我你需要什么其他代码。 - Mathieu
使用此代码,您无法使用急切加载: DealSector.find(params[:deal_sector_id]).deal_subsectors - 您可以从控制台(rails c)运行它并查看查询。它将触发2个查询:查找DealSector和获取DealSubsectors。您必须考虑其他加载某些模型的地方 - 例如在视图中。 - shlajin
当然,我期望这个问题的答案不是告诉我它不能按原样工作。我知道这一点。我期望的是如何能够管理急切加载交易子部门。我知道你可以使用 :includes 在 active admin 上急切加载模型,但我需要指导和答案来帮助我用精确的答案或想法实现它。 - Mathieu

1

Rails 4相对于预加载算法进行了一些更改。 您可以尝试在您的情况下使用以下代码片段:

@deal_subsectors = DealSector.eager_load(:deal_subsectors).find(params[:deal_sector_id]).dealsubsectors

或者

@deal_subsectors = DealSector.includes(:deal_subsectors).find(params[:deal_sector_id]).dealsubsectors

第一个将在单个查询中获取数据,但第二个将进行两个查询。

看起来工作正常。只是一个问题:在我的终端内可以看到的加载速度是否比我不使用急切加载时更快,例如使用 @deal_subsectors = DealSector.eager_load(:deal_subsectors).find(params[:deal_sector_id]).deal_subsectors 而非没有使用急切加载的 @deal_subsectors = DealSector.find(params[:deal_sector_id]).deal_subsectors?由于我没有许多子部门(目前少于20个),也许使用急切加载需要更多时间是正常的。可以这样说:当我有更多子部门时,采用急切加载更划算吗? - Mathieu
是的,@mathieu,你的想法绝对正确。大多数情况下,急切加载可以让你避免N+1查询陷阱,如果没有急切加载,你可能需要运行100个查询才能得到1个结果,而不是运行1个查询就能得到100个结果。急切加载总是会有回报的,也许你只会遇到一两个例外。在你的情况下,急切加载肯定会减少查询时间,使你的响应更快。数据越多,你从急切加载中获得的好处就越多。 - meOn
感谢您的进一步解释。 - Mathieu

0

为什么您不能只获取给定sector_id的所有DealSubsector记录?

DealSubsector.where(deal_sector_id: params[:deal_sector_id])

Yury,谢谢你,但我并不是说我之前使用的方法不起作用。这个方法完美地运行:@deal_subsectors = DealSector.find(params[:deal_sector_id]).deal_subsectors 但是我想使用贪婪加载。 - Mathieu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接