Rails : Ransack : Sorting data by ratings

Its not quite easily graspable to sort your search results by Ratings if you have separate Ratings table and there is an intermediate table called Job or Similar. Example Model schema is below for reference.

class Garage < ApplicationRecord
  has_many :jobs, through: :quote_requests
  has_many :ratings, through: :jobs
end
class Job < ApplicationRecord
  belongs_to :quote_request
  belongs_to :garage
  has_one :rating
end
# == Schema Information
#
# Table name: ratings
#
#  id         :integer          not null, primary key
#  star_count :integer
#  job_id     :integer
#  user_id    :integer
#  metadata   :jsonb
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class Rating < ApplicationRecord
  belongs_to :job
  belongs_to :reviewer, class_name: :VehicleOwner, foreign_key: :user_id
end

I suppose, you have got the idea about the relationship amount the Models.

Solution

I found somewhere that I need to override Ransack to support custom sorting scopes like

# in config/initializers/ransack_override.rb
# A custom initializer that enables sorting via custom scopes in Ransack (like the same feature in MetaSearch)

module Ransack
  module Adapters
    module ActiveRecord
      class Context < ::Ransack::Context

        # Allows for sorting by custom scopes
        #
        #
        # Define your custom scopes in your model, e. g. sort_by_title_asc and sort_by_title_desc
        # (The scopes would sort by some calculated column or a column added via some crazy join, etc.)
        #
        # In your sort links refer to the scopes like to standard fields, e. g.
        #   <%= sort_link(@q, :title, 'Crazy calculated title') %>
        def evaluate(search, opts = {})
          viz = Visitor.new
          relation = @object.where(viz.accept(search.base))
          if search.sorts.any?
            custom_scopes = search.sorts.select do |s|
              custom_scope_name = :"sort_by_#{s.name}_#{s.dir}"
              relation.respond_to?(custom_scope_name)
            end
            attribute_scopes = search.sorts - custom_scopes

            relation = relation.except(:order)

            custom_scopes.each do |s|
              custom_scope_name = :"sort_by_#{s.name}_#{s.dir}"
              relation = relation.public_send(custom_scope_name)
            end

            relation = relation.reorder(viz.accept(attribute_scopes)) if attribute_scopes.any?
          end
          opts[:distinct] ? relation.distinct : relation
        end
      end
    end
  end
end

In views

# This is slim template | index.slim
button.btn.btn-default[type="button"]
  = sort_link @q, :ratings do
    i.fa.fa-star.gold
    |  Rating

In Model [Garage]

# models/garage.rb
scope :sort_by_ratings_asc,  -> { joins(:ratings).group('id').order('AVG(ratings.star_count)')}
scope :sort_by_ratings_desc, -> { joins(:ratings).group('id').order('AVG(ratings.star_count) desc')}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s