Ruby on Rails: Sorting collection of objects

I have a method that will provide an array of model object. Some of those model's attributes are easy to sort by using some help from SQL. I mean, using .find(:condition => {})

The problem is some of the other attributes isn't. It has to be calculated, modified, or did something else before showing. Then, I found the way to sort it using collection sorting. Which is, collection.sort{|a,b| a.something_to_sort <=> b.something_to_sort}

OK! That's works. But the problem is, is it possible to make that something_to_sort part to become a dynamic variable? For example, I want to take a parameter from the UI and assign it to that something_to_sort like following,

HTML

<select name="sort">
    <option value="name">Name</option>
    <option value="age">Age</option>
    <option value="activity.category">Activity</option>
    <option value="tax.calculate_personal_income_tax">Income Tax</option>
    <option value="tax.calculate_withholding_tax">Withholding Tax</option>
    <option value="doctor.name">Doctor's Name</option>
</select>

Rails

params[:sort] ||= "Age"
@people = Person.find(:all)
@people.sort{|a,b| a.params[:sort] <=> b.params[:sort]}    #Note that this is an incorrect syntax

Is there any way to do this by not having to write a sort block for each of sorting option?

Additional #1

I've just tried this and it was working for some sorting option

@people.sort{|a,b| a.send(params[:sort]) <=> b.send(params[:sort])}

This works for name and age as they are people's attribute (and it works for peopls's method too). But for the association's such as tax.calculate_personal_income_tax, it's not.

So, my colleague told me to create new people's method, calculate_personal_income_tax and then change the option value from tax.calculate_personal_income_tax to calculate_personal_income_tax so that the send method will be able to find this method ...

def calculate_personal_income_tax
    tax.calculate_personal_income_tax
end

But, this is not really what I want. Because I believe that there must be a way to access this association method without having to define a new model method. If I choose to do this and one day people model become larger, more attribute, more information to sort and display. Then, there will be a lot of method like this.

The other reason is, if I have to define a new method, that method should have to do some logic, calculation or else, not just retrieve a data from other model.

Additional #2

Finally, I've just found the way to access other model attribute by adding args to the find method, :join and :select.

@people = Person.find(:all, :select => "persons.*, tax.tax_rate AS tax_rate", :joins => :tax, :condition => {some conditions})

The result of that will be a table of person joining with tax. Then, I use the send(params[:sort]) thing to help me sorting the attribute that I want.

Answers


@people.sort {|a, b| a.send(params[:sort]) <=> b.send(params[:sort])}

params[:sort] ||= "age"
method_chain = params[:sort].split(".")
@people = Person.find(:all)
@sorted_people = 
  @people.sort_by do |person|
    method_chain.inject(person) do |memo,method|
      memo.send(method)
    end
  end

This assumes that the values passed in params[:sort] are always valid method names which can be chained together in the order specified. You'd want to keep in mind that anything can be passed in the params hash, so this would be very unsafe if exposed to untrusted users (e.g. params[:sort] = "destroy". Oops.)

Also, if you can do this in SQL instead you'll get better performance.

In fact, the more I look at this, the more it seems like a bad idea.


Need Your Help

Adapter in Android Fragment

android gridview android-fragments android-adapter

I'm new to Android and I'm trying to create a GridView inside a Fragment. But the line

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.