Ruby on Rails Thursday, February 4, 2016



On 2/4/16 11:30 AM, John Sanderbeck wrote:
I am trying to make this as efficient as possible which is why I was  trying to do the group call.    I was doing it in the Highchart definition which worked however it does  many queries depending on how many initiatives there are.    series: [{    name: "Trainings",    data: [    <% Initiative.all.each do |initiative| %>        ['<%= initiative.name %>',<%= Training.where("initiative_id = ?",  initiative.id).count %>],    <% end %>    ]  }],

In order to be most efficient I would first ensure that you have the possessive relationships are in place. If so you would be able to include Training when Calling Initiative.

Initiative.all.includes(:training).each do |initiative|
    puts initiative.training.size
end

This will utilize eager loading to reduce this run to 2 queries. The issue with this would be dataset size, imagine if you have 1000 initiatives with a total of 300,000 trainings between them. You will need to be able to hold all of those trainings in memory to run this. Not efficient.

Likewise your methodology is not efficient as it essentially is an n+1 anitpattern, meaning that you will be doing n queries to get the trainings and another 1 for the initiatives. So if you have 1000 initiatives that will be 1001 queries.

So the better way to do this would be to have a counter cache of trainings on the initiative model. This would involve adding a trainings_count field to initiative and then setting the relationship on the training model to something like

belongs_to :initiative, counter_cache: true

Then when you call initiative.training.size, rails would look to the data in that field before trying to pull the data from the database, so you would be able to use

Initiative.all.each do |initiative|
    puts initiative.training.size
end

Note that, when adding counter_caching to an existing model set you will need to run a process (pref in the migration) to reset the counters for the model.

This could be your migration file. Though using a pure sql statement might be more efficient.

  add_column :initiatives, :trainings_count, null: false, default: 0
  Initiative.all.each do |i|
    Initiative.reset_counters(i.id, :trainings)
  end

hope this helps
    One of the issues is that there may not be any record count for a  particular initiative so I also need to return a zero but still map the  initiative.    The example from the Railscast is like this and is similar to what I  was trying to do however this example was based on a day mapping.    def orders_chart_series(orders, start_time)      orders_by_day = orders.where(:purchased_at =>  start_time.beginning_of_day..Time.zone.now.end_of_day).  group("date(purchased_at)").select("purchased_at, sum(total_price) as  total_price")        (start_time.to_date..Date.today).map do |date|        order = orders_by_day.detect { |order| order.purchased_at.to_date  == date }        order && order.total_price.to_f || 0      end.inspect  end    I will try the examples you have given me so far to see if I can  accomplish what I need.    I appreciate the fast responses...    

No comments:

Post a Comment