Module: ActiveRecord::Base::TaggingExtensions
These extensions make models taggable. This file is automatically generated and required by your app if you run the tagging generator included with has_many_polymorphs.
Public Class Methods
tagged_with_any (*tag_list)
# File generators/tagging/templates/tagging_extensions.rb, line 153 153: def self.tagged_with_any(*tag_list) 154: options = tag_list.last.is_a?(Hash) ? tag_list.pop : {} 155: tag_list = parse_tags(tag_list) 156: 157: scope = scope(:find) 158: options[:select] ||= "#{table_name}.*" 159: options[:from] ||= "#{table_name}, #{Tag.table_name}, #{Tagging.table_name}" 160: 161: sql = "SELECT #{(scope && scope[:select]) || options[:select]} " 162: sql << "FROM #{(scope && scope[:from]) || options[:from]} " 163: 164: add_joins!(sql, options, scope) 165: 166: sql << "WHERE #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id " 167: sql << "AND #{Tagging.table_name}.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' " 168: sql << "AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id " 169: 170: sql << "AND (" 171: or_options = [] 172: tag_list.each do |name| 173: or_options << "#{Tag.table_name}.name = '#{name}'" 174: end 175: or_options_joined = or_options.join(" OR ") 176: sql << "#{or_options_joined}) " 177: 178: 179: sql << "AND #{sanitize_sql(options[:conditions])} " if options[:conditions] 180: 181: columns = column_names.map do |column| 182: "#{table_name}.#{column}" 183: end.join(", ") 184: 185: sql << "GROUP BY #{columns} " 186: 187: add_order!(sql, options[:order], scope) 188: add_limit!(sql, options, scope) 189: add_lock!(sql, options, scope) 190: 191: find_by_sql(sql) 192: end
Public Instance Methods
_add_tags (incoming)
Add tags to self. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
We need to avoid name conflicts with the built-in ActiveRecord association methods, thus the underscores.
# File generators/tagging/templates/tagging_extensions.rb, line 9 9: def _add_tags incoming 10: taggable?(true) 11: tag_cast_to_string(incoming).each do |tag_name| 12: begin 13: tag = Tag.find_or_create_by_name(tag_name) 14: raise Tag::Error, "tag could not be saved: #{tag_name}" if tag.new_record? 15: tags << tag 16: rescue ActiveRecord::StatementInvalid => e 17: raise unless e.to_s =~ /duplicate/i 18: end 19: end 20: end
_remove_tags (outgoing)
Removes tags from self. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
# File generators/tagging/templates/tagging_extensions.rb, line 23 23: def _remove_tags outgoing 24: taggable?(true) 25: outgoing = tag_cast_to_string(outgoing) 26: return [] if outgoing.empty? 27: <% if options[:self_referential] %> 28: # because of http://dev.rubyonrails.org/ticket/6466 29: taggings.destroy(*(taggings.find(:all, :include => :<%= parent_association_name -%>).select do |tagging| 30: outgoing.include? tagging.<%= parent_association_name -%>.name 31: end)) 32: <% else -%> 33: outgoing_tags = <%= parent_association_name -%>s.find_all_by_name(outgoing) 34: outgoing_taggings = taggings.find_all_by_<%= parent_association_name -%>_id(outgoing_tags.map(&:id)) 35: 36: taggings.destroy(*outgoing_taggings) 37: <% end -%> 38: end 39: 40: # Returns the tags on <tt>self</tt> as a string. 41: def tag_list 42: # Redefined later to avoid an RDoc parse error. 43: end 44: 45: # Replace the existing tags on <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags. 46: def tag_with list 47: #:stopdoc: 48: taggable?(true) 49: list = tag_cast_to_string(list) 50: 51: # Transactions may not be ideal for you here; be aware. 52: Tag.transaction do 53: current = <%= parent_association_name -%>s.map(&:name) 54: _add_tags(list - current) 55: _remove_tags(current - list) 56: end 57: 58: self 59: #:startdoc: 60: end 61: 62: # Returns the tags on <tt>self</tt> as a string. 63: def tag_list #:nodoc: 64: #:stopdoc: 65: taggable?(true) 66: <%= parent_association_name -%>s.reload 67: <%= parent_association_name -%>s.to_s 68: #:startdoc: 69: end 70: 71: def tag_list=(value) 72: tag_with(value) 73: end 74: 75: private 76: 77: def tag_cast_to_string obj #:nodoc: 78: case obj 79: when Array 80: obj.map! do |item| 81: case item 82: when /^\d+$/, Fixnum then Tag.find(item).name # This will be slow if you use ids a lot. 83: when Tag then item.name 84: when String then item 85: else 86: raise "Invalid type" 87: end 88: end 89: when String 90: obj = obj.split(Tag::DELIMITER).map do |tag_name| 91: tag_name.strip.squeeze(" ") 92: end 93: else 94: raise "Invalid object of class #{obj.class} as tagging method parameter" 95: end.flatten.compact.map(&:downcase).uniq 96: end 97: 98: # Check if a model is in the :taggables target list. The alternative to this check is to explicitly include a TaggingMethods module (which you would create) in each target model. 99: def taggable?(should_raise = false) #:nodoc: 100: unless flag = respond_to?(:<%= parent_association_name -%>s) 101: raise "#{self.class} is not a taggable model" if should_raise 102: end 103: flag 104: end 105: 106: end 107: 108: module TaggingFinders 109: # Find all the objects tagged with the supplied list of tags 110: # 111: # Usage : Model.tagged_with("ruby") 112: # Model.tagged_with("hello", "world") 113: # Model.tagged_with("hello", "world", :limit => 10) 114: # 115: # XXX This query strategy is not performant, and needs to be rewritten as an inverted join or a series of unions 116: # 117: def tagged_with(*tag_list) 118: options = tag_list.last.is_a?(Hash) ? tag_list.pop : {} 119: tag_list = parse_tags(tag_list) 120: 121: scope = scope(:find) 122: options[:select] ||= "#{table_name}.*" 123: options[:from] ||= "#{table_name}, #{Tag.table_name}, #{Tagging.table_name}" 124: 125: sql = "SELECT #{(scope && scope[:select]) || options[:select]} " 126: sql << "FROM #{(scope && scope[:from]) || options[:from]} " 127: 128: add_joins!(sql, options[:joins], scope) 129: 130: sql << "WHERE #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id " 131: sql << "AND #{Tagging.table_name}.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' " 132: sql << "AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id " 133: 134: tag_list_condition = tag_list.map {|name| "'#{name}'"}.join(", ") 135: 136: sql << "AND (#{Tag.table_name}.name IN (#{sanitize_sql(tag_list_condition)})) " 137: sql << "AND #{sanitize_sql(options[:conditions])} " if options[:conditions] 138: 139: columns = column_names.map do |column| 140: "#{table_name}.#{column}" 141: end.join(", ") 142: 143: sql << "GROUP BY #{columns} " 144: sql << "HAVING COUNT(#{Tagging.table_name}.tag_id) = #{tag_list.size}" 145: 146: add_order!(sql, options[:order], scope) 147: add_limit!(sql, options, scope) 148: add_lock!(sql, options, scope) 149: 150: find_by_sql(sql) 151: end 152: 153: def self.tagged_with_any(*tag_list) 154: options = tag_list.last.is_a?(Hash) ? tag_list.pop : {} 155: tag_list = parse_tags(tag_list) 156: 157: scope = scope(:find) 158: options[:select] ||= "#{table_name}.*" 159: options[:from] ||= "#{table_name}, #{Tag.table_name}, #{Tagging.table_name}" 160: 161: sql = "SELECT #{(scope && scope[:select]) || options[:select]} " 162: sql << "FROM #{(scope && scope[:from]) || options[:from]} " 163: 164: add_joins!(sql, options, scope) 165: 166: sql << "WHERE #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id " 167: sql << "AND #{Tagging.table_name}.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' " 168: sql << "AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id " 169: 170: sql << "AND (" 171: or_options = [] 172: tag_list.each do |name| 173: or_options << "#{Tag.table_name}.name = '#{name}'" 174: end 175: or_options_joined = or_options.join(" OR ") 176: sql << "#{or_options_joined}) " 177: 178: 179: sql << "AND #{sanitize_sql(options[:conditions])} " if options[:conditions] 180: 181: columns = column_names.map do |column| 182: "#{table_name}.#{column}" 183: end.join(", ") 184: 185: sql << "GROUP BY #{columns} " 186: 187: add_order!(sql, options[:order], scope) 188: add_limit!(sql, options, scope) 189: add_lock!(sql, options, scope) 190: 191: find_by_sql(sql) 192: end 193: 194: def parse_tags(tags) 195: return [] if tags.blank? 196: tags = Array(tags).first 197: tags = tags.respond_to?(:flatten) ? tags.flatten : tags.split(Tag::DELIMITER) 198: tags.map { |tag| tag.strip.squeeze(" ") }.flatten.compact.map(&:downcase).uniq 199: end 200: 201: end 202: 203: include TaggingExtensions 204: extend TaggingFinders 205: end
parse_tags (tags)
# File generators/tagging/templates/tagging_extensions.rb, line 194 194: def parse_tags(tags) 195: return [] if tags.blank? 196: tags = Array(tags).first 197: tags = tags.respond_to?(:flatten) ? tags.flatten : tags.split(Tag::DELIMITER) 198: tags.map { |tag| tag.strip.squeeze(" ") }.flatten.compact.map(&:downcase).uniq 199: end
tag_list= (value)
# File generators/tagging/templates/tagging_extensions.rb, line 71 71: def tag_list=(value) 72: tag_with(value) 73: end
tagged_with (*tag_list)
XXX This query strategy is not performant, and needs to be rewritten as an inverted join or a series of unions
# File generators/tagging/templates/tagging_extensions.rb, line 117 117: def tagged_with(*tag_list) 118: options = tag_list.last.is_a?(Hash) ? tag_list.pop : {} 119: tag_list = parse_tags(tag_list) 120: 121: scope = scope(:find) 122: options[:select] ||= "#{table_name}.*" 123: options[:from] ||= "#{table_name}, #{Tag.table_name}, #{Tagging.table_name}" 124: 125: sql = "SELECT #{(scope && scope[:select]) || options[:select]} " 126: sql << "FROM #{(scope && scope[:from]) || options[:from]} " 127: 128: add_joins!(sql, options[:joins], scope) 129: 130: sql << "WHERE #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id " 131: sql << "AND #{Tagging.table_name}.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' " 132: sql << "AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id " 133: 134: tag_list_condition = tag_list.map {|name| "'#{name}'"}.join(", ") 135: 136: sql << "AND (#{Tag.table_name}.name IN (#{sanitize_sql(tag_list_condition)})) " 137: sql << "AND #{sanitize_sql(options[:conditions])} " if options[:conditions] 138: 139: columns = column_names.map do |column| 140: "#{table_name}.#{column}" 141: end.join(", ") 142: 143: sql << "GROUP BY #{columns} " 144: sql << "HAVING COUNT(#{Tagging.table_name}.tag_id) = #{tag_list.size}" 145: 146: add_order!(sql, options[:order], scope) 147: add_limit!(sql, options, scope) 148: add_lock!(sql, options, scope) 149: 150: find_by_sql(sql) 151: end