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,115 +1,295 @@
1
- require File.dirname(__FILE__) + '/../spec_helper'
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'db/migrate/2_add_missing_unique_indices.rb'
2
4
 
3
- describe Tag do
5
+ shared_examples_for 'without unique index' do
6
+ prepend_before(:all) { AddMissingUniqueIndices.down }
7
+ append_after(:all) do
8
+ ActsAsTaggableOn::Tag.delete_all
9
+ AddMissingUniqueIndices.up
10
+ end
11
+ end
12
+
13
+ describe ActsAsTaggableOn::Tag do
4
14
  before(:each) do
5
- clean_database!
6
- @tag = Tag.new
7
- @user = TaggableModel.create(:name => "Pablo")
15
+ @tag = ActsAsTaggableOn::Tag.new
16
+ @user = TaggableModel.create(name: 'Pablo')
8
17
  end
9
18
 
10
- describe "named like any" do
11
- before(:each) do
12
- Tag.create(:name => "awesome")
13
- Tag.create(:name => "epic")
19
+
20
+ describe 'named like any' do
21
+ if ActsAsTaggableOn::Utils.using_case_insensitive_collation?
22
+ context 'case insensitive collation and unique index on tag name' do
23
+ before(:each) do
24
+ ActsAsTaggableOn::Tag.create(name: 'Awesome')
25
+ ActsAsTaggableOn::Tag.create(name: 'epic')
26
+ end
27
+
28
+ it 'should find both tags' do
29
+ expect(ActsAsTaggableOn::Tag.named_like_any(%w(awesome epic)).count).to eq(2)
30
+ end
31
+ end
14
32
  end
15
33
 
16
- it "should find both tags" do
17
- Tag.named_like_any(["awesome", "epic"]).should have(2).items
34
+ context 'case insensitive collation without indexes or case sensitive collation with indexes' do
35
+ if ActsAsTaggableOn::Utils.using_case_insensitive_collation?
36
+ include_context 'without unique index'
37
+ end
38
+
39
+ before(:each) do
40
+ ActsAsTaggableOn::Tag.create(name: 'Awesome')
41
+ ActsAsTaggableOn::Tag.create(name: 'awesome')
42
+ ActsAsTaggableOn::Tag.create(name: 'epic')
43
+ end
44
+
45
+ it 'should find both tags' do
46
+ expect(ActsAsTaggableOn::Tag.named_like_any(%w(awesome epic)).count).to eq(3)
47
+ end
18
48
  end
19
49
  end
20
50
 
21
- describe "find or create by name" do
51
+ describe 'find or create by name' do
22
52
  before(:each) do
23
- @tag.name = "awesome"
53
+ @tag.name = 'awesome'
24
54
  @tag.save
25
55
  end
26
56
 
27
- it "should find by name" do
28
- Tag.find_or_create_with_like_by_name("awesome").should == @tag
57
+ it 'should find by name' do
58
+ expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('awesome')).to eq(@tag)
29
59
  end
30
60
 
31
- it "should find by name case insensitive" do
32
- Tag.find_or_create_with_like_by_name("AWESOME").should == @tag
61
+ it 'should find by name case insensitive' do
62
+ expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('AWESOME')).to eq(@tag)
33
63
  end
34
64
 
35
- it "should create by name" do
36
- lambda {
37
- Tag.find_or_create_with_like_by_name("epic")
38
- }.should change(Tag, :count).by(1)
65
+ it 'should create by name' do
66
+ expect(-> {
67
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('epic')
68
+ }).to change(ActsAsTaggableOn::Tag, :count).by(1)
39
69
  end
40
70
  end
41
71
 
42
- describe "find or create all by any name" do
72
+ unless ActsAsTaggableOn::Utils.using_sqlite?
73
+ describe 'find or create by unicode name' do
74
+ before(:each) do
75
+ @tag.name = 'привет'
76
+ @tag.save
77
+ end
78
+
79
+ it 'should find by name' do
80
+ expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('привет')).to eq(@tag)
81
+ end
82
+
83
+ it 'should find by name case insensitive' do
84
+ expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('ПРИВЕТ')).to eq(@tag)
85
+ end
86
+
87
+ if ActsAsTaggableOn::Utils.using_case_insensitive_collation?
88
+ it 'should find by name accent insensitive' do
89
+ @tag.name = 'inupiat'
90
+ @tag.save
91
+ expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('Iñupiat')).to eq(@tag)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ describe 'find or create all by any name' do
43
98
  before(:each) do
44
- @tag.name = "awesome"
99
+ @tag.name = 'awesome'
45
100
  @tag.save
46
101
  end
47
102
 
48
- it "should find by name" do
49
- Tag.find_or_create_all_with_like_by_name("awesome").should == [@tag]
103
+ it 'should find by name' do
104
+ expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('awesome')).to eq([@tag])
50
105
  end
51
106
 
52
- it "should find by name case insensitive" do
53
- Tag.find_or_create_all_with_like_by_name("AWESOME").should == [@tag]
107
+ it 'should find by name case insensitive' do
108
+ expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('AWESOME')).to eq([@tag])
54
109
  end
