Tyler
Tyler

Reputation: 698

Sorting Based on a has_many relationship

Project is located at

http://code-dojo.herokuapp.com/

and

https://github.com/TylerSangster/Code-Dojo

I have a resource review site with the following models.

class Resource < ActiveRecord::Base

  has_many :reviews


class Review < ActiveRecord::Base

  belongs_to :resource

I have added resource sorting by name, created at, and cost. I would also like to sort by the highest reviewed resource.

How can I create a sort by review.score ?

EDIT resource controller

  def index
    @resources = Resource.where(status: true).includes([:providers, :subjects, :formats, :reviews])
    @resources = @resources.tagged_with(params[:tag]) if params[:tag]
    @resources = @resources.order(sort_column + " " + sort_direction)
    @resources = @resources.paginate(:page => params[:page], :per_page => 10)


    respond_with @resources
  end

private methods

  def sort_column
    Resource.column_names.include?(params[:sort]) ? params[:sort] : "name"
  end

  def sort_direction
    %w[asc desc].include?(params[:direction]) ? params[:direction] : "asc"
  end

application.rb

  def sortable(column, title = nil)
    title ||= column.titleize
    css_class = column == sort_column ? "current #{sort_direction}" : nil
    direction = column == sort_column && sort_direction == "asc" ? "desc" : "asc"
    link_to title, {:sort => column, :direction => direction}, {:class => css_class}
  end

index.html.erb

  • <%= sortable "name" %>
  •   <li><div class="btn btn-default"><%= sortable "created_at", "Most Recent" %></div></li>
      <li><%= link_to "Highest Rated", resources_path, class:"btn btn-default" %></li>
      <li><div class="btn btn-default"><%= sortable "cost" %></div></li>
    

    Upvotes: 1

    Views: 571

    Answers (2)

    lurker
    lurker

    Reputation: 58324

    Since you want to sort a set of resources based upon their review scores, and there are many review scores per resource, then a means of evaluating them needs to be determined. To do this, one could think about what the scores mean and why they would be sorted for the user.

    A logical choice might be to sum the review scores of a resource to create an aggregate score for the resource. It would work like this (assuming @resources is a collection of Resource active records):

    @resources.sort_by( &:aggregate_score )
    

    The highest rated would be the last element:

    highest_score_resource = @resources.sort_by( &:aggregate_score ).last
    

    In the Resource model, define aggregate_score:

    def aggregate_score
      self.reviews.select { |r| r.score }.sum(&:score)
    end
    

    If you know the scores are never nil, then you could do:

    def aggregate_score
      self.reviews.sum(&:score)
    end
    

    If you decide that there's some other means you want to use to compute an aggregate score for the resource, you change the algorithm in aggregate_score.

    Upvotes: 2

    Dmitry Matveev
    Dmitry Matveev

    Reputation: 5376

    You obviously need order method of ActiveRecord class

    Review.order("score")
    

    There is an article that might help you too.

    Hope this helps :)

    EDIT:

    class Resource < ActiveRecord::Base
      scope :ordered, :order => "some_col DESC"
    end
    
    class Review < ActiveRecord::Base
      has_many :resources
    end
    
    review.resources # uses the default ordering
    review.resources.ordered # uses the "some_col DESC" ordering
    

    Upvotes: 0

    Related Questions