Topic: DRY collection of a find result

Hello,

I want to extract four instance variables to make them available to a view. My query:

@results = Item.find(:all, :conditions ...)

Now, each item in @results belongs to a category and in the view, they should be separated for better display. So is it better to write 4 find lines or only one as above and collect them ? if you suggest the second way, please help me in coding it!

Thanks in advance

Re: DRY collection of a find result

I suggest the second way. Rails extends the core ruby library module Enumerable with a method called group_by. Your code would simply be:

@results = Item.find(:all, :conditions => ...).group_by(&:category)

and in your view:

<% @results.each do |category,items| %>
  <h1><%= category.name %></h1>
  render :partial => 'item', :collection => items
<% end %>

voila!

Re: DRY collection of a find result

Thanks very much fabio for the tip, i want just to precise that the 3rd line of the code below should be in a rails tag (<%= ... %>).

Since I'm extracting collections for displaying them in different select tags, i substituted the 3rd line by this one:

collection_select("group", "item_id" , items, "id", "name")

But i found also the render :collection very useful for many other purposes, Thanks again smile

Re: DRY collection of a find result

I'm fine getting different items listed using group_by. However, say I now wanna be able to sort within those groups? How can I update only the partial for the group I want to re-sort?

Re: DRY collection of a find result

You can sort the items before looping/rendering them:

<% @results.each do |category,items| %>
  <h1><%= category.name %></h1>
  <%= render :partial => 'item', :collection => items.sort_by(&:foo) %>
<% end %>

Railscasts - Free Ruby on Rails Screencasts

Re: DRY collection of a find result

Yes, however that will re-sort each collection right? What I'm looking to do is only resort one. eg. I have a list of projects grouped by division. Now I want to resort the projects from division X by some other attribute. But leave the other ones sorted as they already are. I've got them listed ok. But when I go to sort is where I'm looking for some guidance.

thanx for any pointers wink

Re: DRY collection of a find result

hardcoding the name or id of that one database-object would be a bad idea...

add a column to the model, and save in there either a boolean to differenciate Sort <-> no sort
or save the attribute name to sort by in it.

Last edited by Duplex (2007-10-10 19:16:30)

Re: DRY collection of a find result

Alternatively you can add acts_as_list to your item model and scope by category:

# item model
acts_as_list :scope => 'category'

This way each child item is ordered so you can rearrange them however you want and then sort by the "position" column.

Railscasts - Free Ruby on Rails Screencasts

Re: DRY collection of a find result

hmmm... al these suggestions look like they would still apply globally? Maybe I'm missing something or not being clear.

I have a model Projects that has a bunch of fields (including belonging to Division). I grab all the projects and group them by Division and list them in said groups. Each group is sorted by a default order I declare when I do the find/group_by. I'd *like* to be able to re-sort ONE of the groups by a different attribute than the default (say by a column header link).

I can do it where I re-sort all the groups , but I'd like to see how to only update one of em. Hopefully, that clears up what I'm tryin to do?

thanx again for all the feedback!

Re: DRY collection of a find result

I see, in that case Duplex's solution should work. Add a boolean column to the divisions table determining whether it should be sorted or not. For example:

# in migration
add_column :divisions, :sort_by_date, :boolean, :default => false, :null => false

I also recommend not using group_by but do a find with eager loading. Like this:

# in controller
@divisions = Division.find(:all, :include => :items) # you can add conditions here if you need to

# in view
<% for division in @divisions %>
  <%=h division.name %>
  <% for item in division.sorted_items %>
    <%=h item.name %>
  <% end %>
<% end %>

# in division model
def sorted_items
  if sort_by_date?
    items.sort_by(&:date)
  else
    items.sort_by(&:name)
  end
end


Hope that helps.

Railscasts - Free Ruby on Rails Screencasts