acts_as_20ggable 1.0.0

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.
Files changed (49) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +1 -0
  3. data/CHANGELOG +6 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +61 -0
  6. data/README +76 -0
  7. data/Rakefile +24 -0
  8. data/acts_as_20ggable.gemspec +26 -0
  9. data/generators/acts_as_20ggable_migration/acts_as_20ggable_migration_generator.rb +11 -0
  10. data/generators/acts_as_20ggable_migration/templates/migration.rb +45 -0
  11. data/lib/acts_as_20ggable.rb +7 -0
  12. data/lib/acts_as_taggable.rb +201 -0
  13. data/lib/tag.rb +146 -0
  14. data/lib/tag_counts_extension.rb +3 -0
  15. data/lib/tag_hierarchy_builder.rb +201 -0
  16. data/lib/tag_list.rb +108 -0
  17. data/lib/tagging.rb +10 -0
  18. data/lib/tags_helper.rb +13 -0
  19. data/test/fixtures/magazine.rb +3 -0
  20. data/test/fixtures/magazines.yml +7 -0
  21. data/test/fixtures/photo.rb +8 -0
  22. data/test/fixtures/photos.yml +24 -0
  23. data/test/fixtures/post.rb +7 -0
  24. data/test/fixtures/posts.yml +34 -0
  25. data/test/fixtures/schema.rb +73 -0
  26. data/test/fixtures/special_post.rb +2 -0
  27. data/test/fixtures/subscription.rb +4 -0
  28. data/test/fixtures/subscriptions.yml +3 -0
  29. data/test/fixtures/taggings.yml +162 -0
  30. data/test/fixtures/tags.yml +75 -0
  31. data/test/fixtures/tags_hierarchy.yml +31 -0
  32. data/test/fixtures/tags_synonyms.yml +16 -0
  33. data/test/fixtures/tags_transitive_hierarchy.yml +96 -0
  34. data/test/fixtures/user.rb +9 -0
  35. data/test/fixtures/users.yml +7 -0
  36. data/test/fixtures/video.rb +3 -0
  37. data/test/fixtures/videos.yml +9 -0
  38. data/test/lib/acts_as_taggable_test.rb +359 -0
  39. data/test/lib/tag_hierarchy_builder_test.rb +109 -0
  40. data/test/lib/tag_list_test.rb +120 -0
  41. data/test/lib/tag_test.rb +45 -0
  42. data/test/lib/tagging_test.rb +14 -0
  43. data/test/lib/tags_helper_test.rb +28 -0
  44. data/test/lib/tags_hierarchy_test.rb +12 -0
  45. data/test/support/activerecord_test_connector.rb +129 -0
  46. data/test/support/custom_asserts.rb +35 -0
  47. data/test/support/database.yml +10 -0
  48. data/test/test_helper.rb +20 -0
  49. metadata +183 -0
