Rails: includes with polymorphic association

前端 未结 6 653
暗喜
暗喜 2020-12-24 11:55

I read this interesting article about Using Polymorphism to Make a Better Activity Feed in Rails.

We end up with something like

class Activity < A         


        
相关标签:
6条回答
  • 2020-12-24 12:07

    This will hopefully be fixed in rails 5.0. There is already an issue and a pull request for it.

    https://github.com/rails/rails/pull/17479

    https://github.com/rails/rails/issues/8005

    I have forked rails and applied the patch to 4.2-stable and it works for me. Feel free to use my fork, even though I cannot guarantee to sync with upstream on a regular basis.

    https://github.com/ttosch/rails/tree/4-2-stable

    0 讨论(0)
  • 2020-12-24 12:10

    Edit 2: I'm now using rails 4.2 and eager loading polymorphism is now a feature :)

    Edit: This seemed to work in the console, but for some reason, my suggestion of use with the partials below still generates N+1 Query Stack warnings with the bullet gem. I need to investigate...

    Ok, I found the solution ([edit] or did I ?), but it assumes that you know all subjects types.

    class Activity < ActiveRecord::Base
      belongs_to :subject, polymorphic: true
    
      belongs_to :event, -> { includes(:activities).where(activities: { subject_type: 'Event' }) }, foreign_key: :subject_id
      belongs_to :image, -> { includes(:activities).where(activities: { subject_type: 'Image' }) }, foreign_key: :subject_id
    end
    

    And now you can do

    Activity.includes(:part, event: :guests, image: :tags).order(created_at: :desc).limit(10)
    

    But for eager loading to work, you must use for example

    activity.event.guests.first
    

    and not

    activity.part.guests.first
    

    So you can probably define a method to use instead of subject

    def eager_loaded_subject
      public_send(subject.class.to_s.underscore)
    end
    

    So now you can have a view with

    render partial: :subject, collection: activity
    

    A partial with

    # _activity.html.erb
    render :partial => 'activities/' + activity.subject_type.underscore, object: activity.eager_loaded_subject
    

    And two (dummy) partials

    # _event.html.erb
    <p><%= event.guests.map(&:name).join(', ') %></p>
    
    # _image.html.erb
    <p><%= image.tags.first.map(&:name).join(', ') %></p>
    
    0 讨论(0)
  • 2020-12-24 12:11

    Does this generate a valid SQL query or does it fail because events can't be JOINed with tags and images can't be JOINed with guests?

    class Activity < ActiveRecord::Base
      self.per_page = 10
    
      def self.feed
        includes(subject: [:guests, :tags]).order(created_at: :desc)
      end
    end
    
    # in the controller
    
    Activity.feed.paginate(page: params[:page])
    

    This would use will_paginate.

    0 讨论(0)
  • 2020-12-24 12:13

    After I read Eager-loading Polymorphic Relationships in Rails article I ended up with the following solution:

    class ActivitiesController < ApplicationController
      def index
        activities = current_user.activities.page(:page)
    
        @activities = Activities::PreloadForIndex.new(activities).run
      end
    end
    
    class Activities::PreloadForIndex
      def initialize(activities)
        @activities = activities
      end
    
      def run
        preload_for event(activities), subject: :guests
        preload_for image(activities), subject: :tags
        activities
      end
    
      private
    
      def preload_for(activities, associations)
        ActiveRecord::Associations::Preloader.new.preload(activities, associations)
      end
    
      def event(activities)
        activities.select &:event?
      end
    
      def image(activities)
        activities.select &:image?
      end
    end
    
    0 讨论(0)
  • 2020-12-24 12:17

    I would suggest adding the polymorphic association to your Event and Guest models. polymorphic doc

    class Event < ActiveRecord::Base
      has_many :guests
      has_many :subjects
      after_create :create_activities
    end
    
    class Image < ActiveRecord::Base
      has_many :tags
      has_many :subjects
      after_create :create_activities
    end
    

    and then try doing

    Activity.includes(:subject => [:event, :guest]).order(created_at: :desc).limit(20)
    
    0 讨论(0)
  • 2020-12-24 12:28
    image_activities = Activity.where(:subject_type => 'Image').includes(:subject => :tags).order(created_at: :desc).limit(20)
    event_activities = Activity.where(:subject_type => 'Event').includes(:subject => :guests).order(created_at: :desc).limit(20)
    activities = (image_activities + event_activities).sort_by(&:created_at).reverse.first(20)
    
    0 讨论(0)
提交回复
热议问题