acts-as-taggable-on 2.0.0.pre5 → 3.5.0

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