Sort by object attribute

Let me start by saying that my ultimate goal is to have a listing of items grouped by release date (using distance_of_time_in_words) and sorted in chronological order.

This is what I've done so far and it's not really working:
1) I have a model that has a release_date attribute
2) In the controller, converted the release_date value to distance_of_time_in_words
3) In the controller grouped all the objects with the same distance_of_time_in_words values together using group_by
4) In the view used model_name.keys.sort but what I get is an alphabetical sorting of the distance_of_time_in_words values (i.e. "2 Months", "5 Days", "6 Days", "about a month"). They do not sort chronologically.

So now my question is, how do I do a sort by an attribute of a value in a hash? What I want to do is sort by the model_name.release_date value. How do I structure the sort call to do that?
LVL 10
victornegriAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Andrew DoadesIT TechnicianCommented:
"So now my question is, how do I do a sort by an attribute of a value in a hash?"

The attached code might not be 100% what you want, but I think it should give a clear idea.
hash = Hash.new
hash['al'] = 'alexander'
hash['barney'] = 'rubble'
hash['fred'] = 'flinstone'
hash['john'] = 'doe'
 
# sort the hash by value, and print the results in the sorted order.
hash.sort{|a,b| a[1]<=>b[1]}.each { |elem|
  puts "#{elem[1]}, #{elem[0]}"
}
 
#this would be the result
alexander, al
doe, john
flinstone, fred
rubble, barney

Open in new window

0
victornegriAuthor Commented:
But how about if the value is an object and I want to sort by one of that object's attributes?

myObject1 = myClass.new
myObject1.someAttribute = "Test Value"
myObject1.save
 
myObject2 = myClass.new
myObject2.someAttribute = "Test Value 2"
myObject2.save
 
myHash = Hash.new
hash['key1'] = myObject1
hash['key2'] = myObject2

Open in new window

0
Andrew DoadesIT TechnicianCommented:
would it not be like ?
myHash = Hash.new
hash['key1'] = myObject1.someAttribute
hash['key2'] = myObject2.someAttribute

Open in new window

0
Cloud Class® Course: CompTIA Cloud+

The CompTIA Cloud+ Basic training course will teach you about cloud concepts and models, data storage, networking, and network infrastructure.

victornegriAuthor Commented:
No. My hash has the object as the value. That's what group_by does.
0
Andrew DoadesIT TechnicianCommented:
Would the below not work strait off?

I am looking at this page here

http://www.crummy.com/writing/RubyCookbook/test_results/11049.html
myObject1 = myClass.new
myObject1.someAttribute = "Test Value"
myObject1.save
 
myObject2 = myClass.new
myObject2.someAttribute = "Test Value 2"
myObject2.save
 
myHash = Hash.new
hash['key1'] = myObject1
hash['key2'] = myObject2

Open in new window

