Ferrell427
Ferrell427

Reputation: 17

Rails, how to sort objects by an instance array

In a Ruby on Rails application, I have a class called Members with each object having an attribute called executive position that is one of the strings in this array

@executive = ['President', 'Vice President', 'Treasurer', 'Secretary', 'Director of Programs', 'Director of Strategic Partnerships', 'Director of Public Relations', 'Director of Publications', 'Director of Community Service', 'Director of Fundraising', 'Historian', 'Digital Administrator']

I want to sort them in this order, showing the president first, then Vice President etc.

Member.all.sort_by {|member| member.executive_position == @executive}

Upvotes: 1

Views: 471

Answers (3)

mu is too short
mu is too short

Reputation: 434665

I think you have the logic in the wrong place. You should be using an SQL CASE statement to do the sorting inside the database, that will make it easier to combine this sorting with other query components (such as subqueries and secondary sorting keys). For example, you could have a constant like this:

EXECUTIVES = [ 'President', ... ]

and then a scope like this in your model:

def self.order_by_rank
  q = connection.method(:quote)
  order([
    'case members.executive_position',
    *EXECUTIVES.length.each_with_index.map { |s, i| "when #{q[s]} then #{i}" },
    "else #{EXECUTIVES.length}",
    'end'
  ].join(' '))
end

With that you could say things like:

Member.order_by_rank
Member.where(...).order_by_rank.order(:name)

A straight forward modification to the order_by_rank scope would give you an in_this_order('President', 'Vice President', 'Treasurer', ...) scope:

def self.in_this_order(*titles)
  q = connection.method(:quote)
  order([
    'case members.executive_position',
    *titles.each_with_index.map { |s, i| "when #{q[s]} then #{i}" },
    "else #{titles.length}",
    'end'
  ].join(' '))
end

You could also put the titles and their order in a database table:

title            | pos
-----------------+----
'President'      | 1
'Vice President' | 2
...

and then JOIN to it and ORDER BY pos.

Upvotes: 0

rubyprince
rubyprince

Reputation: 17793

Member.all.sort_by { |member| @executive.index(member.executive_position) }

If this is used in other places also, I would go on further to add a method in the Member model like this:

# member.rb
class Member < ActiveRecord::Base
  ...
  EXECUTIVES = ['President', 'Vice President', 'Treasurer',
    'Secretary', 'Director of Programs', 'Director of Strategic Partnerships', 
    'Director of Public Relations', 'Director of Publications', 
    'Director of Community Service', 'Director of Fundraising', 
    'Historian', 'Digital Administrator']

  def position
    EXECUTIVES.index(executive_position)
  end

end

Then the code will be

Member.all.sort_by { |member| member.position }

or shorter

Member.all.sort_by(&:position)

Upvotes: 0

ndnenkov
ndnenkov

Reputation: 36101

Member.all.sort_by { |member| @executive.index(member.executive_position) }

Upvotes: 2

Related Questions