2

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.

2
  • So you cannot use SQL to order the results? Commented Dec 1, 2011 at 4:11
  • Yes, some of the displaying column can't be ordered by SQL statement(Or maybe, I don't know how to do it yet). Some of them are too complicate as it involving with a lot of related models Commented Dec 1, 2011 at 4:12

3 Answers 3

2
@people.sort {|a, b| a.send(params[:sort]) <=> b.send(params[:sort])}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks mate, I found that too. The problem is, it works for people's attribute or method. But not for it's association (Such as, tax.calculate_personal_income_tax) ... Anyway, Thanks again mate.
1
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.

Comments

1

I have a loot system that is doing this on a calculated value, that is based on a reference table which can change. As the value is calculated, it could come from anywhere.

My players/index.html.rb looks something like.

<h1>Players</h1>
<table>
  <tr>
    <th>Name</th>
    <%= generate_headings(params) %>
  </tr>

  <% players.each do |player| %>
    <tr>
      <td><%= player.name %></td>
      <% LootType.all.each do |loot_type| %>
        <td><%= player.loot_rate(loot_type.name) %></td>
      <% end %>
    <tr>
  <% end %>
</table>

The loot_rate function is calculating on two related tables (player.raids and player.items) as "player raids / loot of type + 1".

The generate_headings function is in players_helper.rb, and it looks like:

def generate_headings(params = {})
  sort = params[:sort]
  headings = ""
  LootType.all.each do |loot_type|
    heading_text = "#{loot_type.name} Rate"
    if (sort == loot_type.name)
      column_heading = "<th>#{heading_text}</th>"
    else
      heading_url = "/players?sort=#{loot_type.name}"
      column_heading = "<th>#{link_to heading_text, heading_url}</th>"
    end
    headings += column_heading
  end
  headings.html_safe
end

In my index action of players_controller.rb, I have:

def index
  sort = params[:sort]
  if !sort
    @players.sort! { |a,b| a.name <=> b.name }
  else
    @players.sort! { |a,b| b.loot_rate(sort) <=> a.loot_rate(sort) } # Reverse sort (for my purposes)
  end

  respond_to do |format|
    format.html
    format.json {render :json => @players }
  end
end

This means that when I add or remove "loot types" in my system, and the player list automagically allows users to sort by those types.

Long winded answer, but hope it helps.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.