#29
May 09, 2007

group_by Month

Learn how to use the very useful group_by method to group an array by anything you want! In this episode I group an array of tasks by month then sort it properly.
Download (30.3 MB, 5:11)
alternative download for iPod & Apple TV (10.7 MB, 5:11)
# tasks_controller.rb
def index
  @tasks = Task.find(:all, :order => 'due_at, id', :limit => 50)
  @task_months = @tasks.group_by { |t| t.due_at.beginning_of_month }
end
<!-- tasks/index.rhtml -->
<% @task_months.sort.each do |month, tasks| %>
  <h2><%= month.strftime('%B') %></h2>
  <% for task in tasks %>
    <div class="task">
      <strong><%= task.name %></strong>
      due on <%= task.due_at.to_date.to_s(:long) %>
    </div>
  <% end %>
<% end %>

32 comments

chineseGuy May 09, 2007 at 01:05

haha,I learn some new tip,thanks!~
哈哈,我又学到新技巧啦,谢谢!~


eTrueke May 09, 2007 at 01:42

Really a good tip!

I love your blog. Congratulations.


weskycn May 09, 2007 at 01:55

我除了尊敬,没有别的说了!
chineseguy,够意思。
在中国好像上这个网站挺快的啊,ror,love
you!


Oskar May 09, 2007 at 02:59

Great episode as always


Jake May 09, 2007 at 06:40

Another good tip! Thanks!


Kino May 09, 2007 at 07:04

Thanks!
Your make good screencasts!


Dave May 09, 2007 at 07:50

Great stuff. Gonna have to watch this one a few times :-)

As always, awesome job!


vigosan May 09, 2007 at 09:09

Wooow, I have no words !!!


Carl Porth May 09, 2007 at 10:40

I don't think you need that hack at the end, you should be able to do:

<% @task_months.sort { |a,b| a[0] <=> b[0] }.each do |month, tasks|

When you call sort on a hash it will just convert it to an array and do what you originally wanted.


Ryan Bates May 09, 2007 at 10:45

Hi Carl, I knew there must be another way to do that. Thanks for the tip!

I'm not sure if it's any cleaner than the other hack, but I can imagine extracting this into a custom Hash method to keep the view nice and clean.


Carl Porth May 09, 2007 at 10:47

I like that idea, perhaps sort_by_keys would be appropriate.


Ryan Bates May 09, 2007 at 10:50

Just found this which I think is the best solution:

@task_months.sort_by(&:first).each do |month, tasks|

Edit: Actually, it looks like "sort" by itself automatically sorts by key:

@task_months.sort.each do |month, tasks|


Carl Porth May 09, 2007 at 10:57

Ah, much better. We should pair program more often.


Steven Soroka May 09, 2007 at 13:18

Instead of grouping by date.beginning_of_month, you could just group by date.month :)


Ryan Bates May 09, 2007 at 13:53

@Steven, the reason I chose beginning_of_month is because it allows you to easily format the month however you want in the view. You can use the short month name, full month name, include the year, etc. The "month" method just returns the month as an integer so you lose the year information.


Clayton May 09, 2007 at 19:06

The sound seemed a little "fuzzy" on this one... other than that I've learned ANOTHER new something about rails. Thanks!


jjk2 May 09, 2007 at 20:46

ryan, how r u able to make ur screencasts so high quality, yet file size so small? can this be achieved with formats other than .mov ? what program do you use ?


Ryan Bates May 09, 2007 at 20:58

@Clayton, hmm, not sure why it's fuzzy. It sounds okay here and I didn't do anything different. Oh well, hope it's not a regular occurrence.

@jjk2, I'm using the Animation compression setting in QuickTime. The secret is to set the keyframe rate way up (about 1500) and it should make decent size files.


Carlos May 10, 2007 at 15:48

I really love your blog. Keep your good job job! Congratulations!


jeff May 11, 2007 at 10:40

Hi I watch this railscast religiously. It has taught me a lot. I really appreciate the excellent explanations for why you are doing something not only in rails but with ruby in general. I just spent about three hours figuring this little thing out so I figured i'd post it.

My application calls for a day of the week select box. there is no real helper for this. Here is the code I use..

<select>
<% dates = Date.const_get :DAYS%>
<%= options_from_collection_for_select(dates.sort{|x,y| x.last<=>y.last},'last','first',@preference.autosendday) %>
</select>

hope this helps someone save a minute of there programming life


jeff May 11, 2007 at 10:51

even better

<%= options_from_collection_for_select((Date.const_get :DAYS).sort_by(&:first),'last','first',@preference.autosendday) %>


Slaptijack May 11, 2007 at 11:14

As always, good work.


james May 12, 2007 at 14:57

lovely technique. can't believe how simple it is when you know the trick! replaced a horrible, horrible hack job i'd written in my ignorance.

so .. any hints on how you'd paginate that page you generate?

thanks for the wonderful videos, they're really great.


Ryan Bates May 12, 2007 at 22:45

@james, good question about pagination. It's not too tricky, you just have to make sure you order them properly so the grouped items are close together. For example, in this case I would need to group them by due_at so the month names stick together. Then you would treat it like normal. The only problem is a page my split up a group, but I can't think of an easy way to resolve this problem.

If you need to group items already, pagination may not be the best solution. Instead consider creating some sort of index of groups where clicking one will take you to the items in that group. In this case I would make a list of months, and clicking one would reveal the tasks for that month. This way the person doesn't have to deal with guessing which page has a certain month on it.


Marcel May 20, 2007 at 14:36

Awesome tip! I didn't know this already existed, so I implemented it all by myself ;)
Thanks for this video and your great blog.
Keep it up it's great stuff!


untaldouglas Jul 12, 2007 at 18:54

Hola !
Thanks for your screencasts, they rock !

Would you please consider to do an episode that include geting totals by groups, in a way that a parameter for filtering could be pass ...
Like for producing something like :

Month No of episodes Sum of Donations($)
January 23 3,500
Febreary 5 1,000
March 0 500
.......
Total 999 9,999.99

Gracias !


Johan Sep 13, 2007 at 03:11

Great podcasts!

How would you solve the problem if we wanted to group by year also. Like:

2006
Jan
Dec
2010
Jan
Feb

Where should i put the group_by year


Ryan Bates Sep 13, 2007 at 09:48

@Johan, try something like this:

http://pastie.caboo.se/96864

That is really ugly in the view, so what I'd probably do is move this into a helper + partial.


Peter Roome Dec 05, 2007 at 02:28

This is a very handy little tip, thanks. Keep up the good work with the casts too.

Just one question: How would you go about producing a list of the months so that every month of the year was listed whether it has anything to group under it or not? (Does that make sense?!?!)

Cheers


William Monk Dec 06, 2007 at 10:49

how could i go about reversing the whole thing, so if i had posts from Descember and November, it would show:
December
...
..
November
...
..
October
etc?


cover Dec 25, 2007 at 01:46

@William Monk

You just need a reverse :)

<% @task_months.keys.sort.reverse.each do |month| %>


iratik Jul 10, 2008 at 04:26

I don't see where anyone else has mentioned this but
@task_months.keys.sort would just sort the months in alphabetical order. Maybe you need something that looks at the arrays pointed to by the month names and looks at the first element of each array (a time object) to see if its newer or older than the next. Then loop through the resulting array of ordered value sets and lookup the correct index for the section title.

@task_months.values.sort{|a,b| a[0]<=>b[0]}.each{|tval|
@task_months.index(tval) ...

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(required)

subscribe:
sponsored by:
if you want to help:
required:
Get Quicktime Player