@@ -0,0 +1,16 @@
1
+ cat_synonym_pussy:
2
+ tag_id: 13
3
+ synonym_id: 15
4
+
5
+ cat_synonym_kitty:
6
+ tag_id: 13
7
+ synonym_id: 18
8
+
9
+ horse_synonym_racehorse:
10
+ tag_id: 11
11
+ synonym_id: 16
12
+
13
+ question_synonym_problem:
14
+ tag_id: 4
15
+ synonym_id: 17
16
+
@@ -0,0 +1,96 @@
1
+ people_children:
2
+ tag_id: 6
3
+ child_id: 7
4
+
5
+ people_nude:
6
+ tag_id: 6
7
+ child_id: 8
8
+
9
+ people_me:
10
+ tag_id: 6
11
+ child_id: 9
12
+
13
+ nature_animals:
14
+ tag_id: 3
15
+ child_id: 10
16
+
17
+ nature_animals_horse:
18
+ tag_id: 10
19
+ child_id: 11
20
+
21
+ nature_horse:
22
+ tag_id: 3
23
+ child_id: 11
24
+
25
+ nature_animals_domestic:
26
+ tag_id: 10
27
+ child_id: 12
28
+
29
+ nature_domestic:
30
+ tag_id: 3
31
+ child_id: 12
32
+
33
+ nature_animals_domestic_cat:
34
+ tag_id: 12
35
+ child_id: 13
36
+
37
+ animals_cat:
38
+ tag_id: 10
39
+ child_id: 13
40
+
41
+ nature_cat:
42
+ tag_id: 3
43
+ child_id: 13
44
+
45
+ nature_animals_domestic_cat_kitten:
46
+ tag_id: 13
47
+ child_id: 14
48
+
49
+ animals_kitten:
50
+ tag_id: 10
51
+ child_id: 14
52
+
53
+ nature_kitten:
54
+ tag_id: 3
55
+ child_id: 14
56
+
57
+ domestic_kitten:
58
+ tag_id: 12
59
+ child_id: 14
60
+
61
+
62
+ cat_synonym_pussy:
63
+ tag_id: 13
64
+ child_id: 15
65
+
66
+ cat_synonym_kitty:
67
+ tag_id: 13
68
+ child_id: 18
69
+
70
+ horse_synonym_racehorse:
71
+ tag_id: 11
72
+ child_id: 16
73
+
74
+ question_synonym_problem:
75
+ tag_id: 4
76
+ child_id: 17
77
+
78
+
79
+ cat_synonym_pussy_rev:
80
+ child_id: 13
81
+ tag_id: 15
82
+
83
+ cat_synonym_kitty_rev:
84
+ child_id: 13
85
+ tag_id: 18
86
+
87
+ horse_synonym_racehorse_rev:
88
+ child_id: 11
89
+ tag_id: 16
90
+
91
+ question_synonym_problem_rev:
92
+ child_id: 4
93
+ tag_id: 17
94
+
95
+
96
+
@@ -0,0 +1,9 @@
1
+ class User < ActiveRecord::Base
2
+ has_many :posts
3
+ has_many :photos
4
+
5
+ has_many :subscriptions
6
+ has_many :magazines, :through => :subscriptions
7
+
8
+ has_many :videos
9
+ end
@@ -0,0 +1,7 @@
1
+ jonathan:
2
+ id: 1
3
+ name: Jonathan
4
+
5
+ sam:
6
+ id: 2
7
+ name: Sam
@@ -0,0 +1,3 @@
1
+ class Video < ActiveRecord::Base
2
+ acts_as_taggable
3
+ end
@@ -0,0 +1,9 @@
1
+ jonathan_good_cat:
2
+ id: 1
3
+ user_id: 1
4
+ title: Good cat
5
+
6
+ sam_kitten:
7
+ id: 2
8
+ user_id: 2
9
+ title: Kitten
@@ -0,0 +1,359 @@
1
+ require 'test_helper'
2
+
3
+ ActiverecordTestConnector.setup
4
+ abort unless ActiverecordTestConnector.able_to_connect
5
+
6
+ class ActsAsTaggableTest < ActiveSupport::TestCase
7
+ fixtures :tags, :taggings, :posts, :users, :photos, :subscriptions, :magazines, :tags_transitive_hierarchy
8
+
9
+ def test_find_related_tags_with
10
+ assert_equivalent [tags(:good), tags(:bad), tags(:question)], Post.find_related_tags("nature").to_a
11
+ assert_equivalent [tags(:nature)], Post.find_related_tags([tags(:good)])
12
+ assert_equivalent [tags(:bad), tags(:question)], Post.find_related_tags(["Very Good", "Nature"])
13
+ assert_equivalent [tags(:bad), tags(:question)], Post.find_related_tags([tags(:good), tags(:nature)])
14
+ end
15
+
16
+ def test_find_related_tags_with_non_existent_tags
17
+ assert_equal [], Post.find_related_tags("ABCDEFG")
18
+ assert_equal [], Post.find_related_tags(['HIJKLM'])
19
+ end
20
+
21
+ def test_find_related_tags_with_nothing
22
+ assert_equal [], Post.find_related_tags("")
23
+ assert_equal [], Post.find_related_tags([])
24
+ end
25
+
26
+ def test_find_tagged_with
27
+ assert_equivalent [posts(:jonathan_sky), posts(:sam_flowers)], Post.find_tagged_with('"Very good"')
28
+ assert_equal Post.find_tagged_with('"Very good"'), Post.find_tagged_with(['Very good'])
29
+ assert_equal Post.find_tagged_with('"Very good"'), Post.find_tagged_with([tags(:good)])
30
+
31
+ assert_equivalent [photos(:jonathan_dog), photos(:sam_flower), photos(:sam_sky)], Photo.find_tagged_with('Nature')
32
+ assert_equal Photo.find_tagged_with('Nature'), Photo.find_tagged_with(['Nature'])
33
+ assert_equal Photo.find_tagged_with('Nature'), Photo.find_tagged_with([tags(:nature)])
34
+
35
+ assert_equivalent [photos(:jonathan_bad_cat), photos(:jonathan_dog), photos(:jonathan_questioning_dog)], Photo.find_tagged_with('"Crazy animal" Bad')
36
+ assert_equal Photo.find_tagged_with('"Crazy animal" Bad'), Photo.find_tagged_with(['Crazy animal', 'Bad'])
37
+ assert_equal Photo.find_tagged_with('"Crazy animal" Bad'), Photo.find_tagged_with([tags(:animal), tags(:bad)])
38
+ end
39
+
40
+ def test_find_tagged_with_nothing
41
+ assert_empty Post.find_tagged_with("")
42
+ assert_empty Post.find_tagged_with([])
43
+ end
44
+
45
+ def test_find_tagged_with_nonexistant_tags
46
+ assert_empty Post.find_tagged_with('ABCDEFG')
47
+ assert_empty Photo.find_tagged_with(['HIJKLM'])
48
+ assert_empty Photo.find_tagged_with([Tag.new(:name => 'unsaved tag')])
49
+ end
50
+
51
+ def test_find_tagged_with_match_all
52
+ assert_equivalent [photos(:jonathan_dog)], Photo.find_tagged_with('Crazy animal, "Nature"', :match_all => true)
53
+ end
54
+
55
+ def test_find_tagged_with_match_all_and_include
56
+ assert_equivalent [posts(:jonathan_sky), posts(:sam_flowers)], Post.find_tagged_with(['Very good', 'Nature'], :match_all => true, :include => :tags)
57
+ end
58
+
59
+ def test_find_tagged_with_conditions
60
+ assert_empty Post.where("1 = 0").find_tagged_with('"Very good", Nature')
61
+ end
62
+
63
+ def test_find_tagged_with_duplicates_options_hash
64
+ options = { :conditions => '1=1' }.freeze
65
+ assert_nothing_raised { Post.find_tagged_with("Nature", options) }
66
+ end
67
+
68
+ def test_find_tagged_with_exclusions
69
+ assert_equivalent [photos(:jonathan_questioning_dog), photos(:jonathan_bad_cat)], Photo.find_tagged_with("Nature", :exclude => true)
70
+ assert_equivalent [posts(:jonathan_grass), posts(:jonathan_rain), posts(:jonathan_cloudy), posts(:jonathan_still_cloudy)], Post.find_tagged_with("'Very good', Bad", :exclude => true)
71
+ end
72
+
73
+ def test_find_tagged_with_respects_custom_table_name
74
+ Tagging.table_name = "categorisations"
75
+ Tag.table_name = "categories"
76
+
77
+ sql = Photo.find_tagged_with("Hello").to_sql
78
+
79
+ assert_no_match(/ taggings /, sql)
80
+ assert_no_match(/ tags /, sql)
81
+
82
+ assert_match(/ categorisations /, sql)
83
+ assert_match(/ categories /, sql)
84
+ ensure
85
+ Tagging.table_name = "taggings"
86
+ Tag.table_name = "tags"
87
+ end
88
+
89
+ def test_include_tags_on_find_tagged_with
90
+ assert_nothing_raised do
91
+ Photo.find_tagged_with('Nature', :include => :tags)
92
+ Photo.find_tagged_with("Nature", :include => { :taggings => :tag })
93
+ end
94
+ end
95
+
96
+ def test_basic_tag_counts_on_class
97
+ assert_tag_counts Post.tag_counts, :good => 2, :nature => 7, :question => 1, :bad => 1
98
+ assert_tag_counts Photo.tag_counts, :good => 1, :nature => 3, :question => 1, :bad => 1, :animal => 3
99
+ end
100
+
101
+ def test_tag_counts_on_class_with_date_conditions
102
+ assert_tag_counts Post.tag_counts(:start_at => Date.new(2006, 8, 4)), :good => 1, :nature => 5, :question => 1, :bad => 1
103
+ assert_tag_counts Post.tag_counts(:end_at => Date.new(2006, 8, 6)), :good => 1, :nature => 4, :question => 1
104
+ 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
105
+
106
+ 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
107
+ end
108
+
109
+ def test_tag_counts_on_class_with_frequencies
110
+ assert_tag_counts Photo.tag_counts(:at_least => 2), :nature => 3, :animal => 3
111
+ assert_tag_counts Photo.tag_counts(:at_most => 2), :good => 1, :question => 1, :bad => 1
112
+ end
113
+
114
+ def test_tag_counts_on_class_with_frequencies_and_conditions
115
+ assert_tag_counts Photo.tag_counts(:at_least => 2, :conditions => '1=1'), :nature => 3, :animal => 3
116
+ end
117
+
118
+ def test_tag_counts_duplicates_options_hash
119
+ options = { :at_least => 2, :conditions => '1=1' }.freeze
120
+ assert_nothing_raised { Photo.tag_counts(options) }
121
+ end
122
+
123
+ def test_tag_counts_with_limit
124
+ assert_equal 2, Photo.tag_counts(:limit => 2).size
125
+ assert_equal 1, Post.tag_counts(:at_least => 4, :limit => 2).size
126
+ end
127
+
128
+ def test_tag_counts_with_limit_and_order
129
+ assert_equal [tags(:nature), tags(:good)], Post.tag_counts(:order => 'count desc', :limit => 2)
130
+ end
131
+
132
+ def test_tag_counts_on_association
133
+ assert_tag_counts users(:jonathan).posts.tag_counts, :good => 1, :nature => 5, :question => 1
134
+ assert_tag_counts users(:sam).posts.tag_counts, :good => 1, :nature => 2, :bad => 1
135
+
136
+ assert_tag_counts users(:jonathan).photos.tag_counts, :animal => 3, :nature => 1, :question => 1, :bad => 1
137
+ assert_tag_counts users(:sam).photos.tag_counts, :nature => 2, :good => 1
138
+ end
139
+
140
+ def test_tag_counts_on_association_with_options
141
+ assert_equal [], users(:jonathan).posts.tag_counts(:conditions => '1=0')
142
+ assert_tag_counts users(:jonathan).posts.tag_counts(:at_most => 2), :good => 1, :question => 1
143
+ end
144
+
145
+ def test_tag_counts_on_has_many_through
146
+ assert_tag_counts users(:jonathan).magazines.tag_counts, :good => 1
147
+ end
148
+
149
+ def test_tag_counts_respects_custom_table_names
150
+ Tagging.table_name = "categorisations"
151
+ Tag.table_name = "categories"
152
+
153
+ options = Photo.find_options_for_tag_counts(:start_at => 2.weeks.ago, :end_at => Date.today)
154
+ sql = options.values.join(' ')
155
+
156
+ assert_no_match /taggings/, sql
157
+ assert_no_match /tags/, sql
158
+
159
+ assert_match /categorisations/, sql
160
+ assert_match /categories/, sql
161
+ ensure
162
+ Tagging.table_name = "taggings"
163
+ Tag.table_name = "tags"
164
+ end
165
+
166
+ def test_tag_list_reader
167
+ assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
168
+ assert_equivalent ["Bad", "Crazy animal"], photos(:jonathan_bad_cat).tag_list
169
+ end
170
+
171
+ def test_reassign_tag_list
172
+ assert_equivalent ["Nature", "Question"], posts(:jonathan_rain).tag_list
173
+ posts(:jonathan_rain).taggings.reload
174
+
175
+ # Only an update of the posts table should be executed
176
+ # TODO 3 = begin + update + commit
177
+ assert_queries 3 do
178
+ posts(:jonathan_rain).update_attributes!(:tag_list => posts(:jonathan_rain).tag_list.to_s)
179
+ end
180
+
181
+ assert_equivalent ["Nature", "Question"], posts(:jonathan_rain).tag_list
182
+ end
183
+
184
+ def test_new_tags
185
+ assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
186
+ posts(:jonathan_sky).update_attributes!(:tag_list => "#{posts(:jonathan_sky).tag_list}, One, Two")
187
+ assert_equivalent ["Very good", "Nature", "One", "Two"], posts(:jonathan_sky).tag_list
188
+ end
189
+
190
+ def test_remove_tag
191
+ assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
192
+ posts(:jonathan_sky).update_attributes!(:tag_list => "Nature")
193
+ assert_equivalent ["Nature"], posts(:jonathan_sky).tag_list
194
+ end
195
+
196
+ def test_change_case_of_tags
197
+ original_tag_names = photos(:jonathan_questioning_dog).tag_list
198
+ photos(:jonathan_questioning_dog).update_attributes!(:tag_list => photos(:jonathan_questioning_dog).tag_list.to_s.upcase)
199
+
200
+ # The new tag list is not uppercase becuase the AR finders are not case-sensitive
201
+ # and find the old tags when re-tagging with the uppercase tags.
202
+ assert_equivalent original_tag_names, photos(:jonathan_questioning_dog).reload.tag_list
203
+ end
204
+
205
+ def test_remove_and_add_tag
206
+ assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
207
+ posts(:jonathan_sky).update_attributes!(:tag_list => "Nature, Beautiful")
208
+ assert_equivalent ["Nature", "Beautiful"], posts(:jonathan_sky).tag_list
209
+ end
210
+
211
+ def test_tags_not_saved_if_validation_fails
212
+ assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
213
+ assert !posts(:jonathan_sky).update_attributes(:tag_list => "One, Two", :text => "")
214
+ assert_equivalent ["Very good", "Nature"], Post.find(posts(:jonathan_sky).id).tag_list
215
+ end
216
+
217
+ def test_tag_list_accessors_on_new_record
218
+ p = Post.new(:text => 'Test')
219
+
220
+ assert p.tag_list.blank?
221
+ p.tag_list = "One, Two"
222
+ assert_equal "One, Two", p.tag_list.to_s
223
+ end
224
+
225
+ def test_clear_tag_list_with_nil
226
+ p = photos(:jonathan_questioning_dog)
227
+
228
+ assert !p.tag_list.blank?
229
+ assert p.update_attributes(:tag_list => nil)
230
+ assert p.tag_list.blank?
231
+
232
+ assert p.reload.tag_list.blank?
233
+ end
234
+
235
+ def test_clear_tag_list_with_string
236
+ p = photos(:jonathan_questioning_dog)
237
+
238
+ assert !p.tag_list.blank?
239
+ assert p.update_attributes(:tag_list => ' ')
240
+ assert p.tag_list.blank?
241
+
242
+ assert p.reload.tag_list.blank?
243
+ end
244
+
245
+ def test_tag_list_reset_on_reload
246
+ p = photos(:jonathan_questioning_dog)
247
+ assert !p.tag_list.blank?
248
+ p.tag_list = nil
249
+ assert p.tag_list.blank?
250
+ assert !p.reload.tag_list.blank?
251
+ end
252
+
253
+ def test_instance_tag_counts
254
+ assert_tag_counts posts(:jonathan_sky).tag_counts, :good => 2, :nature => 7
255
+ end
256
+
257
+ def test_tag_list_populated_when_cache_nil
258
+ assert_nil posts(:jonathan_sky).cached_tag_list
259
+ posts(:jonathan_sky).save!
260
+ assert_equal posts(:jonathan_sky).tag_list.to_s, posts(:jonathan_sky).cached_tag_list
261
+ end
262
+
263
+ def test_cached_tag_list_used
264
+ posts(:jonathan_sky).save!
265
+ posts(:jonathan_sky).reload
266
+
267
+ assert_no_queries do
268
+ assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
269
+ end
270
+ end
271
+
272
+ def test_cached_tag_list_not_used
273
+ # Load fixture and column information
274
+ posts(:jonathan_sky).taggings(:reload)
275
+
276
+ assert_queries 1 do
277
+ # Tags association will be loaded
278
+ posts(:jonathan_sky).tag_list
279
+ end
280
+ end
281
+
282
+ def test_cached_tag_list_updated
283
+ assert_nil posts(:jonathan_sky).cached_tag_list
284
+ posts(:jonathan_sky).save!
285
+
286
+ assert_equivalent ["Very good", "Nature"], TagList.from(posts(:jonathan_sky).cached_tag_list)
287
+ posts(:jonathan_sky).update_attributes!(:tag_list => "None")
288
+
289
+ assert_equal 'None', posts(:jonathan_sky).cached_tag_list
290
+ assert_equal 'None', posts(:jonathan_sky).reload.cached_tag_list
291
+ end
292
+
293
+ def test_clearing_cached_tag_list
294
+ # Generate the cached tag list
295
+ posts(:jonathan_sky).save!
296
+
297
+ posts(:jonathan_sky).update_attributes!(:tag_list => "")
298
+ assert_equal "", posts(:jonathan_sky).cached_tag_list
299
+ end
300
+
301
+ def test_find_tagged_with_using_sti
302
+ special_post = SpecialPost.create!(:text => "Test", :tag_list => "Random")
303
+
304
+ assert_equal [special_post], SpecialPost.find_tagged_with("Random")
305
+ assert Post.find_tagged_with("Random").include?(special_post)
306
+ end
307
+
308
+ def test_tag_counts_using_sti
309
+ SpecialPost.create!(:text => "Test", :tag_list => "Nature")
310
+
311
+ assert_tag_counts SpecialPost.tag_counts, :nature => 1
312
+ end
313
+
314
+ def test_case_insensitivity
315
+ assert_difference "Tag.count", 1 do
316
+ Post.create!(:text => "Test", :tag_list => "one")
317
+ Post.create!(:text => "Test", :tag_list => "One")
318
+ end
319
+
320
+ assert_equal Post.find_tagged_with("Nature").to_a, Post.find_tagged_with("nature").to_a
321
+ end
322
+
323
+ def test_tag_not_destroyed_when_unused
324
+ posts(:jonathan_sky).tag_list.add("Random")
325
+ posts(:jonathan_sky).save!
326
+
327
+ assert_no_difference 'Tag.count' do
328
+ posts(:jonathan_sky).tag_list.remove("Random")
329
+ posts(:jonathan_sky).save!
330
+ end
331
+ end
332
+
333
+ def test_tag_destroyed_when_unused
334
+ Tag.destroy_unused = true
335
+
336
+ posts(:jonathan_sky).tag_list.add("Random")
337
+ posts(:jonathan_sky).save!
338
+
339
+ assert_difference 'Tag.count', -1 do
340
+ posts(:jonathan_sky).tag_list.remove("Random")
341
+ posts(:jonathan_sky).save!
342
+ end
343
+ ensure
344
+ Tag.destroy_unused = false
345
+ end
346
+ end
347
+
348
+ #class ActsAsTaggableOnSteroidsFormTest < ActiveSupport::TestCase
349
+ class ActsAsTaggableOnSteroidsFormTest < ActionView::TestCase
350
+ include ActionView::Helpers::FormHelper
351
+
352
+ fixtures :tags, :taggings, :posts, :users, :photos
353
+
354
+ def test_tag_list_contents
355
+ fields_for :post, posts(:jonathan_sky) do |f|
356
+ assert_match /Very good, Nature/, f.text_field(:tag_list)
357
+ end
358
+ end
359
+ end