0
victornegriAuthor Commented:
My question is about sorting a hash using the object attribute, not about creating a hash (unless I'm misunderstanding your comment).
0
Andrew DoadesIT TechnicianCommented:
I think that we are both getting a little confused, no offence!!

Hopefully the below code should create the objects, then hash the entered value and sore the hashed values.

I personally can't see why it would not work, unless I am missing something?!
myObject1 = myClass.new
myObject1.someAttribute = "Test Value"
myObject1.save
 
myObject2 = myClass.new
myObject2.someAttribute = "Test Value 2"
myObject2.save
 
myHash = Hash.new
hash['key1'] = myObject1.someAttribute
hash['key2'] = myObject2.someAttribute
 
myHash.sort{|a,b| a[1]<=>b[1]}.each { |elem|
  puts "#{elem[1]}, #{elem[0]}"
}

Open in new window

0
victornegriAuthor Commented:
FYI, the code I typed above is not the real code. It was just something I typed to illustrate my point. I tried what you suggested earlier and it would not work. I don't know how to specify to sort on the attribute and not on the primary key or anything like that. I tried this too but got an error:

myHash.sort{|a,b| a[1].myAttribute<=>b[1].myAttribute}.each { |elem|
  puts "#{elem[1]}, #{elem[0]}"
}


Here's some real code (I'm aware that I'm sorting by keys right now. I want to switch it to value but I keep getting an error):

In the controller:
 
@items = Category.all_category_items.group_by {|i| distance_of_time_in_words(Time.now, i.release_date)}
 
In the view:
 
<% @items.keys.sort.each do |release_date| %>
<h2><%= release_date.titleize %></h2>
<ul>
	
	<% for item in @items[release_date] %>
		<li><%= Category.find(item.category_id).name + ": " + link_to(item.name, item) + ": " +  item.release_date %></li>
	<% end %>
</ul>
<% end %>

Open in new window

0
Andrew DoadesIT TechnicianCommented:
OK done some reading and I think this is what you want?!

Look at this part

hash.sort{|a,b| a[1]<=>b[1]}.each { |elem|
  puts "#{elem[1]}, #{elem[0]}"
}

then change to this

hash.sort{|a,b| b[1]<=>a[1]}.each { |elem|
  puts "#{elem[1]}, #{elem[0]}"
}

This should reverse the order that the results are ordered, is this what you want?

Of course change this to your code, etc. but keep the a and b bits in the same "layout"
0
victornegriAuthor Commented:
Nope. Don't want to reverse the order. I want to sort based on an attribute of the object.
0
karfiCommented:
What is the error message you got when you tried this:

myHash.sort{|a,b| a[1].myAttribute<=>b[1].myAttribute}.each { |elem|
  puts "#{elem[1]}, #{elem[0]}"
}

What is the type of the myAttribute member?
Maybe in the class of myAttribute the <=> operator is missing.
class MyAttr
 
  attr_accessor :something
 
  # ....
 
  # maybe you need this?
  def <=>(other)
    return something<=>other.something
  end
end
 
class MyClass
  attr_accessor :myAttribute
 
  # ....
 
  def initialize()
     myAttribute = MyAttr.new()
  end
end

Open in new window

0
victornegriAuthor Commented:
I get an "undefined method 'myAttribute' in Array..." error when I try the above statement.

myAttribute is of the type 'date'

How would I add the <=> operator to the myAttribute member?

BTW, myAttribute is part of the example code we were working with. Can you look at the real code I posted above to see what I need to do there? myAttribute is equivalent to release_date in the real code.
0
karfiCommented:
OK, I think I see the problem:

the group_by method returns a Hash object, where the keys are what distance_of_time_in_words calls return, and the values are Arrays which are containing the objects with the same "distance_of_time_in_words" values:

"6 Days" => [ obj9, obj7],
"2 Months" => [ obj1, obj6, obj2, ...],
"5 Days" => [ obj2, obj3]

So you can use the first object of the array for sorting:
model_name.sort { |a, b|
 a[1][0].release_date<=>b[1][0].release_date
}.each { |elem|
 puts "#{elem[0]} => #{elem[1]}"
}

Open in new window

0
victornegriAuthor Commented:
So this is what I changed it to and I get this error:
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.each

<% @items.sort{|a,b| a[1][0].release_date<=>b[1][0].release_date}.each { |release_date| %>
<h2><%= release_date%></h2>
<ul>
	
	<% for item in @items[release_date] %>
		<li><%= Category.find(item.category_id).name + ": " + link_to(item.name, item) + ": " + distance_of_time_in_words(Time.now, item.release_date) %></li>
	<% end %>
</ul>
<% } %>

Open in new window

0
victornegriAuthor Commented:
Do I have to tell it somehow to sort by the value of the hash and not by the key?
0
karfiCommented:
when iterating over a Hash (@items here), you get the key, value pair in the each block. (Or an Array with two items (key and value) when using only one placeholder.

use each_key in your code or see the following:
<% @items.sort{|a,b| a[1][0].release_date<=>b[1][0].release_date}.each { |release_date, data_array| %>
<h2><%= release_date%></h2>
<ul>
        
        <% for item in data_array %>
                <li><%= Category.find(item.category_id).name + ": " + link_to(item.name, item) + ": " + distance_of_time_in_words(Time.now, item.release_date) %></li>
        <% end %>
</ul>
<% } %>

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
victornegriAuthor Commented:
Awesome! Not only did you solve my problem but you helped me understand how sort and group_by work! Thanks!
0
victornegriAuthor Commented:
Thanks!
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Ruby

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.