Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can one obtain a row count from has_many :through relations with :uniq => true

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.

like image 537
Noel Walters Avatar asked Feb 04 '10 11:02

Noel Walters


1 Answers

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.

like image 102
EmFi Avatar answered Oct 20 '22 17:10

EmFi