acts-as-taggable-on 2.0.0 → 3.2.2

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 (86) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.travis.yml +23 -0
  4. data/Appraisals +20 -0
  5. data/CHANGELOG.md +112 -0
  6. data/CONTRIBUTING.md +44 -0
  7. data/Gemfile +9 -5
  8. data/Guardfile +5 -0
  9. data/{MIT-LICENSE → LICENSE.md} +1 -1
  10. data/README.md +385 -0
  11. data/Rakefile +13 -53
  12. data/UPGRADING.md +13 -0
  13. data/acts-as-taggable-on.gemspec +38 -0
  14. data/{lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb → db/migrate/1_acts_as_taggable_on_migration.rb} +5 -3
  15. data/db/migrate/2_add_missing_unique_indices.rb +19 -0
  16. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +14 -0
  17. data/gemfiles/activerecord_3.2.gemfile +15 -0
  18. data/gemfiles/activerecord_4.0.gemfile +15 -0
  19. data/gemfiles/activerecord_4.1.gemfile +15 -0
  20. data/gemfiles/activerecord_edge.gemfile +16 -0
  21. data/lib/acts-as-taggable-on.rb +65 -22
  22. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +53 -24
  23. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +120 -42
  24. data/lib/acts_as_taggable_on/acts_as_taggable_on/compatibility.rb +35 -0
  25. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +309 -124
  26. data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +36 -0
  27. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +96 -72
  28. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +43 -36
  29. data/lib/acts_as_taggable_on/engine.rb +5 -0
  30. data/lib/acts_as_taggable_on/tag.rb +133 -63
  31. data/lib/acts_as_taggable_on/tag_list.rb +162 -81
  32. data/lib/acts_as_taggable_on/taggable.rb +101 -0
  33. data/lib/acts_as_taggable_on/{acts_as_tagger.rb → tagger.rb} +34 -13
  34. data/lib/acts_as_taggable_on/tagging.rb +28 -18
  35. data/lib/acts_as_taggable_on/tags_helper.rb +12 -14
  36. data/lib/acts_as_taggable_on/utils.rb +62 -0
  37. data/lib/acts_as_taggable_on/version.rb +4 -0
  38. data/lib/acts_as_taggable_on.rb +6 -0
  39. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +189 -195
  40. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +78 -81
  41. data/spec/acts_as_taggable_on/caching_spec.rb +76 -0
  42. data/spec/acts_as_taggable_on/related_spec.rb +89 -0
  43. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +210 -0
  44. data/spec/acts_as_taggable_on/tag_list_spec.rb +149 -61
  45. data/spec/acts_as_taggable_on/tag_spec.rb +245 -65
  46. data/spec/acts_as_taggable_on/taggable_spec.rb +776 -187
  47. data/spec/acts_as_taggable_on/tagger_spec.rb +119 -70
  48. data/spec/acts_as_taggable_on/tagging_spec.rb +17 -21
  49. data/spec/acts_as_taggable_on/tags_helper_spec.rb +38 -22
  50. data/spec/acts_as_taggable_on/utils_spec.rb +15 -0
  51. data/spec/internal/app/models/altered_inheriting_taggable_model.rb +3 -0
  52. data/spec/internal/app/models/cached_model.rb +3 -0
  53. data/spec/internal/app/models/cached_model_with_array.rb +5 -0
  54. data/spec/internal/app/models/company.rb +15 -0
  55. data/spec/internal/app/models/inheriting_taggable_model.rb +2 -0
  56. data/spec/internal/app/models/market.rb +2 -0
  57. data/spec/internal/app/models/models.rb +90 -0
  58. data/spec/internal/app/models/non_standard_id_taggable_model.rb +8 -0
  59. data/spec/internal/app/models/ordered_taggable_model.rb +4 -0
  60. data/spec/internal/app/models/other_cached_model.rb +3 -0
  61. data/spec/internal/app/models/other_taggable_model.rb +4 -0
  62. data/spec/internal/app/models/student.rb +2 -0
  63. data/spec/internal/app/models/taggable_model.rb +13 -0
  64. data/spec/internal/app/models/untaggable_model.rb +3 -0
  65. data/spec/internal/app/models/user.rb +3 -0
  66. data/spec/internal/config/database.yml.sample +19 -0
  67. data/spec/internal/db/schema.rb +97 -0
  68. data/spec/schema.rb +57 -18
  69. data/spec/spec_helper.rb +12 -47
  70. data/spec/support/array.rb +9 -0
  71. data/spec/support/database.rb +42 -0
  72. data/spec/support/database_cleaner.rb +21 -0
  73. metadata +286 -63
  74. data/CHANGELOG +0 -25
  75. data/README.rdoc +0 -221
  76. data/VERSION +0 -1
  77. data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +0 -7
  78. data/generators/acts_as_taggable_on_migration/templates/migration.rb +0 -29
  79. data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +0 -53
  80. data/lib/acts_as_taggable_on/compatibility/Gemfile +0 -6
  81. data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +0 -17
  82. data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +0 -31
  83. data/rails/init.rb +0 -1
  84. data/spec/bm.rb +0 -52
  85. data/spec/models.rb +0 -30
  86. /data/{spec/spec.opts → .rspec} +0 -0
@@ -1,291 +1,880 @@
1
- require File.dirname(__FILE__) + '/../spec_helper'
1
+ # encoding: utf-8
2
+ require 'spec_helper'
2
3
 
3
- describe "Taggable" do
4
+ describe 'Taggable To Preserve Order' do
4
5
  before(:each) do
5
- clean_database!
6
- @taggable = TaggableModel.new(:name => "Bob Jones")
6
+ @taggable = OrderedTaggableModel.new(name: 'Bob Jones')
7
7
  end
8
8
 