55
110
 
56
- it "should create by name" do
57
- lambda {
58
- Tag.find_or_create_all_with_like_by_name("epic")
59
- }.should change(Tag, :count).by(1)
111
+ context 'case sensitive' do
112
+ if ActsAsTaggableOn::Utils.using_case_insensitive_collation?
113
+ include_context 'without unique index'
114
+ end
115
+
116
+ it 'should find by name case sensitive' do
117
+ ActsAsTaggableOn.strict_case_match = true
118
+ expect {
119
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('AWESOME')
120
+ }.to change(ActsAsTaggableOn::Tag, :count).by(1)
121
+ end
60
122
  end
61
123
 
62
- it "should find or create by name" do
63
- lambda {
64
- Tag.find_or_create_all_with_like_by_name("awesome", "epic").map(&:name).should == ["awesome", "epic"]
65
- }.should change(Tag, :count).by(1)
124
+ it 'should create by name' do
125
+ expect {
126
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('epic')
127
+ }.to change(ActsAsTaggableOn::Tag, :count).by(1)
66
128
  end
67
129
 
68
- it "should return an empty array if no tags are specified" do
69
- Tag.find_or_create_all_with_like_by_name([]).should == []
130
+ context 'case sensitive' do
131
+ if ActsAsTaggableOn::Utils.using_case_insensitive_collation?
132
+ include_context 'without unique index'
133
+ end
134
+
135
+ it 'should find or create by name case sensitive' do
136
+ ActsAsTaggableOn.strict_case_match = true
137
+ expect {
138
+ expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('AWESOME', 'awesome').map(&:name)).to eq(%w(AWESOME awesome))
139
+ }.to change(ActsAsTaggableOn::Tag, :count).by(1)
140
+ end
141
+ end
142
+
143
+ it 'should find or create by name' do
144
+ expect {
145
+ expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('awesome', 'epic').map(&:name)).to eq(%w(awesome epic))
146
+ }.to change(ActsAsTaggableOn::Tag, :count).by(1)
147
+ end
148
+
149
+ it 'should return an empty array if no tags are specified' do
150
+ expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name([])).to be_empty
70
151
  end
71
152
  end
72
153
 
73
- it "should require a name" do
154
+ it 'should require a name' do
74
155
  @tag.valid?
75
-
76
- if ActiveRecord::VERSION::MAJOR >= 3
77
- @tag.errors[:name].should == ["can't be blank"]
78
- else
79
- @tag.errors[:name].should == "can't be blank"
80
- end
156
+ #TODO, we should find another way to check this
157
+ expect(@tag.errors[:name]).to eq(["can't be blank"])
81
158
 
82
- @tag.name = "something"
159
+ @tag.name = 'something'
83
160
  @tag.valid?
84
-
85
- if ActiveRecord::VERSION::MAJOR >= 3
86
- @tag.errors[:name].should == []
87
- else
88
- @tag.errors[:name].should be_nil
89
- end
161
+
162
+ expect(@tag.errors[:name]).to be_empty
90
163
  end
91
164
 
92
- it "should equal a tag with the same name" do
93
- @tag.name = "awesome"
94
- new_tag = Tag.new(:name => "awesome")
95
- new_tag.should == @tag
165
+ it 'should limit the name length to 255 or less characters' do
166
+ @tag.name = 'fgkgnkkgjymkypbuozmwwghblmzpqfsgjasflblywhgkwndnkzeifalfcpeaeqychjuuowlacmuidnnrkprgpcpybarbkrmziqihcrxirlokhnzfvmtzixgvhlxzncyywficpraxfnjptxxhkqmvicbcdcynkjvziefqzyndxkjmsjlvyvbwraklbalykyxoliqdlreeykuphdtmzfdwpphmrqvwvqffojkqhlzvinqajsxbszyvrqqyzusxranr'
167
+ @tag.valid?
168
+ #TODO, we should find another way to check this
169
+ expect(@tag.errors[:name]).to eq(['is too long (maximum is 255 characters)'])
170
+
171
+ @tag.name = 'fgkgnkkgjymkypbuozmwwghblmzpqfsgjasflblywhgkwndnkzeifalfcpeaeqychjuuowlacmuidnnrkprgpcpybarbkrmziqihcrxirlokhnzfvmtzixgvhlxzncyywficpraxfnjptxxhkqmvicbcdcynkjvziefqzyndxkjmsjlvyvbwraklbalykyxoliqdlreeykuphdtmzfdwpphmrqvwvqffojkqhlzvinqajsxbszyvrqqyzusxran'
172
+ @tag.valid?
173
+ expect(@tag.errors[:name]).to be_empty
174
+ end
175
+
176
+ it 'should equal a tag with the same name' do
177
+ @tag.name = 'awesome'
178
+ new_tag = ActsAsTaggableOn::Tag.new(name: 'awesome')
179
+ expect(new_tag).to eq(@tag)
96
180
  end
