acts_as_taggable3 2.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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