views:

135

answers:

1

This is my model:

class Tag < ActiveRecord::Base
  # id, name
  has_many :taggings
end

class Tagging < ActiveRecord::Base
  # id, tag_id, owner_id, target_type, target_id
  belongs_to :tag
  belongs_to :owner, :class_name => 'User'
  belongs_to :target, :polymorphic => true
  validates_uniqueness_of :tag_id, :scope => [ :target_id, :target_type, :owner_id ]
end

class Asset < ActiveRecord::Base
  # id, owner_id, title, type, etc
  belongs_to :owner, :class_name => 'User'
  has_many :taggings, :as => :target
  has_many :taggers, :through => :taggings, :source => :owner, :uniq => true
  has_many :tags, :through => :taggings, :uniq => true
end

class User < ActiveRecord::Base
  # id, name, email, etc
  has_many :assets, :foreign_key => 'owner_id'
  has_many :my_taggings, :class_name => 'Tagging', :foreign_key => 'owner_id'
  has_many :my_tags, :through => :my_taggings, :source => :tag, :uniq => true
  has_many :taggings, :as => :target
  has_many :taggers, :through => :taggings, :source => :owner, :uniq => true
  has_many :tags, :through => :taggings, :uniq => true
end

All of the relations are working but I have an additional requirement that I can't find the solution for:

consider this relation in the Asset class

has_many :tags, :through => :taggings, :uniq => true

calling Asset.find( :first ).tags returns an array of Tags as expected but I need for each Tag to contain a count attribute indicating how many times the row would have appeared if :uniq => true was not specified.

eg. more than one User could apply the same Tag to an Asset. I'd like to display the tag name plus the number of users that applied it.

+3  A: 

This should do exactly what you want.

has_many :tags_with_count, :source => :tag, :through => :taggings, 
  :group => "tags.id", :joins => :taggings,
  :select = "tags.*, COUNT('taggings.id') AS frequency"

In terms of rows returned :group => :id will return the same set as :uniq => true, but it will also allow you to perform the calculations you want. This statement is more labour intensive than :uniq => true, so I've given it a different name allowing you to choose whether to fetch the unique tags with their grouped counts, or just the list of unique tags.

The above statement will add the frequency attribute to the records returned. Through the magic of method_missing, you can access that with @tag.frequency.

Usage:

@tags = @asset.tags_with_count
@tags.each{|tag| puts [tag.id, tag.name. tag.frequency].join "\t"}

Will print the id, name, and number of occurrences of each tag for @asset.

EmFi
Nice. Lots of good info there!
Noel Walters
Doesn't quite work. I had to drop the :joins => :taggings and add a :source => :tag. The following works exactly as required -- has_many :tags_with_count, :through => :taggings, :source => :tag, :group => "tags.id", :select => "tags.*, COUNT(\"taggings.id\") AS frequency" ..... I wouldn't have found this without your help So I'm accepting your answer anyway. would be nice of you could edit your answer though.
Noel Walters
Sorry about that. I should've caught the nested quotes, and need for a source option. The solution ahs been edited to fix those minor errors.
EmFi