path = File.expand_path(File.dirname(__FILE__)) $LOAD_PATH << path unless $LOAD_PATH.include?(path) require 'tag' require 'tagging' module IsTaggable class TagList < Set cattr_accessor :delimiter, :output_delimiter @@delimiter = ',' def initialize(list) list = list.split(@@delimiter) if list.is_a? String # Tag must be normalized here because we use it to find tags that need deleting. super list.map{ |t| Tag.normalize(t) }.reject(&:blank?) end def join(*args) to_a.join(*args) end def to_s join(@@output_delimiter || @@delimiter) end end module ActiveRecordExtension def is_taggable(*kinds) class_inheritable_accessor :tag_kinds self.tag_kinds = kinds.map(&:to_s).map(&:singularize) self.tag_kinds << :tag if kinds.empty? include IsTaggable::TaggableMethods end end module TaggableMethods def self.included(klass) klass.class_eval do include IsTaggable::TaggableMethods::InstanceMethods has_many :taggings, :as => :taggable, :dependent => :destroy has_many :tags, :through => :taggings after_save :save_tags tag_kinds.each do |k| define_method("#{k}_list") { get_tag_list(k) } define_method("#{k}_list=") { |new_list| set_tag_list(k, new_list) } define_method("#{k}_list_will_change!") { tag_list_will_change_for_kind!(k) } define_method("#{k}_list_changed?") { tag_list_changed_for_kind?(k) } end end end module InstanceMethods def set_tag_list(kind, list) tag_list = TagList.new(list) tag_list_will_change_for_kind!(kind) instance_variable_set(tag_list_name_for_kind(kind), tag_list) end def get_tag_list(kind) set_tag_list(kind, tags.of_kind(kind).map(&:name)) if tag_list_instance_variable(kind).nil? tag_list_instance_variable(kind) end protected def tag_list_will_change_for_kind!(kind) instance_variable_set(tag_list_changed_name_for_kind(kind), true) end def tag_list_changed_for_kind?(kind) instance_variable_get(tag_list_changed_name_for_kind(kind)) end def tag_list_changed_name_for_kind(kind) "@#{kind}_list_changed" end def tag_list_name_for_kind(kind) "@#{kind}_list" end def tag_list_instance_variable(kind) instance_variable_get(tag_list_name_for_kind(kind)) end def save_tags tag_kinds.each do |tag_kind| next unless tag_list_changed_for_kind?(tag_kind) delete_unused_tags(tag_kind) add_new_tags(tag_kind) instance_variable_set(tag_list_changed_name_for_kind(tag_kind), false) end taggings.each(&:save) end def delete_unused_tags(tag_kind) tags.of_kind(tag_kind).each { |t| tags.delete(t) unless get_tag_list(tag_kind).include?(t.name) } end def add_new_tags(tag_kind) get_tag_list(tag_kind).each do |tag_name| tag = Tag.find_or_initialize_with_name_like_and_kind(tag_name, tag_kind) tags << tag unless tags.include?(tag) end # Remember the normalized tag names. set_tag_list(tag_kind, tags.of_kind(tag_kind).map(&:name)) end end end end ActiveRecord::Base.send(:extend, IsTaggable::ActiveRecordExtension)