user4440189
user4440189

Reputation:

Rails 4: will_paginate error when trying to Sort a Resource Based on a Calculated Value

I use Rails 4.1.0 I want to sort profiles based on completeness so for this i have this code in my profile model

def completeness
    percent = 10

    percent += 15 if self.useravatar.present?
    percent += 5 if self.summary.present?
    percent += 5 if self.profile_languages.present?
    percent += 10 if self.educations.present?
    return percent
end

def self.sorted_by_completeness
  Profile.all.sort_by(&:completeness).reverse
end

and this is my profiles index controller

  def index
    @profiles = Profile.paginate(page: params[:page], per_page: 10).sorted_by_completeness
  end 

But when I'm trying to access profile index page I'm getting this error undefined methodtotal_pages' for #` can someone tell me why i'm getting this error

Upvotes: 1

Views: 326

Answers (3)

SHS
SHS

Reputation: 7744

Two problems with your approach:

  1. In your view, you're probably using will_paginate(@projects) which fails because that method isn't for arrays.

  2. You're sorting the rows after they've been paginated. This means that however you sort them, the sorting will be applied to only one page at a time. Thus whatever solution you choose, you need to specify the order before you paginate the data.

A solution would be to obviously use pagination on your array. Something like:

require 'will_paginate/array' # to enable pagination on an array
@projects = Project.sorting_method.paginate(arguments)

However for a large number of projects, that will soon become impractical.

The best way IMO, to enable pagination, is to have a completeness field in your projects table that gets updated anytime it should. Then, you could very easily do the following:

@projects = Project.order("completeness DESC").paginate(arguments)

Another solution would be to use sub-queries in a SQL select statement to calculate each project's completeness. Something like:

lcq = "SELECT COUNT(*) FROM profile_languages WHERE
  (profile_languages.project_id = projects.id)"
ecq = "SELECT COUNT(*) FROM educations WHERE
  (educations.project_id = projects.id)"

@projects = \
  Project.select(
    "#{project.column_names.join(',')},
    SELECT(
      (CASE useravatar WHEN NULL THEN 0 ELSE 15 END) +
      (CASE summary WHEN NULL THEN 0 ELSE 5 END) +
      (CASE (#{lcq}) WHEN 0 THEN 0 ELSE 5 END) +
      (CASE (#{ecq}) WHEN 0 THEN 0 ELSE 10 END)
    ) AS completeness").
  order("completeness DESC").
  paginate(arguments)

With some debugging, that should work. I don't like it though. A bunch of code that is hard to read and sub-queries that might slow things down a lot.

Upvotes: 1

Rahul Singh
Rahul Singh

Reputation: 3427

sort_by will return Array collection

to test open rails console and type

Profile.all.sort_by(&:completeness).reverse.class

for will_paginate to work with Array you need to require 'will_paginate/array' so just change your profile_controller as

require 'will_paginate/array'
class ProfileController < ApplicationController
  def index
    @profiles = Profile.sorted_by_completeness.paginate(page: params[:page], per_page: 10)
  end 
end

Upvotes: 2

Ajay
Ajay

Reputation: 4251

 def index
  @profiles = Profile.sorted_by_completeness(params[:page])
 end

In your model file:

def self.sorted_by_completeness(current_page)
    Profile.all.sort_by(&:completeness).reverse.paginate(page: current_page)
end

TO define your per_page limit use below code:

 #app/config/initializers/will_paginate.rb 
  WillPaginate.per_page = 10

Else if you want to define it in model level:

  #profile.rb
   class Profile < ActiveRecord::Base
      self.per_page = 10
    #your other model level logic goes here.
    end

Upvotes: 0

Related Questions