acts_as_taggable3 2.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,24 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class ActsAsTaggableMigrationGenerator < Rails::Generators::Base
5
+ include Rails::Generators::Migration
6
+
7
+ def self.source_root
8
+ @source_root ||= File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
9
+ end
10
+
11
+ def self.next_migration_number(dirname)
12
+ Time.now.strftime("%Y%m%d%H%M%S")
13
+ end
14
+
15
+ def create_migration
16
+ migration_template "migration.rb", File.join("db/migrate", "#{file_name}.rb")
17
+ end
18
+
19
+ protected
20
+
21
+ def file_name
22
+ "acts_as_taggable_migration"
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ class ActsAsTaggableMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tags do |t|
4
+ t.string :name
5
+ end
6
+
7
+ create_table :taggings do |t|
8
+ t.references :tag
9
+ t.references :taggable, :polymorphic => true
10
+ t.datetime :created_at
11
+ end
12
+
13
+ add_index :tags, :name
14
+ add_index :taggings, :tag_id
15
+ add_index :taggings, [:taggable_id, :taggable_type]
16
+ end
17
+
18
+ def self.down
19
+ drop_table :taggings
20
+ drop_table :tags
21
+ end
22
+ end
@@ -0,0 +1,59 @@
1
+ require 'active_support/core_ext/module/deprecation'
2
+
3
+ class Tag < ActiveRecord::Base
4
+ cattr_accessor :destroy_unused
5
+ self.destroy_unused = false
6
+
7
+ has_many :taggings, :dependent => :delete_all
8
+
9
+ validates_presence_of :name
10
+ validates_uniqueness_of :name
11
+
12
+ def ==(object)
13
+ super || (object.is_a?(Tag) && name == object.name)
14
+ end
15
+
16
+ def to_s
17
+ name
18
+ end
19
+
20
+ def count
21
+ read_attribute(:count).to_i
22
+ end
23
+
24
+ class << self
25
+ def find_or_create_with_like_by_name(name)
26
+ where(arel_table[:name].matches(name)).first || create(:name => name)
27
+ end
28
+
29
+ # Calculate the tag counts for all tags.
30
+ #
31
+ # - +:start_at+ - restrict the tags to those created after a certain time
32
+ # - +:end_at+ - restrict the tags to those created before a certain time
33
+ # - +:at_least+ - exclude tags with a frequency less than the given value
34
+ # - +:at_most+ - exclude tags with a frequency greater than the given value
35
+ #
36
+ # Deprecated:
37
+ #
38
+ # - +:conditions+
39
+ # - +:limit+
40
+ # - +:order+
41
+ #
42
+ def counts(options = {})
43
+ options.assert_valid_keys :start_at, :end_at, :at_least, :at_most, :conditions, :limit, :order
44
+
45
+ tags = joins(:taggings).group(:name)
46
+ tags = tags.having(['count >= ?', options[:at_least]]) if options[:at_least]
47
+ tags = tags.having(['count <= ?', options[:at_most]]) if options[:at_most]
48
+ tags = tags.where("#{Tagging.quoted_table_name}.created_at >= ?", options[:start_at]) if options[:start_at]
49
+ tags = tags.where("#{Tagging.quoted_table_name}.created_at <= ?", options[:end_at]) if options[:end_at]
50
+
51
+ # TODO: deprecation warning
52
+ tags = tags.where(options[:conditions]) if options[:conditions]
53
+ tags = tags.limit(options[:limit]) if options[:limit]
54
+ tags = tags.order(options[:order]) if options[:order]
55
+
56
+ tags.select("#{quoted_table_name}.*, COUNT(#{quoted_table_name}.id) AS count")
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,109 @@
1
+ class TagList < Array
2
+ cattr_accessor :delimiter
3
+ self.delimiter = ','
4
+
5
+ def initialize(*args)
6
+ add(*args)
7
+ end
8
+
9
+ # Add tags to the tag_list. Duplicate or blank tags will be ignored.
10
+ #
11
+ # tag_list.add("Fun", "Happy")
12
+ #
13
+ # Use the <tt>:parse</tt> option to add an unparsed tag string.
14
+ #
15
+ # tag_list.add("Fun, Happy", :parse => true)
16
+ def add(*names)
17
+ extract_and_apply_options!(names)
18
+ concat(names)
19
+ clean!
20
+ self
21
+ end
22
+
23
+ # Remove specific tags from the tag_list.
24
+ #
25
+ # tag_list.remove("Sad", "Lonely")
26
+ #
27
+ # Like #add, the <tt>:parse</tt> option can be used to remove multiple tags in a string.
28
+ #
29
+ # tag_list.remove("Sad, Lonely", :parse => true)
30
+ def remove(*names)
31
+ extract_and_apply_options!(names)
32
+ delete_if { |name| names.include?(name) }
33
+ self
34
+ end
35
+
36
+ # Toggle the presence of the given tags.
37
+ # If a tag is already in the list it is removed, otherwise it is added.
38
+ def toggle(*names)
39
+ extract_and_apply_options!(names)
40
+
41
+ names.each do |name|
42
+ include?(name) ? delete(name) : push(name)
43
+ end
44
+
45
+ clean!
46
+ self
47
+ end
48
+
49
+ # Transform the tag_list into a tag string suitable for edting in a form.
50
+ # The tags are joined with <tt>TagList.delimiter</tt> and quoted if necessary.
51
+ #
52
+ # tag_list = TagList.new("Round", "Square,Cube")
53
+ # tag_list.to_s # 'Round, "Square,Cube"'
54
+ def to_s
55
+ clean!
56
+
57
+ map do |name|
58
+ name.include?(delimiter) ? "\"#{name}\"" : name
59
+ end.join(delimiter.ends_with?(" ") ? delimiter : "#{delimiter} ")
60
+ end
61
+
62
+ private
63
+ # Remove whitespace, duplicates, and blanks.
64
+ def clean!
65
+ reject!(&:blank?)
66
+ map!(&:strip)
67
+ uniq!
68
+ end
69
+
70
+ def extract_and_apply_options!(args)
71
+ options = args.last.is_a?(Hash) ? args.pop : {}
72
+ options.assert_valid_keys :parse
73
+
74
+ if options[:parse]
75
+ args.map! { |a| self.class.from(a) }
76
+ end
77
+
78
+ args.flatten!
79
+ end
80
+
81
+ class << self
82
+ # Returns a new TagList using the given tag string.
83
+ #
84
+ # tag_list = TagList.from("One , Two, Three")
85
+ # tag_list # ["One", "Two", "Three"]
86
+ def from(source)
87
+ tag_list = new
88
+
89
+ case source
90
+ when Array
91
+ tag_list.add(source)
92
+ else
93
+ string = source.to_s.dup
94
+
95
+ # Parse the quoted tags
96
+ [
97
+ /\s*#{delimiter}\s*(['"])(.*?)\1\s*/,
98
+ /^\s*(['"])(.*?)\1\s*#{delimiter}?/
99
+ ].each do |re|
100
+ string.gsub!(re) { tag_list << $2; "" }
101
+ end
102
+
103
+ tag_list.add(string.split(delimiter))
104
+ end
105
+
106
+ tag_list
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,16 @@
1
+ class Tagging < ActiveRecord::Base #:nodoc:
2
+ belongs_to :tag
3
+ belongs_to :taggable, :polymorphic => true
4
+
5
+ after_destroy :destroy_tag_if_unused
6
+
7
+ private
8
+
9
+ def destroy_tag_if_unused
10
+ if Tag.destroy_unused
11
+ if tag.taggings.count.zero?
12
+ tag.destroy
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ module TagsHelper
2
+ # See the README for an example using tag_cloud.
3
+ def tag_cloud(tags, classes)
4
+ return if tags.all.empty?
5
+
6
+ max_count = tags.sort_by(&:count).last.count.to_f
7
+
8
+ tags.each do |tag|
9
+ index = ((tag.count / max_count) * (classes.size - 1)).round
10
+ yield tag, classes[index]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,106 @@
1
+ require 'test/unit'
2
+
3
+ begin
4
+ require File.dirname(__FILE__) + '/../../../../config/environment'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ gem 'activesupport'
8
+ gem 'activerecord'
9
+ gem 'actionpack'
10
+ require 'active_support/dependencies'
11
+ require 'active_record'
12
+ require 'action_controller'
13
+ end
14
+
15
+ # Search for fixtures first
16
+ fixture_path = File.dirname(__FILE__) + '/fixtures/'
17
+ ActiveSupport::Dependencies.autoload_paths.insert(0, fixture_path)
18
+
19
+ require "active_record/test_case"
20
+ require "active_record/fixtures"
21
+
22
+ require File.dirname(__FILE__) + '/../lib/acts_as_taggable'
23
+ require_dependency File.dirname(__FILE__) + '/../lib/tag_list'
24
+ require_dependency File.dirname(__FILE__) + '/../lib/tags_helper'
25
+
26
+ ENV['DB'] ||= 'sqlite3'
27
+
28
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/debug.log')
29
+ ActiveRecord::Base.configurations = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
30
+ ActiveRecord::Base.establish_connection(ENV['DB'])
31
+
32
+ load(File.dirname(__FILE__) + '/schema.rb')
33
+
34
+ class ActiveSupport::TestCase #:nodoc:
35
+ include ActiveRecord::TestFixtures
36
+
37
+ self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
38
+
39
+ self.use_transactional_fixtures = true
40
+ self.use_instantiated_fixtures = false
41
+
42
+ fixtures :all
43
+
44
+ def assert_equivalent(expected, actual, message = nil)
45
+ if expected.first.is_a?(ActiveRecord::Base)
46
+ assert_equal expected.sort_by(&:id), actual.sort_by(&:id), message
47
+ else
48
+ assert_equal expected.sort, actual.sort, message
49
+ end
50
+ end
51
+
52
+ def assert_tag_counts(tags, expected_values)
53
+ # Map the tag fixture names to real tag names
54
+ expected_values = expected_values.inject({}) do |hash, (tag, count)|
55
+ hash[tags(tag).name] = count
56
+ hash
57
+ end
58
+
59
+ tags.each do |tag|
60
+ value = expected_values.delete(tag.name)
61
+
62
+ assert_not_nil value, "Expected count for #{tag.name} was not provided"
63
+ assert_equal value, tag.count, "Expected value of #{value} for #{tag.name}, but was #{tag.count}"
64
+ end
65
+
66
+ unless expected_values.empty?
67
+ assert false, "The following tag counts were not present: #{expected_values.inspect}"
68
+ end
69
+ end
70
+
71
+ def assert_queries(num = 1)
72
+ $query_count = 0
73
+ yield
74
+ ensure
75
+ assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
76
+ end
77
+
78
+ def assert_no_queries(&block)
79
+ assert_queries(0, &block)
80
+ end
81
+
82
+ # From Rails trunk
83
+ def assert_difference(expressions, difference = 1, message = nil, &block)
84
+ expression_evaluations = [expressions].flatten.collect{|expression| lambda { eval(expression, block.binding) } }
85
+
86
+ original_values = expression_evaluations.inject([]) { |memo, expression| memo << expression.call }
87
+ yield
88
+ expression_evaluations.each_with_index do |expression, i|
89
+ assert_equal original_values[i] + difference, expression.call, message
90
+ end
91
+ end
92
+
93
+ def assert_no_difference(expressions, message = nil, &block)
94
+ assert_difference expressions, 0, message, &block
95
+ end
96
+ end
97
+
98
+ ActiveRecord::Base.connection.class.class_eval do
99
+ def execute_with_counting(sql, name = nil, &block)
100
+ $query_count ||= 0
101
+ $query_count += 1
102
+ execute_without_counting(sql, name, &block)
103
+ end
104
+
105
+ alias_method_chain :execute, :counting
106
+ end
@@ -0,0 +1,384 @@
1
+ require File.expand_path('../abstract_unit', __FILE__)
2
+
3
+ class ActsAsTaggableOnSteroidsTest < ActiveSupport::TestCase
4
+ def test_find_related_tags_with
5
+ assert_equivalent [tags(:good), tags(:bad), tags(:question)], Post.find_related_tags("nature")
6
+ assert_equivalent [tags(:nature)], Post.find_related_tags([tags(:good)])
7
+ assert_equivalent [tags(:bad), tags(:question)], Post.find_related_tags(["Very Good", "Nature"])
8
+ assert_equivalent [tags(:bad), tags(:question)], Post.find_related_tags([tags(:good), tags(:nature)])
9
+ end
10
+
11
+ def test_find_tagged_with_include_and_order
12
+ assert_equal photos(:sam_sky, :sam_flower, :jonathan_dog), Photo.find_tagged_with("Nature", :order => "photos.title DESC", :include => :user)
13
+ end
14
+
15
+ def test_find_related_tags_with_non_existent_tags
16
+ assert_equal [], Post.find_related_tags("ABCDEFG")
17
+ assert_equal [], Post.find_related_tags(['HIJKLM'])
18
+ end
19
+
20
+ def test_find_related_tags_with_nothing
21
+ assert_equal [], Post.find_related_tags("")
22
+ assert_equal [], Post.find_related_tags([])
23
+ end
24
+
25
+ def test_find_tagged_with
26
+ assert_equivalent [posts(:jonathan_sky), posts(:sam_flowers)], Post.find_tagged_with('"Very good"')
27
+ assert_equal Post.find_tagged_with('"Very good"'), Post.find_tagged_with(['Very good'])
28
+ assert_equal Post.find_tagged_with('"Very good"'), Post.find_tagged_with([tags(:good)])
29
+
30
+ assert_equivalent [photos(:jonathan_dog), photos(:sam_flower), photos(:sam_sky)], Photo.find_tagged_with('Nature')
31
+ assert_equal Photo.find_tagged_with('Nature'), Photo.find_tagged_with(['Nature'])
32
+ assert_equal Photo.find_tagged_with('Nature'), Photo.find_tagged_with([tags(:nature)])
33
+
34
+ assert_equivalent [photos(:jonathan_bad_cat), photos(:jonathan_dog), photos(:jonathan_questioning_dog)], Photo.find_tagged_with('"Crazy animal" Bad')
35
+ assert_equal Photo.find_tagged_with('"Crazy animal" Bad'), Photo.find_tagged_with(['Crazy animal', 'Bad'])
36
+ assert_equal Photo.find_tagged_with('"Crazy animal" Bad'), Photo.find_tagged_with([tags(:animal), tags(:bad)])
37
+ end
38
+
39
+ def test_find_tagged_with_nothing
40
+ assert_equal [], Post.find_tagged_with("")
41
+ assert_equal [], Post.find_tagged_with([])
42
+ end
43
+
44
+ def test_find_tagged_with_nonexistant_tags
45
+ assert_equal [], Post.find_tagged_with('ABCDEFG')
46
+ assert_equal [], Photo.find_tagged_with(['HIJKLM'])
47
+ assert_equal [], Photo.find_tagged_with([Tag.new(:name => 'unsaved tag')])
48
+ end
49
+
50
+ def test_find_tagged_with_match_all
51
+ assert_equivalent [photos(:jonathan_dog)], Photo.find_tagged_with('Crazy animal, "Nature"', :match_all => true)
52
+ end
53
+
54
+ def test_find_tagged_with_match_all_and_include
55
+ assert_equivalent [posts(:jonathan_sky), posts(:sam_flowers)], Post.find_tagged_with(['Very good', 'Nature'], :match_all => true, :include => :tags)
56
+ end
57
+
58
+ def test_find_tagged_with_conditions
59
+ assert_equal [], Post.find_tagged_with('"Very good", Nature', :conditions => '1=0')
60
+ end
61
+
62
+ def test_find_tagged_with_duplicates_options_hash
63
+ options = { :conditions => '1=1' }.freeze
64
+ assert_nothing_raised { Post.find_tagged_with("Nature", options) }
65
+ end
66
+
67
+ def test_find_tagged_with_exclusions
68
+ assert_equivalent [photos(:jonathan_questioning_dog), photos(:jonathan_bad_cat)], Photo.find_tagged_with("Nature", :exclude => true)
69
+ assert_equivalent [posts(:jonathan_grass), posts(:jonathan_rain), posts(:jonathan_cloudy), posts(:jonathan_still_cloudy)], Post.find_tagged_with("'Very good', Bad", :exclude => true)
70
+ end
71
+
72
+ # def test_find_options_for_find_tagged_with_no_tags_returns_empty_hash
73
+ # assert_equal Hash.new, Post.find_options_for_find_tagged_with("")
74
+ # assert_equal Hash.new, Post.find_options_for_find_tagged_with([nil])
75
+ # end
76
+
77
+ # def test_find_options_for_find_tagged_with_leaves_arguments_unchanged
78
+ # original_tags = photos(:jonathan_questioning_dog).tags.dup
79
+ # Photo.find_options_for_find_tagged_with(photos(:jonathan_questioning_dog).tags)
80
+ # assert_equal original_tags, photos(:jonathan_questioning_dog).tags
81
+ # end
82
+
83
+ # def test_find_options_for_find_tagged_with_respects_custom_table_name
84
+ # Tagging.table_name = "categorisations"
85
+ # Tag.table_name = "categories"
86
+ #
87
+ # options = Photo.find_options_for_find_tagged_with("Hello")
88
+ #
89
+ # assert_no_match(/ taggings /, options[:joins])
90
+ # assert_no_match(/ tags /, options[:joins])
91
+ #
92
+ # assert_match(/ categorisations /, options[:joins])
93
+ # assert_match(/ categories /, options[:joins])
94
+ # ensure
95
+ # Tagging.table_name = "taggings"
96
+ # Tag.table_name = "tags"
97
+ # end
98
+
99
+ def test_include_tags_on_find_tagged_with
100
+ assert_nothing_raised do
101
+ Photo.find_tagged_with('Nature', :include => :tags)
102
+ Photo.find_tagged_with("Nature", :include => { :taggings => :tag })
103
+ end
104
+ end
105
+
106
+ def test_basic_tag_counts_on_class
107
+ assert_tag_counts Post.tag_counts, :good => 2, :nature => 7, :question => 1, :bad => 1
108
+ assert_tag_counts Photo.tag_counts, :good => 1, :nature => 3, :question => 1, :bad => 1, :animal => 3
109
+ end
110
+
111
+ def test_tag_counts_on_class_with_date_conditions
112
+ assert_tag_counts Post.tag_counts(:start_at => Date.new(2006, 8, 4)), :good => 1, :nature => 5, :question => 1, :bad => 1
113
+ assert_tag_counts Post.tag_counts(:end_at => Date.new(2006, 8, 6)), :good => 1, :nature => 4, :question => 1
114
+ assert_tag_counts Post.tag_counts(:start_at => Date.new(2006, 8, 5), :end_at => Date.new(2006, 8, 10)), :good => 1, :nature => 4, :bad => 1
115
+
116
+ assert_tag_counts Photo.tag_counts(:start_at => Date.new(2006, 8, 12), :end_at => Date.new(2006, 8, 19)), :good => 1, :nature => 2, :bad => 1, :question => 1, :animal => 3
117
+ end
118
+
119
+ def test_tag_counts_on_class_with_frequencies
120
+ assert_tag_counts Photo.tag_counts(:at_least => 2), :nature => 3, :animal => 3
121
+ assert_tag_counts Photo.tag_counts(:at_most => 2), :good => 1, :question => 1, :bad => 1
122
+ end
123
+
124
+ def test_tag_counts_on_class_with_frequencies_and_conditions
125
+ assert_tag_counts Photo.tag_counts(:at_least => 2, :conditions => '1=1'), :nature => 3, :animal => 3
126
+ end
127
+
128
+ def test_tag_counts_duplicates_options_hash
129
+ options = { :at_least => 2, :conditions => '1=1' }.freeze
130
+ assert_nothing_raised { Photo.tag_counts(options) }
131
+ end
132
+
133
+ def test_tag_counts_with_limit
134
+ assert_equal 2, Photo.tag_counts(:limit => 2).all.size
135
+ assert_equal 1, Post.tag_counts(:at_least => 4, :limit => 2).all.size
136
+ end
137
+
138
+ def test_tag_counts_with_limit_and_order
139
+ assert_equal [tags(:nature), tags(:good)], Post.tag_counts(:order => 'count desc', :limit => 2)
140
+ end
141
+
142
+ def test_tag_counts_on_association
143
+ assert_tag_counts users(:jonathan).posts.tag_counts, :good => 1, :nature => 5, :question => 1
144
+ assert_tag_counts users(:sam).posts.tag_counts, :good => 1, :nature => 2, :bad => 1
145
+
146
+ assert_tag_counts users(:jonathan).photos.tag_counts, :animal => 3, :nature => 1, :question => 1, :bad => 1
147
+ assert_tag_counts users(:sam).photos.tag_counts, :nature => 2, :good => 1
148
+ end
149
+
150
+ def test_tag_counts_on_association_with_options
151
+ assert_equal [], users(:jonathan).posts.tag_counts(:conditions => '1=0')
152
+ assert_tag_counts users(:jonathan).posts.tag_counts(:at_most => 2), :good => 1, :question => 1
153
+ end
154
+
155
+ def test_tag_counts_on_has_many_through
156
+ assert_tag_counts users(:jonathan).magazines.tag_counts, :good => 1
157
+ end
158
+
159
+ def test_tag_counts_on_model_instance
160
+ assert_tag_counts photos(:jonathan_dog).tag_counts, :animal => 3, :nature => 3
161
+ end
162
+
163
+ def test_tag_counts_on_model_instance_merges_conditions
164
+ assert_tag_counts photos(:jonathan_dog).tag_counts(:conditions => "tags.name = 'Crazy animal'"), :animal => 3
165
+ end
166
+
167
+ def test_tag_counts_on_model_instance_with_no_tags
168
+ photo = Photo.create!
169
+
170
+ assert_tag_counts photo.tag_counts, {}
171
+ end
172
+
173
+ def test_tag_counts_should_sanitize_scope_conditions
174
+ Photo.send :with_scope, :find => { :conditions => ["tags.id = ?", tags(:animal).id] } do
175
+ assert_tag_counts Photo.tag_counts, :animal => 3
176
+ end
177
+ end
178
+
179
+ # # NOTE: the SQL FROM statement won't change when changing table_name dynamically...
180
+ # def test_tag_counts_respects_custom_table_names
181
+ # Tagging.table_name = "categorisations"
182
+ # Tag.table_name = "categories"
183
+ #
184
+ # sql = Photo.tag_counts(:start_at => 2.weeks.ago, :end_at => Date.today).to_sql
185
+ #
186
+ # assert_no_match /taggings/, sql
187
+ # assert_no_match /tags/, sql
188
+ #
189
+ # assert_match /categorisations/, sql
190
+ # assert_match /categories/, sql
191
+ # ensure
192
+ # Tagging.table_name = "taggings"
193
+ # Tag.table_name = "tags"
194
+ # end
195
+
196
+ def test_tag_list_reader
197
+ assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
198
+ assert_equivalent ["Bad", "Crazy animal"], photos(:jonathan_bad_cat).tag_list
199
+ end
200
+
201
+ def test_reassign_tag_list
202
+ assert_equivalent ["Nature", "Question"], posts(:jonathan_rain).tag_list
203
+ posts(:jonathan_rain).taggings.reload
204
+
205
+ assert_queries ENV['DB'] == 'sqlite3' ? 1 : 3 do
206
+ posts(:jonathan_rain).update_attributes!(:tag_list => posts(:jonathan_rain).tag_list.to_s)
207
+ end
208
+
209
+ assert_equivalent ["Nature", "Question"], posts(:jonathan_rain).tag_list
210
+ end
211
+
212
+ def test_new_tags
213
+ assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
214
+ posts(:jonathan_sky).update_attributes!(:tag_list => "#{posts(:jonathan_sky).tag_list}, One, Two")
215
+ assert_equivalent ["Very good", "Nature", "One", "Two"], posts(:jonathan_sky).tag_list
216
+ end
217
+
218
+ def test_remove_tag
219
+ assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
220
+ posts(:jonathan_sky).update_attributes!(:tag_list => "Nature")
221
+ assert_equivalent ["Nature"], posts(:jonathan_sky).tag_list
222
+ end
223
+
224
+ def test_change_case_of_tags
225
+ original_tag_names = photos(:jonathan_questioning_dog).tag_list
226
+ photos(:jonathan_questioning_dog).update_attributes!(:tag_list => photos(:jonathan_questioning_dog).tag_list.to_s.upcase)
227
+
228
+ # The new tag list is not uppercase becuase the AR finders are not case-sensitive
229
+ # and find the old tags when re-tagging with the uppercase tags.
230
+ assert_equivalent original_tag_names, photos(:jonathan_questioning_dog).reload.tag_list
231
+ end
232
+
233
+ def test_remove_and_add_tag
234
+ assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
235
+ posts(:jonathan_sky).update_attributes!(:tag_list => "Nature, Beautiful")
236
+ assert_equivalent ["Nature", "Beautiful"], posts(:jonathan_sky).tag_list
237
+ end
238
+
239
+ def test_tags_not_saved_if_validation_fails
240
+ assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
241
+ assert !posts(:jonathan_sky).update_attributes(:tag_list => "One, Two", :text => "")
242
+ assert_equivalent ["Very good", "Nature"], Post.find(posts(:jonathan_sky).id).tag_list
243
+ end
244
+
245
+ def test_tag_list_accessors_on_new_record
246
+ p = Post.new(:text => 'Test')
247
+
248
+ assert p.tag_list.blank?
249
+ p.tag_list = "One, Two"
250
+ assert_equal "One, Two", p.tag_list.to_s
251
+ end
252
+
253
+ def test_clear_tag_list_with_nil
254
+ p = photos(:jonathan_questioning_dog)
255
+
256
+ assert !p.tag_list.blank?
257
+ assert p.update_attributes(:tag_list => nil)
258
+ assert p.tag_list.blank?
259
+
260
+ assert p.reload.tag_list.blank?
261
+ end
262
+
263
+ def test_clear_tag_list_with_string
264
+ p = photos(:jonathan_questioning_dog)
265
+
266
+ assert !p.tag_list.blank?
267
+ assert p.update_attributes(:tag_list => ' ')
268
+ assert p.tag_list.blank?
269
+
270
+ assert p.reload.tag_list.blank?
271
+ end
272
+
273
+ def test_tag_list_reset_on_reload
274
+ p = photos(:jonathan_questioning_dog)
275
+ assert !p.tag_list.blank?
276
+ p.tag_list = nil
277
+ assert p.tag_list.blank?
278
+ assert !p.reload.tag_list.blank?
279
+ end
280
+
281
+ def test_instance_tag_counts
282
+ assert_tag_counts posts(:jonathan_sky).tag_counts, :good => 2, :nature => 7
283
+ end
284
+
285
+ def test_tag_list_populated_when_cache_nil
286
+ assert_nil posts(:jonathan_sky).cached_tag_list
287
+ posts(:jonathan_sky).save!
288
+ assert_equal posts(:jonathan_sky).tag_list.to_s, posts(:jonathan_sky).cached_tag_list
289
+ end
290
+
291
+ def test_cached_tag_list_used
292
+ posts(:jonathan_sky).save!
293
+ posts(:jonathan_sky).reload
294
+
295
+ assert_no_queries do
296
+ assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
297
+ end
298
+ end
299
+
300
+ def test_cached_tag_list_not_used
301
+ # Load fixture and column information
302
+ posts(:jonathan_sky).taggings(:reload)
303
+
304
+ assert_queries 1 do
305
+ # Tags association will be loaded
306
+ posts(:jonathan_sky).tag_list
307
+ end
308
+ end
309
+
310
+ def test_cached_tag_list_updated
311
+ assert_nil posts(:jonathan_sky).cached_tag_list
312
+ posts(:jonathan_sky).save!
313
+ assert_equivalent ["Very good", "Nature"], TagList.from(posts(:jonathan_sky).cached_tag_list)
314
+ posts(:jonathan_sky).update_attributes!(:tag_list => "None")
315
+
316
+ assert_equal 'None', posts(:jonathan_sky).cached_tag_list
317
+ assert_equal 'None', posts(:jonathan_sky).reload.cached_tag_list
318
+ end
319
+
320
+ def test_clearing_cached_tag_list
321
+ # Generate the cached tag list
322
+ posts(:jonathan_sky).save!
323
+
324
+ posts(:jonathan_sky).update_attributes!(:tag_list => "")
325
+ assert_equal "", posts(:jonathan_sky).cached_tag_list
326
+ end
327
+
328
+ def test_find_tagged_with_using_sti
329
+ special_post = SpecialPost.create!(:text => "Test", :tag_list => "Random")
330
+
331
+ assert_equal [special_post], SpecialPost.find_tagged_with("Random")
332
+ assert Post.find_tagged_with("Random").include?(special_post)
333
+ end
334
+
335
+ def test_tag_counts_using_sti
336
+ SpecialPost.create!(:text => "Test", :tag_list => "Nature")
337
+
338
+ assert_tag_counts SpecialPost.tag_counts, :nature => 1
339
+ end
340
+
341
+ def test_case_insensitivity
342
+ assert_difference "Tag.count", 1 do
343
+ Post.create!(:text => "Test", :tag_list => "one")
344
+ Post.create!(:text => "Test", :tag_list => "One")
345
+ end
346
+
347
+ assert_equal Post.find_tagged_with("Nature").collect(&:id),
348
+ Post.find_tagged_with("nature").collect(&:id)
349
+ end
350
+
351
+ def test_tag_not_destroyed_when_unused
352
+ posts(:jonathan_sky).tag_list.add("Random")
353
+ posts(:jonathan_sky).save!
354
+
355
+ assert_no_difference 'Tag.count' do
356
+ posts(:jonathan_sky).tag_list.remove("Random")
357
+ posts(:jonathan_sky).save!
358
+ end
359
+ end
360
+
361
+ def test_tag_destroyed_when_unused
362
+ Tag.destroy_unused = true
363
+
364
+ posts(:jonathan_sky).tag_list.add("Random")
365
+ posts(:jonathan_sky).save!
366
+
367
+ assert_difference 'Tag.count', -1 do
368
+ posts(:jonathan_sky).tag_list.remove("Random")
369
+ posts(:jonathan_sky).save!
370
+ end
371
+ ensure
372
+ Tag.destroy_unused = false
373
+ end
374
+ end
375
+
376
+ #class ActsAsTaggableOnSteroidsFormTest < ActiveSupport::TestCase
377
+ # include ActionView::Helpers::FormHelper
378
+ #
379
+ # def test_tag_list_contents
380
+ # fields_for :post, posts(:jonathan_sky) do |f|
381
+ # assert_match posts(:jonathan_sky).tag_list.to_s, f.text_field(:tag_list)
382
+ # end
383
+ # end
384
+ #end