9
- it "should have tag types" do
9
+
10
+ it 'should have tag associations' do
11
+ [:tags, :colours].each do |type|
12
+ expect(@taggable.respond_to?(type)).to be_truthy
13
+ expect(@taggable.respond_to?("#{type.to_s.singularize}_taggings")).to be_truthy
14
+ end
15
+ end
16
+
17
+ it 'should have tag methods' do
18
+ [:tags, :colours].each do |type|
19
+ expect(@taggable.respond_to?("#{type.to_s.singularize}_list")).to be_truthy
20
+ expect(@taggable.respond_to?("#{type.to_s.singularize}_list=")).to be_truthy
21
+ expect(@taggable.respond_to?("all_#{type}_list")).to be_truthy
22
+ end
23
+ end
24
+
25
+ it 'should return tag list in the order the tags were created' do
26
+ # create
27
+ @taggable.tag_list = 'rails, ruby, css'
28
+ expect(@taggable.instance_variable_get('@tag_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy
29
+
30
+ expect(-> {
31
+ @taggable.save
32
+ }).to change(ActsAsTaggableOn::Tag, :count).by(3)
33
+
34
+ @taggable.reload
35
+ expect(@taggable.tag_list).to eq(%w(rails ruby css))
36
+
37
+ # update
38
+ @taggable.tag_list = 'pow, ruby, rails'
39
+ @taggable.save
40
+
41
+ @taggable.reload
42
+ expect(@taggable.tag_list).to eq(%w(pow ruby rails))
43
+
44
+ # update with no change
45
+ @taggable.tag_list = 'pow, ruby, rails'
46
+ @taggable.save
47
+
48
+ @taggable.reload
49
+ expect(@taggable.tag_list).to eq(%w(pow ruby rails))
50
+
51
+ # update to clear tags
52
+ @taggable.tag_list = ''
53
+ @taggable.save
54
+
55
+ @taggable.reload
56
+ expect(@taggable.tag_list).to be_empty
57
+ end
58
+
59
+ it 'should return tag objects in the order the tags were created' do
60
+ # create
61
+ @taggable.tag_list = 'pow, ruby, rails'
62
+ expect(@taggable.instance_variable_get('@tag_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy
63
+
64
+ expect(-> {
65
+ @taggable.save
66
+ }).to change(ActsAsTaggableOn::Tag, :count).by(3)
67
+
68
+ @taggable.reload
69
+ expect(@taggable.tags.map { |t| t.name }).to eq(%w(pow ruby rails))
70
+
71
+ # update
72
+ @taggable.tag_list = 'rails, ruby, css, pow'
73
+ @taggable.save
74
+
75
+ @taggable.reload
76
+ expect(@taggable.tags.map { |t| t.name }).to eq(%w(rails ruby css pow))
77
+ end
78
+
79
+ it 'should return tag objects in tagging id order' do
80
+ # create
81
+ @taggable.tag_list = 'pow, ruby, rails'
82
+ @taggable.save
83
+
84
+ @taggable.reload
85
+ ids = @taggable.tags.map { |t| t.taggings.first.id }
86
+ expect(ids).to eq(ids.sort)
87
+
88
+ # update
89
+ @taggable.tag_list = 'rails, ruby, css, pow'
90
+ @taggable.save
91
+
92
+ @taggable.reload
93
+ ids = @taggable.tags.map { |t| t.taggings.first.id }
94
+ expect(ids).to eq(ids.sort)
95
+ end
96
+ end
97
+
98
+ describe 'Taggable' do
99
+ before(:each) do
100
+ @taggable = TaggableModel.new(name: 'Bob Jones')
101
+ @taggables = [@taggable, TaggableModel.new(name: 'John Doe')]
102
+ end
103
+
104
+ it 'should have tag types' do
10
105
  [:tags, :languages, :skills, :needs, :offerings].each do |type|
11
- TaggableModel.tag_types.should include type
106
+ expect(TaggableModel.tag_types).to include type
12
107
  end
13
108
 
14
- @taggable.tag_types.should == TaggableModel.tag_types
109
+ expect(@taggable.tag_types).to eq(TaggableModel.tag_types)
15
110
  end
16
111
 
17
- it "should have tag_counts_on" do
18
- TaggableModel.tag_counts_on(:tags).all.should be_empty
112
+ it 'should have tag_counts_on' do
113
+ expect(TaggableModel.tag_counts_on(:tags)).to be_empty
19
114
 
20
- @taggable.tag_list = ["awesome", "epic"]
115
+ @taggable.tag_list = %w(awesome epic)
21
116
  @taggable.save
22
117
 
23
- TaggableModel.tag_counts_on(:tags).length.should == 2
24
- @taggable.tag_counts_on(:tags).length.should == 2
118
+ expect(TaggableModel.tag_counts_on(:tags).length).to eq(2)
119
+ expect(@taggable.tag_counts_on(:tags).length).to eq(2)
25
120
  end
26
121
 
27
- it "should be able to create tags" do
28
- @taggable.skill_list = "ruby, rails, css"
29
- @taggable.instance_variable_get("@skill_list").instance_of?(TagList).should be_true
30
-
31
- lambda {
122
+ it 'should have tags_on' do
123
+ expect(TaggableModel.tags_on(:tags)).to be_empty
124
+
125
+ @taggable.tag_list = %w(awesome epic)
126
+ @taggable.save
127
+
128
+ expect(TaggableModel.tags_on(:tags).length).to eq(2)
129
+ expect(@taggable.tags_on(:tags).length).to eq(2)
130
+ end
131
+
132
+ it 'should return [] right after create' do
133
+ blank_taggable = TaggableModel.new(name: 'Bob Jones')
134
+ expect(blank_taggable.tag_list).to be_empty
135
+ end
136
+
137
+ it 'should be able to create tags' do
138
+ @taggable.skill_list = 'ruby, rails, css'
139
+ expect(@taggable.instance_variable_get('@skill_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy
140
+
141
+ expect(-> {
32
142
  @taggable.save
33
- }.should change(Tag, :count).by(3)
34
-
143
+ }).to change(ActsAsTaggableOn::Tag, :count).by(3)
144
+
35
145
  @taggable.reload
36
- @taggable.skill_list.sort.should == %w(ruby rails css).sort
146
+ expect(@taggable.skill_list.sort).to eq(%w(ruby rails css).sort)
37
147
  end
38
148
 
39
- it "should be able to create tags through the tag list directly" do
40
- @taggable.tag_list_on(:test).add("hello")
41
- @taggable.tag_list_cache_on(:test).should_not be_empty
42
- @taggable.tag_list_on(:test).should == ["hello"]
43
-
149
+ it 'should be able to create tags through the tag list directly' do
150
+ @taggable.tag_list_on(:test).add('hello')
151
+ expect(@taggable.tag_list_cache_on(:test)).to_not be_empty
152
+ expect(@taggable.tag_list_on(:test)).to eq(['hello'])
153
+
44
154
  @taggable.save
45
155
  @taggable.save_tags
46
-
156
+
47
157
  @taggable.reload
48
- @taggable.tag_list_on(:test).should == ["hello"]
158
+ expect(@taggable.tag_list_on(:test)).to eq(['hello'])
49
159
  end
50
160
 
51
- it "should differentiate between contexts" do
52
- @taggable.skill_list = "ruby, rails, css"
53
- @taggable.tag_list = "ruby, bob, charlie"
161
+ it 'should differentiate between contexts' do
162
+ @taggable.skill_list = 'ruby, rails, css'
163
+ @taggable.tag_list = 'ruby, bob, charlie'
54
164
  @taggable.save
55
165
  @taggable.reload
56
- @taggable.skill_list.should include("ruby")
57
- @taggable.skill_list.should_not include("bob")
166
+ expect(@taggable.skill_list).to include('ruby')
167
+ expect(@taggable.skill_list).to_not include('bob')
58
168
  end
59
169
 
60
- it "should be able to remove tags through list alone" do
61
- @taggable.skill_list = "ruby, rails, css"
170
+ it 'should be able to remove tags through list alone' do
171
+ @taggable.skill_list = 'ruby, rails, css'
62
172
  @taggable.save
63
173
  @taggable.reload
64
- @taggable.should have(3).skills
65
- @taggable.skill_list = "ruby, rails"
174
+ expect(@taggable.skills.count).to eq(3)
175
+ @taggable.skill_list = 'ruby, rails'
66
176
  @taggable.save
67
177
  @taggable.reload
68
- @taggable.should have(2).skills
178
+ expect(@taggable.skills.count).to eq(2)
179
+ end
180
+
181
+ it 'should be able to select taggables by subset of tags using ActiveRelation methods' do
182
+ @taggables[0].tag_list = 'bob'
183
+ @taggables[1].tag_list = 'charlie'
184
+ @taggables[0].skill_list = 'ruby'
185
+ @taggables[1].skill_list = 'css'
186
+ @taggables.each { |taggable| taggable.save }
187
+
188
+ @found_taggables_by_tag = TaggableModel.joins(:tags).where(tags: {name: ['bob']})
189
+ @found_taggables_by_skill = TaggableModel.joins(:skills).where(tags: {name: ['ruby']})
190
+
191
+ expect(@found_taggables_by_tag).to include @taggables[0]
192
+ expect(@found_taggables_by_tag).to_not include @taggables[1]
193
+ expect(@found_taggables_by_skill).to include @taggables[0]
194
+ expect(@found_taggables_by_skill).to_not include @taggables[1]
195
+ end
196
+
197
+ it 'should be able to find by tag' do
198
+ @taggable.skill_list = 'ruby, rails, css'
199
+ @taggable.save
200
+
201
+ expect(TaggableModel.tagged_with('ruby').first).to eq(@taggable)
69
202
  end
70
203
 
71
- it "should be able to find by tag" do
72
- @taggable.skill_list = "ruby, rails, css"
204
+ it 'should be able to get a count with find by tag when using a group by' do
205
+ @taggable.skill_list = 'ruby'
73
206
  @taggable.save
74
207
 
75
- TaggableModel.tagged_with("ruby").first.should == @taggable
208
+ expect(TaggableModel.tagged_with('ruby').group(:created_at).count.count).to eq(1)
76
209
  end
77
210
 
78
- it "should be able to find by tag with context" do
79
- @taggable.skill_list = "ruby, rails, css"
80
- @taggable.tag_list = "bob, charlie"
211
+ it "shouldn't generate a query with DISTINCT by default" do
212
+ @taggable.skill_list = 'ruby, rails, css'
81
213
  @taggable.save
82
214
 
83
- TaggableModel.tagged_with("ruby").first.should == @taggable
84
- TaggableModel.tagged_with("ruby, css").first.should == @taggable
85
- TaggableModel.tagged_with("bob", :on => :skills).first.should_not == @taggable
86
- TaggableModel.tagged_with("bob", :on => :tags).first.should == @taggable
215
+ expect(TaggableModel.tagged_with('ruby').to_sql).to_not match /DISTINCT/
87
216
  end
88
217
 
89
- it "should not care about case" do
90
- bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby")
91
- frank = TaggableModel.create(:name => "Frank", :tag_list => "Ruby")
218
+ it 'should be able to find by tag with context' do
219
+ @taggable.skill_list = 'ruby, rails, css'
220
+ @taggable.tag_list = 'bob, charlie'
221
+ @taggable.save
92
222
 
93
- Tag.find(:all).size.should == 1
94
- TaggableModel.tagged_with("ruby").to_a.should == TaggableModel.tagged_with("Ruby").to_a
223
+ expect(TaggableModel.tagged_with('ruby').first).to eq(@taggable)
224
+ expect(TaggableModel.tagged_with('ruby, css').first).to eq(@taggable)
225
+ expect(TaggableModel.tagged_with('bob', on: :skills).first).to_not eq(@taggable)
226
+ expect(TaggableModel.tagged_with('bob', on: :tags).first).to eq(@taggable)
95
227
  end
96
228
 
97
- it "should be able to get tag counts on model as a whole" do
98
- bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
99
- frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
100
- charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
101
- TaggableModel.tag_counts.all.should_not be_empty
102
- TaggableModel.skill_counts.all.should_not be_empty
229
+ it 'should not care about case' do
230
+ TaggableModel.create(name: 'Bob', tag_list: 'ruby')
231
+ TaggableModel.create(name: 'Frank', tag_list: 'Ruby')
232
+
233
+ expect(ActsAsTaggableOn::Tag.all.size).to eq(1)
234
+ expect(TaggableModel.tagged_with('ruby').to_a).to eq(TaggableModel.tagged_with('Ruby').to_a)
103
235
  end
104
236
 
105
- it "should be able to get all tag counts on model as whole" do
106
- bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
107
- frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
108
- charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
237
+ it 'should be able to find by tags with other joins in the query' do
238
+ @taggable.skill_list = 'ruby, rails, css'
239
+ @taggable.tag_list = 'bob, charlie'
240
+ @taggable.save
109
241
 
110
- TaggableModel.all_tag_counts.all.should_not be_empty
111
- TaggableModel.all_tag_counts.first.count.should == 3 # ruby
242
+ expect(TaggableModel.group(:created_at).tagged_with(['bob', 'css'], :any => true).first).to eq(@taggable)
243
+
244
+ bob = TaggableModel.create(:name => 'Bob', :tag_list => 'ruby, rails, css')
245
+ frank = TaggableModel.create(:name => 'Frank', :tag_list => 'ruby, rails')
246
+ charlie = TaggableModel.create(:name => 'Charlie', :skill_list => 'ruby, java')
247
+
248
+ # Test for explicit distinct in select
249
+ bob.untaggable_models.create!
250
+ frank.untaggable_models.create!
251
+ charlie.untaggable_models.create!
252
+
253
+ expect(TaggableModel.select('distinct(taggable_models.id), taggable_models.*').joins(:untaggable_models).tagged_with(['css', 'java'], :any => true).to_a.sort).to eq([bob, charlie].sort)
254
+
255
+ expect(TaggableModel.select('distinct(taggable_models.id), taggable_models.*').joins(:untaggable_models).tagged_with(['rails', 'ruby'], :any => false).to_a.sort).to eq([bob, frank].sort)
112
256
  end
113
257
 
114
- if ActiveRecord::VERSION::MAJOR >= 3
115
- it "should not return read-only records" do
116
- TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
117
- TaggableModel.tagged_with("ruby").first.should_not be_readonly
118
- end
119
- else
120
- xit "should not return read-only records" do
121
- # apparantly, there is no way to set readonly to false in a scope if joins are made
258
+ unless ActsAsTaggableOn::Utils.using_sqlite?
259
+ it 'should not care about case for unicode names' do
260
+ ActsAsTaggableOn.strict_case_match = false
261
+ TaggableModel.create(name: 'Anya', tag_list: 'ПРИВЕТ')
262
+ TaggableModel.create(name: 'Igor', tag_list: 'привет')
263
+ TaggableModel.create(name: 'Katia', tag_list: 'ПРИВЕТ')
264
+
265
+ expect(ActsAsTaggableOn::Tag.all.size).to eq(1)
266
+ expect(TaggableModel.tagged_with('привет').to_a).to eq(TaggableModel.tagged_with('ПРИВЕТ').to_a)
122
267
  end
123
-
124
- it "should be possible to return writable records" do
125
- TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
126
- TaggableModel.tagged_with("ruby").first(:readonly => false).should_not be_readonly
268
+ end
269
+
270
+ context 'should be able to create and find tags in languages without capitalization :' do
271
+ ActsAsTaggableOn.strict_case_match = false
272
+ {
273
+ japanese: {name: 'Chihiro', tag_list: '日本の'},
274
+ hebrew: {name: 'Salim', tag_list: 'עברית'},
275
+ chinese: {name: 'Ieie', tag_list: '中国的'},
276
+ arabic: {name: 'Yasser', tag_list: 'العربية'},
277
+ emo: {name: 'Emo', tag_list: '✏'}
278
+ }.each do |language, values|
279
+
280
+ it language do
281
+ TaggableModel.create(values)
282
+ expect(TaggableModel.tagged_with(values[:tag_list]).count).to eq(1)
283
+ end
127
284
  end
128
285
  end
129
286
 
130
- it "should be able to get scoped tag counts" do
131
- bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
132
- frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
133
- charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
287
+ it 'should be able to get tag counts on model as a whole' do
288
+ TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
289
+ TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
290
+ TaggableModel.create(name: 'Charlie', skill_list: 'ruby')
291
+ expect(TaggableModel.tag_counts).to_not be_empty
292
+ expect(TaggableModel.skill_counts).to_not be_empty
293
+ end
294
+
295
+ it 'should be able to get all tag counts on model as whole' do
296
+ TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
297
+ TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
298
+ TaggableModel.create(name: 'Charlie', skill_list: 'ruby')
134
299
 
135
- TaggableModel.tagged_with("ruby").tag_counts.first.count.should == 2 # ruby
136
- TaggableModel.tagged_with("ruby").skill_counts.first.count.should == 1 # ruby
300
+ expect(TaggableModel.all_tag_counts).to_not be_empty
301
+ expect(TaggableModel.all_tag_counts(order: 'tags.id').first.count).to eq(3) # ruby
137
302
  end
138
303
 
139
- it "should be able to get all scoped tag counts" do
140
- bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
141
- frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
142
- charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
304
+ it 'should be able to get all tags on model as whole' do
305
+ TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
306
+ TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
307
+ TaggableModel.create(name: 'Charlie', skill_list: 'ruby')
308
+
309
+ expect(TaggableModel.all_tags).to_not be_empty
310
+ expect(TaggableModel.all_tags(order: 'tags.id').first.name).to eq('ruby')
311
+ end
312
+
313
+ it 'should be able to use named scopes to chain tag finds by any tags by context' do
314
+ bob = TaggableModel.create(name: 'Bob', need_list: 'rails', offering_list: 'c++')
315
+ TaggableModel.create(name: 'Frank', need_list: 'css', offering_list: 'css')
316
+ TaggableModel.create(name: 'Steve', need_list: 'c++', offering_list: 'java')
317
+
318
+ # Let's only find those who need rails or css and are offering c++ or java
319
+ expect(TaggableModel.tagged_with(['rails, css'], on: :needs, any: true).tagged_with(['c++', 'java'], on: :offerings, any: true).to_a).to eq([bob])
320
+ end
143
321
 
144
- TaggableModel.tagged_with("ruby").all_tag_counts.first.count.should == 3 # ruby
322
+ it 'should not return read-only records' do
323
+ TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
324
+ expect(TaggableModel.tagged_with('ruby').first).to_not be_readonly
145
325
  end
146
326
 
147
- it "should be able to set a custom tag context list" do
148
- bob = TaggableModel.create(:name => "Bob")
149
- bob.set_tag_list_on(:rotors, "spinning, jumping")
150
- bob.tag_list_on(:rotors).should == ["spinning","jumping"]
327
+ it 'should be able to get scoped tag counts' do
328
+ TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
329
+ TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
330
+ TaggableModel.create(name: 'Charlie', skill_list: 'ruby')
331
+
332
+ expect(TaggableModel.tagged_with('ruby').tag_counts(order: 'tags.id').first.count).to eq(2) # ruby
333
+ expect(TaggableModel.tagged_with('ruby').skill_counts.first.count).to eq(1) # ruby
334
+ end
335
+
336
+ it 'should be able to get all scoped tag counts' do
337
+ TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
338
+ TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
339
+ TaggableModel.create(name: 'Charlie', skill_list: 'ruby')
340
+
341
+ expect(TaggableModel.tagged_with('ruby').all_tag_counts(order: 'tags.id').first.count).to eq(3) # ruby
342
+ end
343
+
344
+ it 'should be able to get all scoped tags' do
345
+ TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
346
+ TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
347
+ TaggableModel.create(name: 'Charlie', skill_list: 'ruby')
348
+
349
+ expect(TaggableModel.tagged_with('ruby').all_tags(order: 'tags.id').first.name).to eq('ruby')
350
+ end
351
+
352
+ it 'should only return tag counts for the available scope' do
353
+ frank = TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
354
+ TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
355
+ TaggableModel.create(name: 'Charlie', skill_list: 'ruby, java')
356
+
357
+ expect(TaggableModel.tagged_with('rails').all_tag_counts.size).to eq(3)
358
+ expect(TaggableModel.tagged_with('rails').all_tag_counts.any? { |tag| tag.name == 'java' }).to be_falsy
359
+
360
+ # Test specific join syntaxes:
361
+ frank.untaggable_models.create!
362
+ expect(TaggableModel.tagged_with('rails').joins(:untaggable_models).all_tag_counts.size).to eq(2)
363
+ expect(TaggableModel.tagged_with('rails').joins(untaggable_models: :taggable_model).all_tag_counts.size).to eq(2)
364
+ expect(TaggableModel.tagged_with('rails').joins([:untaggable_models]).all_tag_counts.size).to eq(2)
365
+ end
366
+
367
+ it 'should only return tags for the available scope' do
368
+ frank = TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
369
+ TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
370
+ TaggableModel.create(name: 'Charlie', skill_list: 'ruby, java')
371
+
372
+ expect(TaggableModel.tagged_with('rails').all_tags.count).to eq(3)
373
+ expect(TaggableModel.tagged_with('rails').all_tags.any? { |tag| tag.name == 'java' }).to be_falsy
374
+
375
+ # Test specific join syntaxes:
376
+ frank.untaggable_models.create!
377
+ expect(TaggableModel.tagged_with('rails').joins(:untaggable_models).all_tags.size).to eq(2)
378
+ expect(TaggableModel.tagged_with('rails').joins(untaggable_models: :taggable_model).all_tags.size).to eq(2)
379
+ expect(TaggableModel.tagged_with('rails').joins([:untaggable_models]).all_tags.size).to eq(2)
380
+ end
381
+
382
+ it 'should be able to set a custom tag context list' do
383
+ bob = TaggableModel.create(name: 'Bob')
384
+ bob.set_tag_list_on(:rotors, 'spinning, jumping')
385
+ expect(bob.tag_list_on(:rotors)).to eq(%w(spinning jumping))
151
386
  bob.save
152
387
  bob.reload
153
- bob.tags_on(:rotors).should_not be_empty
388
+ expect(bob.tags_on(:rotors)).to_not be_empty
389
+ end
390
+
391
+ it 'should be able to find tagged' do
392
+ bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive', skill_list: 'ruby, rails, css')
393
+ frank = TaggableModel.create(name: 'Frank', tag_list: 'weaker, depressed, inefficient', skill_list: 'ruby, rails, css')
394
+ steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier, more productive', skill_list: 'c++, java, ruby')
395
+
396
+ expect(TaggableModel.tagged_with('ruby', order: 'taggable_models.name').to_a).to eq([bob, frank, steve])
397
+ expect(TaggableModel.tagged_with('ruby, rails', order: 'taggable_models.name').to_a).to eq([bob, frank])
398
+ expect(TaggableModel.tagged_with(%w(ruby rails), order: 'taggable_models.name').to_a).to eq([bob, frank])
399
+ end
400
+
401
+ it 'should be able to find tagged with quotation marks' do
402
+ bob = TaggableModel.create(name: 'Bob', tag_list: "fitter, happier, more productive, 'I love the ,comma,'")
403
+ expect(TaggableModel.tagged_with("'I love the ,comma,'")).to include(bob)
404
+ end
405
+
406
+ it 'should be able to find tagged with invalid tags' do
407
+ bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive')
408
+ expect(TaggableModel.tagged_with('sad, happier')).to_not include(bob)
154
409
  end
155
410
 
156
- it "should be able to find tagged" do
157
- bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
158
- frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
159
- steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
411
+ it 'should be able to find tagged with any tag' do
412
+ bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive', skill_list: 'ruby, rails, css')
413
+ frank = TaggableModel.create(name: 'Frank', tag_list: 'weaker, depressed, inefficient', skill_list: 'ruby, rails, css')
414
+ steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier, more productive', skill_list: 'c++, java, ruby')
160
415
 
161
- TaggableModel.tagged_with("ruby", :order => 'taggable_models.name').to_a.should == [bob, frank, steve]
162
- TaggableModel.tagged_with("ruby, rails", :order => 'taggable_models.name').to_a.should == [bob, frank]
163
- TaggableModel.tagged_with(["ruby", "rails"], :order => 'taggable_models.name').to_a.should == [bob, frank]
416
+ expect(TaggableModel.tagged_with(%w(ruby java), order: 'taggable_models.name', any: true).to_a).to eq([bob, frank, steve])
417
+ expect(TaggableModel.tagged_with(%w(c++ fitter), order: 'taggable_models.name', any: true).to_a).to eq([bob, steve])
418
+ expect(TaggableModel.tagged_with(%w(depressed css), order: 'taggable_models.name', any: true).to_a).to eq([bob, frank])
164
419
  end
165
420
 
166
- it "should be able to find tagged with any tag" do
167
- bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
168
- frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
169
- steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
421
+ it 'should be able to order by number of matching tags when matching any' do
422
+ bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive', skill_list: 'ruby, rails, css')
423
+ frank = TaggableModel.create(name: 'Frank', tag_list: 'weaker, depressed, inefficient', skill_list: 'ruby, rails, css')
424
+ steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier, more productive', skill_list: 'c++, java, ruby')
170
425
 
171
- TaggableModel.tagged_with(["ruby", "java"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank, steve]
172
- TaggableModel.tagged_with(["c++", "fitter"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, steve]
173
- TaggableModel.tagged_with(["depressed", "css"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank]
426
+ expect(TaggableModel.tagged_with(%w(ruby java), any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([steve, bob, frank])
427
+ expect(TaggableModel.tagged_with(%w(c++ fitter), any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([steve, bob])
428
+ expect(TaggableModel.tagged_with(%w(depressed css), any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([frank, bob])
429
+ expect(TaggableModel.tagged_with(['fitter', 'happier', 'more productive', 'c++', 'java', 'ruby'], any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([steve, bob, frank])
430
+ expect(TaggableModel.tagged_with(%w(c++ java ruby fitter), any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([steve, bob, frank])
174
431
  end
175
432
 
176
- it "should be able to find tagged on a custom tag context" do
177
- bob = TaggableModel.create(:name => "Bob")
178
- bob.set_tag_list_on(:rotors, "spinning, jumping")
179
- bob.tag_list_on(:rotors).should == ["spinning","jumping"]
433
+ context 'wild: true' do
434
+ it 'should use params as wildcards' do
435
+ bob = TaggableModel.create(name: 'Bob', tag_list: 'bob, tricia')
436
+ frank = TaggableModel.create(name: 'Frank', tag_list: 'bobby, jim')
437
+ steve = TaggableModel.create(name: 'Steve', tag_list: 'john, patricia')
438
+ jim = TaggableModel.create(name: 'Jim', tag_list: 'jim, steve')
439
+
440
+ expect(TaggableModel.tagged_with(%w(bob tricia), wild: true, any: true).to_a.sort_by { |o| o.id }).to eq([bob, frank, steve])
441
+ expect(TaggableModel.tagged_with(%w(bob tricia), wild: true, exclude: true).to_a).to eq([jim])
442
+ end
443
+ end
444
+
445
+ it 'should be able to find tagged on a custom tag context' do
446
+ bob = TaggableModel.create(name: 'Bob')
447
+ bob.set_tag_list_on(:rotors, 'spinning, jumping')
448
+ expect(bob.tag_list_on(:rotors)).to eq(%w(spinning jumping))
180
449
  bob.save
181
450
 
182
- TaggableModel.tagged_with("spinning", :on => :rotors).to_a.should == [bob]
451
+ expect(TaggableModel.tagged_with('spinning', on: :rotors).to_a).to eq([bob])
183
452
  end
184
453
 
185
- it "should be able to use named scopes to chain tag finds" do
186
- bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
187
- frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
188
- steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, python')
454
+ it 'should be able to use named scopes to chain tag finds' do
455
+ bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive', skill_list: 'ruby, rails, css')
456
+ frank = TaggableModel.create(name: 'Frank', tag_list: 'weaker, depressed, inefficient', skill_list: 'ruby, rails, css')
457
+ steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier, more productive', skill_list: 'c++, java, python')
189
458
 
190
459
  # Let's only find those productive Rails developers
191
- TaggableModel.tagged_with('rails', :on => :skills, :order => 'taggable_models.name').to_a.should == [bob, frank]
192
- TaggableModel.tagged_with('happier', :on => :tags, :order => 'taggable_models.name').to_a.should == [bob, steve]
193
- TaggableModel.tagged_with('rails', :on => :skills).tagged_with('happier', :on => :tags).to_a.should == [bob]
194
- TaggableModel.tagged_with('rails').tagged_with('happier', :on => :tags).to_a.should == [bob]
460
+ expect(TaggableModel.tagged_with('rails', on: :skills, order: 'taggable_models.name').to_a).to eq([bob, frank])
461
+ expect(TaggableModel.tagged_with('happier', on: :tags, order: 'taggable_models.name').to_a).to eq([bob, steve])
462
+ expect(TaggableModel.tagged_with('rails', on: :skills).tagged_with('happier', on: :tags).to_a).to eq([bob])
463
+ expect(TaggableModel.tagged_with('rails').tagged_with('happier', on: :tags).to_a).to eq([bob])
195
464
  end
196
465
 
197
- it "should be able to find tagged with only the matching tags" do
198
- bob = TaggableModel.create(:name => "Bob", :tag_list => "lazy, happier")
199
- frank = TaggableModel.create(:name => "Frank", :tag_list => "fitter, happier, inefficient")
200
- steve = TaggableModel.create(:name => 'Steve', :tag_list => "fitter, happier")
466
+ it 'should be able to find tagged with only the matching tags' do
467
+ TaggableModel.create(name: 'Bob', tag_list: 'lazy, happier')
468
+ TaggableModel.create(name: 'Frank', tag_list: 'fitter, happier, inefficient')
469
+ steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier')
201
470
 
202
- TaggableModel.tagged_with("fitter, happier", :match_all => true).to_a.should == [steve]
471
+ expect(TaggableModel.tagged_with('fitter, happier', match_all: true).to_a).to eq([steve])
203
472
  end
204
473
 
205
- it "should be able to find tagged with some excluded tags" do
206
- bob = TaggableModel.create(:name => "Bob", :tag_list => "happier, lazy")
207
- frank = TaggableModel.create(:name => "Frank", :tag_list => "happier")
208
- steve = TaggableModel.create(:name => 'Steve', :tag_list => "happier")
474
+ it 'should be able to find tagged with only the matching tags for a context' do
475
+ TaggableModel.create(name: 'Bob', tag_list: 'lazy, happier', skill_list: 'ruby, rails, css')
476
+ frank = TaggableModel.create(name: 'Frank', tag_list: 'fitter, happier, inefficient', skill_list: 'css')
477
+ TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier', skill_list: 'ruby, rails, css')
209
478
 
210
- TaggableModel.tagged_with("lazy", :exclude => true).to_a.should == [frank, steve]
479
+ expect(TaggableModel.tagged_with('css', on: :skills, match_all: true).to_a).to eq([frank])
211
480
  end
212
481
 
213
- it "should not create duplicate taggings" do
214
- bob = TaggableModel.create(:name => "Bob")
215
- lambda {
216
- bob.tag_list << "happier"
217
- bob.tag_list << "happier"
218
- bob.save
219
- }.should change(Tagging, :count).by(1)
482
+ it 'should be able to find tagged with some excluded tags' do
483
+ TaggableModel.create(name: 'Bob', tag_list: 'happier, lazy')
484
+ frank = TaggableModel.create(name: 'Frank', tag_list: 'happier')
485
+ steve = TaggableModel.create(name: 'Steve', tag_list: 'happier')
486
+
487
+ expect(TaggableModel.tagged_with('lazy', exclude: true)).to include(frank, steve)
488
+ expect(TaggableModel.tagged_with('lazy', exclude: true).size).to eq(2)
489
+ end
490
+
491
+ it 'should return an empty scope for empty tags' do
492
+ ['', ' ', nil, []].each do |tag|
493
+ expect(TaggableModel.tagged_with(tag)).to be_empty
494
+ end
220
495
  end
221
-
222
- describe "Associations" do
496
+
497
+ context 'Duplicates' do
498
+ context 'should not create duplicate taggings' do
499
+ let(:bob) { TaggableModel.create(name: 'Bob') }
500
+ context 'case sensitive' do
501
+ it '#add' do
502
+ expect(lambda {
503
+ bob.tag_list.add 'happier'
504
+ bob.tag_list.add 'happier'
505
+ bob.tag_list.add 'happier', 'rich', 'funny'
506
+ bob.save
507
+ }).to change(ActsAsTaggableOn::Tagging, :count).by(3)
508
+ end
509
+ it '#<<' do
510
+ expect(lambda {
511
+ bob.tag_list << 'social'
512
+ bob.tag_list << 'social'
513
+ bob.tag_list << 'social' << 'wow'
514
+ bob.save
515
+ }).to change(ActsAsTaggableOn::Tagging, :count).by(2)
516
+
517
+ end
518
+
519
+ it 'unicode' do
520
+
521
+ expect(lambda {
522
+ bob.tag_list.add 'ПРИВЕТ'
523
+ bob.tag_list.add 'ПРИВЕТ'
524
+ bob.tag_list.add 'ПРИВЕТ', 'ПРИВЕТ'
525
+ bob.save
526
+ }).to change(ActsAsTaggableOn::Tagging, :count).by(1)
527
+
528
+ end
529
+
530
+ it '#=' do
531
+ expect(lambda {
532
+ bob.tag_list = ['Happy', 'Happy']
533
+ bob.save
534
+ }).to change(ActsAsTaggableOn::Tagging, :count).by(1)
535
+ end
536
+ end
537
+ context 'case insensitive' do
538
+ before(:all) { ActsAsTaggableOn.force_lowercase = true }
539
+ after(:all) { ActsAsTaggableOn.force_lowercase = false }
540
+
541
+ it '#<<' do
542
+ expect(lambda {
543
+ bob.tag_list << 'Alone'
544
+ bob.tag_list << 'AloNe'
545
+ bob.tag_list << 'ALONE' << 'In The dark'
546
+ bob.save
547
+ }).to change(ActsAsTaggableOn::Tagging, :count).by(2)
548
+
549
+ end
550
+
551
+ it '#add' do
552
+ expect(lambda {
553
+ bob.tag_list.add 'forever'
554
+ bob.tag_list.add 'ForEver'
555
+ bob.tag_list.add 'FOREVER', 'ALONE'
556
+ bob.save
557
+ }).to change(ActsAsTaggableOn::Tagging, :count).by(2)
558
+ end
559
+
560
+ it 'unicode' do
561
+
562
+ expect(lambda {
563
+ bob.tag_list.add 'ПРИВЕТ'
564
+ bob.tag_list.add 'привет', 'Привет'
565
+ bob.save
566
+ }).to change(ActsAsTaggableOn::Tagging, :count).by(1)
567
+
568
+ end
569
+
570
+ it '#=' do
571
+ expect(lambda {
572
+ bob.tag_list = ['Happy', 'HAPPY']
573
+ bob.save
574
+ }).to change(ActsAsTaggableOn::Tagging, :count).by(1)
575
+ end
576
+
577
+
578
+ end
579
+
580
+
581
+ end
582
+
583
+ if ActsAsTaggableOn::Utils.supports_concurrency?
584
+ xit 'should not duplicate tags added on different threads' do
585
+ #TODO, try with more threads and fix deadlock
586
+ thread_count = 4
587
+ barrier = Barrier.new thread_count
588
+
589
+ expect {
590
+ thread_count.times.map do |idx|
591
+ Thread.start do
592
+ connor = TaggableModel.first_or_create(name: 'Connor')
593
+ connor.tag_list = 'There, can, be, only, one'
594
+ barrier.wait
595
+ begin
596
+ connor.save
597
+ rescue ActsAsTaggableOn::DuplicateTagError
598
+ # second save should succeed
599
+ connor.save
600
+ end
601
+ end
602
+ end.map(&:join)
603
+ }.to change(ActsAsTaggableOn::Tag, :count).by(5)
604
+ end
605
+ end
606
+ end
607
+
608
+ describe 'Associations' do
223
609
  before(:each) do
224
- @taggable = TaggableModel.create(:tag_list => "awesome, epic")
610
+ @taggable = TaggableModel.create(tag_list: 'awesome, epic')
225
611
  end
226
-
227
- it "should not remove tags when creating associated objects" do
612
+
613
+ it 'should not remove tags when creating associated objects' do
228
614
  @taggable.untaggable_models.create!
229
615
  @taggable.reload
230
- @taggable.tag_list.should have(2).items
616
+ expect(@taggable.tag_list.size).to eq(2)
617
+ end
618
+ end
619
+
620
+ describe 'grouped_column_names_for method' do
621
+ it 'should return all column names joined for Tag GROUP clause' do
622
+ # NOTE: type column supports an STI Tag subclass in the test suite, though
623
+ # isn't included by default in the migration generator
624
+ expect(@taggable.grouped_column_names_for(ActsAsTaggableOn::Tag))
625
+ .to eq('tags.id, tags.name, tags.taggings_count, tags.type')
626
+ end
627
+
628
+ it 'should return all column names joined for TaggableModel GROUP clause' do
629
+ expect(@taggable.grouped_column_names_for(TaggableModel)).to eq('taggable_models.id, taggable_models.name, taggable_models.type')
630
+ end
631
+
632
+ it 'should return all column names joined for NonStandardIdTaggableModel GROUP clause' do
633
+ expect(@taggable.grouped_column_names_for(TaggableModel)).to eq("taggable_models.#{TaggableModel.primary_key}, taggable_models.name, taggable_models.type")
231
634
  end
232
635
  end
233
636
 
234
- describe "grouped_column_names_for method" do
235
- it "should return all column names joined for Tag GROUP clause" do
236
- @taggable.grouped_column_names_for(Tag).should == "tags.id, tags.name"
637
+ describe 'NonStandardIdTaggable' do
638
+ before(:each) do
639
+ @taggable = NonStandardIdTaggableModel.new(name: 'Bob Jones')
640
+ @taggables = [@taggable, NonStandardIdTaggableModel.new(name: 'John Doe')]
641
+ end
642
+
643
+ it 'should have tag types' do
644
+ [:tags, :languages, :skills, :needs, :offerings].each do |type|
645
+ expect(NonStandardIdTaggableModel.tag_types).to include type
646
+ end
647
+
648
+ expect(@taggable.tag_types).to eq(NonStandardIdTaggableModel.tag_types)
649
+ end
650
+
651
+ it 'should have tag_counts_on' do
652
+ expect(NonStandardIdTaggableModel.tag_counts_on(:tags)).to be_empty
653
+
654
+ @taggable.tag_list = %w(awesome epic)
655
+ @taggable.save
656
+
657
+ expect(NonStandardIdTaggableModel.tag_counts_on(:tags).length).to eq(2)
658
+ expect(@taggable.tag_counts_on(:tags).length).to eq(2)
659
+ end
660
+
661
+ it 'should have tags_on' do
662
+ expect(NonStandardIdTaggableModel.tags_on(:tags)).to be_empty
663
+
664
+ @taggable.tag_list = %w(awesome epic)
665
+ @taggable.save
666
+
667
+ expect(NonStandardIdTaggableModel.tags_on(:tags).length).to eq(2)
668
+ expect(@taggable.tags_on(:tags).length).to eq(2)
669
+ end
670
+
671
+ it 'should be able to create tags' do
672
+ @taggable.skill_list = 'ruby, rails, css'
673
+ expect(@taggable.instance_variable_get('@skill_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy
674
+
675
+ expect(-> {
676
+ @taggable.save
677
+ }).to change(ActsAsTaggableOn::Tag, :count).by(3)
678
+
679
+ @taggable.reload
680
+ expect(@taggable.skill_list.sort).to eq(%w(ruby rails css).sort)
237
681
  end
238
682
 
239
- it "should return all column names joined for TaggableModel GROUP clause" do
240
- @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.id, taggable_models.name, taggable_models.type"
683
+ it 'should be able to create tags through the tag list directly' do
684
+ @taggable.tag_list_on(:test).add('hello')
685
+ expect(@taggable.tag_list_cache_on(:test)).to_not be_empty
686
+ expect(@taggable.tag_list_on(:test)).to eq(['hello'])
687
+
688
+ @taggable.save
689
+ @taggable.save_tags
690
+
691
+ @taggable.reload
692
+ expect(@taggable.tag_list_on(:test)).to eq(['hello'])
241
693
  end
242
694
  end
243
695
 
244
- describe "Single Table Inheritance" do
245
- before do
246
- @taggable = TaggableModel.new(:name => "taggable")
247
- @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
248
- @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
696
+ describe 'Dirty Objects' do
697
+ context 'with un-contexted tags' do
698
+ before(:each) do
699
+ @taggable = TaggableModel.create(tag_list: 'awesome, epic')
700
+ end
701
+
702
+ context 'when tag_list changed' do
703
+ before(:each) do
704
+ expect(@taggable.changes).to be_empty
705
+ @taggable.tag_list = 'one'
706
+ end
707
+
708
+ it 'should show changes of dirty object' do
709
+ expect(@taggable.changes).to eq({'tag_list' => ['awesome, epic', ['one']]})
710
+ end
711
+
712
+ it 'flags tag_list as changed' do
713
+ expect(@taggable.tag_list_changed?).to be_truthy
714
+ end
715
+
716
+ it 'preserves original value' do
717
+ expect(@taggable.tag_list_was).to eq('awesome, epic')
718
+ end
719
+
720
+ it 'shows what the change was' do
721
+ expect(@taggable.tag_list_change).to eq(['awesome, epic', ['one']])
722
+ end
723
+ end
724
+
725
+ context 'when tag_list is the same' do
726
+ before(:each) do
727
+ @taggable.tag_list = 'awesome, epic'
728
+ end
729
+
730
+ it 'is not flagged as changed' do
731
+ expect(@taggable.tag_list_changed?).to be_falsy
732
+ end
733
+
734
+ it 'does not show any changes to the taggable item' do
735
+ expect(@taggable.changes).to be_empty
736
+ end
737
+
738
+ context "and using a delimiter different from a ','" do
739
+ before do
740
+ @old_delimiter = ActsAsTaggableOn.delimiter
741
+ ActsAsTaggableOn.delimiter = ';'
742
+ end
743
+
744
+ after do
745
+ ActsAsTaggableOn.delimiter = @old_delimiter
746
+ end
747
+
748
+ it 'does not show any changes to the taggable item when using array assignments' do
749
+ @taggable.tag_list = %w(awesome epic)
750
+ expect(@taggable.changes).to be_empty
751
+ end
752
+ end
753
+ end
249
754
  end
250
-
251
- it "should be able to save tags for inherited models" do
252
- @inherited_same.tag_list = "bob, kelso"
253
- @inherited_same.save
254
- InheritingTaggableModel.tagged_with("bob").first.should == @inherited_same
755
+
756
+ context 'with context tags' do
757
+ before(:each) do
758
+ @taggable = TaggableModel.create('language_list' => 'awesome, epic')
759
+ end
760
+
761
+ context 'when language_list changed' do
762
+ before(:each) do
763
+ expect(@taggable.changes).to be_empty
764
+ @taggable.language_list = 'one'
765
+ end
766
+
767
+ it 'should show changes of dirty object' do
768
+ expect(@taggable.changes).to eq({'language_list' => ['awesome, epic', ['one']]})
769
+ end
770
+
771
+ it 'flags language_list as changed' do
772
+ expect(@taggable.language_list_changed?).to be_truthy
773
+ end
774
+
775
+ it 'preserves original value' do
776
+ expect(@taggable.language_list_was).to eq('awesome, epic')
777
+ end
778
+
779
+ it 'shows what the change was' do
780
+ expect(@taggable.language_list_change).to eq(['awesome, epic', ['one']])
781
+ end
782
+
783
+ it 'shows what the changes were' do
784
+ expect(@taggable.language_list_changes).to eq(['awesome, epic', ['one']])
785
+ end
786
+ end
787
+
788
+ context 'when language_list is the same' do
789
+ before(:each) do
790
+ @taggable.language_list = 'awesome, epic'
791
+ end
792
+
793
+ it 'is not flagged as changed' do
794
+ expect(@taggable.language_list_changed?).to be_falsy
795
+ end
796
+
797
+ it 'does not show any changes to the taggable item' do
798
+ expect(@taggable.changes).to be_empty
799
+ end
800
+ end
255
801
  end
256
-
257
- it "should find STI tagged models on the superclass" do
258
- @inherited_same.tag_list = "bob, kelso"
259
- @inherited_same.save
260
- TaggableModel.tagged_with("bob").first.should == @inherited_same
802
+ end
803
+
804
+ describe 'Autogenerated methods' do
805
+ it 'should be overridable' do
806
+ expect(TaggableModel.create(tag_list: 'woo').tag_list_submethod_called).to be_truthy
261
807
  end
262
-
263
- it "should be able to add on contexts only to some subclasses" do
264
- @inherited_different.part_list = "fork, spoon"
265
- @inherited_different.save
266
- InheritingTaggableModel.tagged_with("fork", :on => :parts).should be_empty
267
- AlteredInheritingTaggableModel.tagged_with("fork", :on => :parts).first.should == @inherited_different
808
+ end
809
+
810
+ # See https://github.com/mbleigh/acts-as-taggable-on/pull/457 for details
811
+ context 'tag_counts and aggreating scopes, compatability with MySQL ' do
812
+ before(:each) do
813
+ TaggableModel.new(:name => 'Barb Jones').tap { |t| t.tag_list = %w(awesome fun) }.save
814
+ TaggableModel.new(:name => 'John Doe').tap { |t| t.tag_list = %w(cool fun hella) }.save
815
+ TaggableModel.new(:name => 'Jo Doe').tap { |t| t.tag_list = %w(curious young naive sharp) }.save
816
+
817
+ TaggableModel.all.each { |t| t.save }
818
+ end
819
+
820
+ context 'Model.limit(x).tag_counts.sum(:tags_count)' do
821
+ it 'should not break on Mysql' do
822
+ # Activerecord 3.2 return a string
823
+ expect(TaggableModel.limit(2).tag_counts.sum('tags_count').to_i).to eq(5)
824
+ end
268
825
  end
269
-
270
- it "should have different tag_counts_on for inherited models" do
271
- @inherited_same.tag_list = "bob, kelso"
272
- @inherited_same.save!
273
- @inherited_different.tag_list = "fork, spoon"
274
- @inherited_different.save!
275
-
276
- InheritingTaggableModel.tag_counts_on(:tags).map(&:name).should == %w(bob kelso)
277
- AlteredInheritingTaggableModel.tag_counts_on(:tags).map(&:name).should == %w(fork spoon)
278
- TaggableModel.tag_counts_on(:tags).map(&:name).should == %w(bob kelso fork spoon)
826
+
827
+ context 'regression prevention, just making sure these esoteric queries still work' do
828
+ context 'Model.tag_counts.limit(x)' do
829
+ it 'should limit the tag objects (not very useful, of course)' do
830
+ array_of_tag_counts = TaggableModel.tag_counts.limit(2)
831
+ expect(array_of_tag_counts.count).to eq(2)
832
+ end
833
+ end
834
+
835
+ context 'Model.tag_counts.sum(:tags_count)' do
836
+ it 'should limit the total tags used' do
837
+ expect(TaggableModel.tag_counts.sum(:tags_count).to_i).to eq(9)
838
+ end
839
+ end
840
+
841
+ context 'Model.tag_counts.limit(2).sum(:tags_count)' do
842
+ it 'limit should have no effect; this is just a sanity check' do
843
+ expect(TaggableModel.tag_counts.limit(2).sum(:tags_count).to_i).to eq(9)
844
+ end
845
+ end
279
846
  end
280
-
281
- it 'should store same tag without validation conflict' do
282
- @taggable.tag_list = 'one'
283
- @taggable.save!
284
-
285
- @inherited_same.tag_list = 'one'
286
- @inherited_same.save!
287
-
288
- @inherited_same.update_attributes! :name => 'foo'
847
+ end
848
+ end
849
+
850
+ if ActsAsTaggableOn::Utils.using_postgresql?
851
+ if ActsAsTaggableOn::Utils.postgresql_support_json?
852
+ describe 'Taggable model with json columns' do
853
+ before(:each) do
854
+ @taggable = TaggableModelWithJson.new(:name => 'Bob Jones')
855
+ @taggables = [@taggable, TaggableModelWithJson.new(:name => 'John Doe')]
856
+ end
857
+
858
+ it 'should be able to find by tag with context' do
859
+ @taggable.skill_list = 'ruby, rails, css'
860
+ @taggable.tag_list = 'bob, charlie'
861
+ @taggable.save
862
+
863
+ expect(TaggableModelWithJson.tagged_with('ruby').first).to eq(@taggable)
864
+ expect(TaggableModelWithJson.tagged_with('ruby, css').first).to eq(@taggable)
865
+ expect(TaggableModelWithJson.tagged_with('bob', :on => :skills).first).to_not eq(@taggable)
866
+ expect(TaggableModelWithJson.tagged_with('bob', :on => :tags).first).to eq(@taggable)
867
+ end
868
+
869
+ it 'should be able to find tagged with any tag' do
870
+ bob = TaggableModelWithJson.create(:name => 'Bob', :tag_list => 'fitter, happier, more productive', :skill_list => 'ruby, rails, css')
871
+ frank = TaggableModelWithJson.create(:name => 'Frank', :tag_list => 'weaker, depressed, inefficient', :skill_list => 'ruby, rails, css')
872
+ steve = TaggableModelWithJson.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
873
+
874
+ expect(TaggableModelWithJson.tagged_with(%w(ruby java), :order => 'taggable_model_with_jsons.name', :any => true).to_a).to eq([bob, frank, steve])
875
+ expect(TaggableModelWithJson.tagged_with(%w(c++ fitter), :order => 'taggable_model_with_jsons.name', :any => true).to_a).to eq([bob, steve])
876
+ expect(TaggableModelWithJson.tagged_with(%w(depressed css), :order => 'taggable_model_with_jsons.name', :any => true).to_a).to eq([bob, frank])
877
+ end
289
878
  end
290
879
  end
291
880
  end