This project is archived and is in readonly mode.

#82 ✓invalid
Daniel Lunde

Iterate over :has_many array with the join (:through) model

Reported by Daniel Lunde | May 1st, 2008 @ 05:28 PM

It is often the case when I iterate over an array of objects in a has_many_through association that I want access to both the :has_many objects and the :through objects. This patch makes that easier.

Example:

class User < ActiveRecord::Base
   has_many :memberships
   has_many :groups, :through => :memberships
 end

user.groups.each_with_join do |group, membership|
  puts "#{group.name}, #{membership.status}"
end

This also works regardless of whether the :through association is joined by a :has_many or :belongs_to.

Comments and changes to this ticket

  • Michael Koziarski

    Michael Koziarski May 1st, 2008 @ 09:10 PM

    • State changed from “new” to “invalid”

    This should really just be:

    user.memberships.each do |membership|

    puts membership.status, membership.group.name

    end

    So I can't the need for this feature.

  • Daniel Lunde

    Daniel Lunde May 1st, 2008 @ 09:22 PM

    What about the cases where you are sorting by groups

    class User < ActiveRecord::Base
       has_many :memberships
       has_many :groups, :through => :memberships, :order => "groups.name"
     end
    
    user.groups.each_with_join do |group, membership|
      puts "#{group.name}, #{membership.status}"
    end
    

    By only doing user.memberships, there's no knowledge of the groups to order it correctly.

    There's other instances where it makes sense to iterate over the groups rather then the memberships.

  • Pratik

    Pratik May 1st, 2008 @ 10:33 PM

    Why can't you just do the following ?

    user = User.find(:first, :include => { :groups => :membership })
    user.groups.each do |group|
      puts "#{group.name}, #{group.membership.status}"
    end
    
  • Daniel Lunde

    Daniel Lunde May 2nd, 2008 @ 04:14 PM

    Pratik:

    It seems like the obvious thing to do, but there's a problem with it. Let me give a more complete example.

    >> pp frank.groups
    [#<Group id: 2, name: "Swim Team">]
    >> pp frank.memberships
    [#<Membership id: 4, user_id: 2, group_id: 2, status: "Starter">]
    
    >> pp john.groups
    [#<Group id: 2, name: "Swim Team">]
    >> pp john.memberships
    [#<Membership id: 6, user_id: 3, group_id: 2, status: "Finisher">]
    
    # Note, I'm using memberships instead of membership since the Group class declares "has_many :memberships"
    >> frank = User.find(2, :include => { :groups => :memberships })
    >> frank.groups.each do |group|
    ?> pp group
    >> pp group.memberships
    >> end
    #<Group id: 2, name: "Swim Team">
    [#<Membership id: 4, user_id: 2, group_id: 2, status: "Starter">, #<Membership id: 6, user_id: 3, group_id: 2, status: "Finisher">]
    

    So when iterating over frank's groups, it returned not only frank's membership, but all memberships from that group. Which is incorrect.

    With my patch, you only get the correct membership record that matches both the user and the group, plus you get it as a record that's not wrapped in an array.

    And in those instances where someone might have multiple memberships to a single group, it does give it back as an array of just those memberships.

  • Daniel Lunde

    Daniel Lunde May 2nd, 2008 @ 04:42 PM

    Here's a real world example where I've needed to use each_with_join:

    class Feature < ActiveRecord::Base
      has_many :featured_items, :dependent => :destroy, :order => 'featured_items.position'
    
      has_many :featured_users,
               :source => :featureable,
               :through => :featured_items,
               :source_type => 'User',
               :order => 'featured_items.position'
    
      has_many :featured_media,
               :source => :featureable,
               :through => :featured_items, 
               :source_type => 'Medium',
               :order => 'featured_items.position'
    end
    
    # in view
    <% feature = Feature.find_by_group("users", :include => :featured_users, :order => 'featured_items.position') %>
    <% feature.featured_users.each_with_join do |user, featured_item| %>
      <%= image_tag(featured_item.photo.public_filename) %><br/>
      <%= user.name %>
    <% end %>
    

    In this case I couldn't do:

    feature.featured_items do |featured_item|
      featured_item.featured_user.name # featured_user is not a valid method
    end
    

    Wouldn't you say that using each_with_join in this example would be easier then alternative methods?

  • Michael Koziarski

    Michael Koziarski May 4th, 2008 @ 01:25 AM

    I certainly can see cases where this is required, but I'm just not convinced that it justifies a new method for us to have to maintain on an ongoing basis.

    If there's a join model, it should be exposed.

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

<h2 style="font-size: 14px">Tickets have moved to Github</h2>

The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>

People watching this ticket

Attachments

Pages