Rails 4 - eager loading on dependent select causing error (Rails4/Active Admin)

1.1k views Asked by At

I have an Active panel with a dependent select i.e. the choice on the first dropdown select impacts what appears in the second drop-down.

All was working perfectly well a few months ago but i just realized yesterday it is not working any more.

I managed to find what cause the error: if I remove the eager loading ("include"), then it works again.

NOT WORKING: (current version):

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

I get this error (form chrome dev tools's Console)

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

WORKING, when I remove "include" / eager loading :

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

It works perfecty in that case.

But I really want to eager load deal subsectors so I wonder what is causing this error, what has changed since it was working. I have a few assumptions but can't find the culprit.

  • Did Rails 4 change the way I should use find(params[:id]..) or the way i should use eager loading

  • did active Admin change the way it handles eager loading: maybe it only works on index and not on edit pages...

  • did turbolinks now on Rails 4 change the way i must eager load?

Here's the code:

- on 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

- the form with the 2 dependent selects

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

- the javascript powering it:

        // 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
        };

ADDED file

class DealSector < ActiveRecord::Base
  has_many    :deal_subsectors
end

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

There are 3 answers

3
meOn On BEST ANSWER

Rails 4 comes with some changes with respect to eager load algorithm. you can try the below code snippets in your case:

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

or

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

first one will fetch the data in single query but second one will make two query.

3
shlajin On

When you call ActiveRecord::find – it means you are expecting to get a one, single model from the DB. Then, you referring to this model and call dealsubsectors – I assume it's has_many relation to your model. It will produce 2 queries: first to fetch original DealSelector, second – all related DealSubsectors. There's nothing you can optimise (unless you dealsubsectors is a custom method in your model, not the relation).

If you see that something hits your DB with queries – you have to look in another place. Say, your form – do you display only one Client per form? If not, it will iterate and fetch all DealSector and DealSubsector again for every new client. Try to provide more code.

1
Yury Lebedev On

Why can't you just fetch the all DealSubsector records for the given the sector_id?

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