jaqbyte
jaqbyte

Reputation: 57

How to eager load in this case?

class Model RunningTracks < ActiveRecord::Base
  has_many :achievements
  has_many :runners, through: :achievements

class Model Runner < ActiveRecord::Base
  has_many :achievements
  has_many :running_tracks, through: :achievements

class Model Achievement < ActiveRecord::Base
  belongs_to :runner
  belongs_to :running_tracks

On the HomePage there is an each do list to display all RunningTracks. Next to the track we display a button to mark it as run. An eager loading solution is an includes and references (Rails 4) in the query in the controller:

class RunningTracksController
  def index
    @running_tracks = RunningTracks.includes(:achievements).references(:achievements)
  end

To improve the usability, the button next to each record should not be visible if it had been marked. (will be refactored in an helper method)

@running_tracks.each do |track|
  .
  - unless track.achievements.map(&:user_id).include? current_user.id
    = button_to "done", running_track_mark_as_done_path(track)

This works...BUT... as the database gets more records it will query a lot of achievements, therefore a custom association and an includes query for only the achievements of the current_user (Devise) should be the solution.

class Model RunningTracks < ActiveRecord::Base
  .
  has_many :achieved_runns, -> { where user_id: current_user.id }, class_name "Achievement"

But because current_user is not accessible in the model, this does not work. I don't think its possible to pass a variable to the hay_many association, is it?

Is there a more efficient solution to do an eager loading of all RunningTracks including only the Achievements by the current user? Thank you for your valuable time!

Upvotes: 2

Views: 1036

Answers (1)

Dan Wich
Dan Wich

Reputation: 4943

The following is ugly and breaks the ActiveRecord metaphor, but would give you very fast lookups as the DB grew in size. Maybe someone else can chime in with a way to keep this within the eager-loading paradigm.

class RunningTrack
   # Returns a Set (for fast lookups) of track IDs that have at least one achievement for the provided user
   def self.running_track_ids_for_achievements_for_user(user_id)
      Set.new Achievement.where(:user_id => user_id).pluck(:running_track_id)
   end
end

Then in your controller:

class RunningTracksController
   def index
      @running_tracks = RunningTracks.all
      @running_track_ids_for_achievements_for_user = RunningTrack.running_track_ids_for_achievements_for_user(current_user.id)
   end
end

And your view:

- unless @running_track_ids_with_achievements_for_user.include? track
   = button_to "done", running_track_mark_as_done_path(track)

Upvotes: 1

Related Questions