97
181
 
98
- it "should return its name when to_s is called" do
99
- @tag.name = "cool"
100
- @tag.to_s.should == "cool"
182
+ it 'should return its name when to_s is called' do
183
+ @tag.name = 'cool'
184
+ expect(@tag.to_s).to eq('cool')
101
185
  end
102
186
 
103
- it "have named_scope named(something)" do
104
- @tag.name = "cool"
187
+ it 'have named_scope named(something)' do
188
+ @tag.name = 'cool'
105
189
  @tag.save!
106
- Tag.named('cool').should include(@tag)
190
+ expect(ActsAsTaggableOn::Tag.named('cool')).to include(@tag)
107
191
  end
108
192
 
109
- it "have named_scope named_like(something)" do
110
- @tag.name = "cool"
193
+ it 'have named_scope named_like(something)' do
194
+ @tag.name = 'cool'
111
195
  @tag.save!
112
- @another_tag = Tag.create!(:name => "coolip")
113
- Tag.named_like('cool').should include(@tag, @another_tag)
196
+ @another_tag = ActsAsTaggableOn::Tag.create!(name: 'coolip')
197
+ expect(ActsAsTaggableOn::Tag.named_like('cool')).to include(@tag, @another_tag)
198
+ end
199
+
200
+ describe 'escape wildcard symbols in like requests' do
201
+ before(:each) do
202
+ @tag.name = 'cool'
203
+ @tag.save
204
+ @another_tag = ActsAsTaggableOn::Tag.create!(name: 'coo%')
205
+ @another_tag2 = ActsAsTaggableOn::Tag.create!(name: 'coolish')
206
+ end
207
+
208
+ it "return escaped result when '%' char present in tag" do
209
+ expect(ActsAsTaggableOn::Tag.named_like('coo%')).to_not include(@tag)
210
+ expect(ActsAsTaggableOn::Tag.named_like('coo%')).to include(@another_tag)
211
+ end
212
+
213
+ end
214
+
215
+ describe 'when using strict_case_match' do
216
+ before do
217
+ ActsAsTaggableOn.strict_case_match = true
218
+ @tag.name = 'awesome'
219
+ @tag.save!
220
+ end
221
+
222
+ after do
223
+ ActsAsTaggableOn.strict_case_match = false
224
+ end
225
+
226
+ it 'should find by name' do
227
+ expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('awesome')).to eq(@tag)
228
+ end
229
+
230
+ context 'case sensitive' do
231
+ if ActsAsTaggableOn::Utils.using_case_insensitive_collation?
232
+ include_context 'without unique index'
233
+ end
234
+
235
+ it 'should find by name case sensitively' do
236
+ expect {
237
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('AWESOME')
238
+ }.to change(ActsAsTaggableOn::Tag, :count)
239
+
240
+ expect(ActsAsTaggableOn::Tag.last.name).to eq('AWESOME')
241
+ end
242
+ end
243
+
244
+ context 'case sensitive' do
245
+ if ActsAsTaggableOn::Utils.using_case_insensitive_collation?
246
+ include_context 'without unique index'
247
+ end
248
+
249
+ it 'should have a named_scope named(something) that matches exactly' do
250
+ uppercase_tag = ActsAsTaggableOn::Tag.create(name: 'Cool')
251
+ @tag.name = 'cool'
252
+ @tag.save!
253
+
254
+ expect(ActsAsTaggableOn::Tag.named('cool')).to include(@tag)
255
+ expect(ActsAsTaggableOn::Tag.named('cool')).to_not include(uppercase_tag)
256
+ end
257
+ end
258
+
259
+ it 'should not change enconding' do
260
+ name = "\u3042"
261
+ original_encoding = name.encoding
262
+ record = ActsAsTaggableOn::Tag.find_or_create_with_like_by_name(name)
263
+ record.reload
264
+ expect(record.name.encoding).to eq(original_encoding)
265
+ end
266
+ end
267
+
268
+ describe 'name uniqeness validation' do
269
+ let(:duplicate_tag) { ActsAsTaggableOn::Tag.new(name: 'ror') }
270
+
271
+ before { ActsAsTaggableOn::Tag.create(name: 'ror') }
272
+
273
+ context "when don't need unique names" do
274
+ include_context 'without unique index'
275
+ it 'should not run uniqueness validation' do
276
+ allow(duplicate_tag).to receive(:validates_name_uniqueness?) { false }
277
+ duplicate_tag.save
278
+ expect(duplicate_tag).to be_persisted
279
+ end
280
+ end
281
+
282
+ context 'when do need unique names' do
283
+ it 'should run uniqueness validation' do
284
+ expect(duplicate_tag).to_not be_valid
285
+ end
286
+
287
+ it 'add error to name' do
288
+ duplicate_tag.save
289
+
290
+ expect(duplicate_tag.errors.size).to eq(1)
291
+ expect(duplicate_tag.errors.messages[:name]).to include('has already been taken')
292
+ end
293
+ end
114
294
  end
115
295
  end