require 'nano/inflect' # The default Tag implementation. A tag attaches semantics to # a given object. #-- # FIXME: use index and char() instead of String. #++ class Tag property :name, String, :uniq => true property :count, Fixnum # An alias for count. alias_method :freq, :count alias_method :frequency, :count def initialize(name = nil) @name = name @count = 0 end #-- # FIXME: use update_properties here! #++ def tag(obj) send(obj.class.name.underscore.pluralize.to_sym) << obj @count += 1 save! end # Return all tagged objects from all categories. def tagged # TODO. end # Helper method def self.total_frequency(tags = Tag.all) tags.inject(1) { |total, t| total += t.count } end def to_s @name end end module Glue # Add tagging methods to the target class. # For more information on the algorithms used surf: # http://www.pui.ch/phred/archives/2005/04/tags-database-schemas.html # # === Example # # class Article # include Taggable # .. # end # # article.tag('navel', 'gmosx', 'nitro') # article.tags # article.tag_names # Article.find_with_tags('navel', 'gmosx') # Article.find_with_any_tag('name', 'gmosx') # # Tag.find_by_name('ruby').articles module Taggable # The tag string separator. setting :separator, :default => ' ', :doc => 'The tag string separator' include Og::EntityMixin many_to_many Tag # Add a tag for this object. def tag(the_tags, options = {}) options = { :clear => true }.merge(options) self.tags.clear if options[:clear] for name in Taggable.tags_to_names(the_tags) Tag.find_or_create_by_name(name).tag(self) unless self.tagged_with?(name) end end alias_method :tag!, :tag # Return the names of the tags. def tag_names tags.collect { |t| t.name } end # Return the tag string def tag_string(separator = Taggable.separator) tags.collect { |t| t.name }.join(separator) end # Checks to see if this object has been tagged # with +tag_name+. def tagged_with?(tag_name) tag_names.include?(tag_name) end alias_method :tagged_by?, :tagged_with? module ClassMethods # Find objects with all of the provided tags. # INTERSECTION (AND) def find_with_tags(*names) info = ogmanager.store.join_table_info(self, Tag) count = names.size names = names.map { |n| "'#{n}'" }.join(',') sql = %{ SELECT o.* FROM #{info[:first_table]} AS o, #{info[:second_table]} as t, #{info[:table]} as j WHERE o.oid = j.#{info[:first_key]} AND t.oid = j.#{info[:second_key]} AND (t.name in (#{names})) GROUP BY o.oid HAVING COUNT(o.oid) = #{count}; } return self.select(sql) end alias_method :find_with_tag, :find_with_tags # Find objects with any of the provided tags. # UNION (OR) def find_with_any_tag(*names) info = ogmanager.store.join_table_info(self, Tag) count = names.size names = names.map { |n| "'#{n}'" }.join(',') sql = %{ SELECT o.* FROM #{info[:first_table]} AS o, #{info[:second_table]} as t, #{info[:table]} as j WHERE o.oid = j.#{info[:first_key]} AND t.oid = j.#{info[:second_key]} AND (t.name in (#{names})) GROUP BY o.oid } return self.select(sql) end end def self.included(base) Tag.module_eval do many_to_many base end base.extend(ClassMethods) #-- # FIXME: Og should handle this automatically. #++ base.send :include, Aspects base.before 'tags.clear', :on => [:og_delete] end # Helper. def self.tags_to_names(the_tags, separator = Taggable.separator) if the_tags.is_a? Array names = the_tags elsif the_tags.is_a? String names = the_tags.split(separator) end names = names.flatten.uniq.compact return names end end end # * George Moschovitis