sb-acts-as-taggable-on 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +39 -0
  5. data/Appraisals +15 -0
  6. data/CHANGELOG.md +330 -0
  7. data/CONTRIBUTING.md +57 -0
  8. data/Gemfile +11 -0
  9. data/Guardfile +5 -0
  10. data/LICENSE.md +20 -0
  11. data/README.md +555 -0
  12. data/Rakefile +21 -0
  13. data/UPGRADING.md +8 -0
  14. data/acts-as-taggable-on.gemspec +32 -0
  15. data/db/migrate/1_acts_as_taggable_on_migration.rb +36 -0
  16. data/db/migrate/2_add_missing_unique_indices.rb +25 -0
  17. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +19 -0
  18. data/db/migrate/4_add_missing_taggable_index.rb +14 -0
  19. data/db/migrate/5_change_collation_for_tag_names.rb +14 -0
  20. data/db/migrate/6_add_missing_indexes_on_taggings.rb +22 -0
  21. data/gemfiles/activerecord_5.0.gemfile +21 -0
  22. data/gemfiles/activerecord_5.1.gemfile +21 -0
  23. data/gemfiles/activerecord_5.2.gemfile +21 -0
  24. data/gemfiles/activerecord_6.0.gemfile +21 -0
  25. data/lib/acts-as-taggable-on.rb +133 -0
  26. data/lib/acts_as_taggable_on.rb +6 -0
  27. data/lib/acts_as_taggable_on/default_parser.rb +79 -0
  28. data/lib/acts_as_taggable_on/engine.rb +4 -0
  29. data/lib/acts_as_taggable_on/generic_parser.rb +19 -0
  30. data/lib/acts_as_taggable_on/tag.rb +139 -0
  31. data/lib/acts_as_taggable_on/tag_list.rb +106 -0
  32. data/lib/acts_as_taggable_on/taggable.rb +101 -0
  33. data/lib/acts_as_taggable_on/taggable/cache.rb +90 -0
  34. data/lib/acts_as_taggable_on/taggable/collection.rb +183 -0
  35. data/lib/acts_as_taggable_on/taggable/core.rb +322 -0
  36. data/lib/acts_as_taggable_on/taggable/ownership.rb +136 -0
  37. data/lib/acts_as_taggable_on/taggable/related.rb +71 -0
  38. data/lib/acts_as_taggable_on/taggable/tag_list_type.rb +4 -0
  39. data/lib/acts_as_taggable_on/taggable/tagged_with_query.rb +16 -0
  40. data/lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb +111 -0
  41. data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +70 -0
  42. data/lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb +82 -0
  43. data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +61 -0
  44. data/lib/acts_as_taggable_on/tagger.rb +89 -0
  45. data/lib/acts_as_taggable_on/tagging.rb +36 -0
  46. data/lib/acts_as_taggable_on/tags_helper.rb +15 -0
  47. data/lib/acts_as_taggable_on/utils.rb +37 -0
  48. data/lib/acts_as_taggable_on/version.rb +3 -0
  49. data/lib/tasks/tags_collate_utf8.rake +21 -0
  50. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +285 -0
  51. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +112 -0
  52. data/spec/acts_as_taggable_on/caching_spec.rb +129 -0
  53. data/spec/acts_as_taggable_on/default_parser_spec.rb +47 -0
  54. data/spec/acts_as_taggable_on/dirty_spec.rb +142 -0
  55. data/spec/acts_as_taggable_on/generic_parser_spec.rb +14 -0
  56. data/spec/acts_as_taggable_on/related_spec.rb +99 -0
  57. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +231 -0
  58. data/spec/acts_as_taggable_on/tag_list_spec.rb +176 -0
  59. data/spec/acts_as_taggable_on/tag_spec.rb +340 -0
  60. data/spec/acts_as_taggable_on/taggable_spec.rb +817 -0
  61. data/spec/acts_as_taggable_on/tagger_spec.rb +153 -0
  62. data/spec/acts_as_taggable_on/tagging_spec.rb +117 -0
  63. data/spec/acts_as_taggable_on/tags_helper_spec.rb +45 -0
  64. data/spec/acts_as_taggable_on/utils_spec.rb +23 -0
  65. data/spec/internal/app/models/altered_inheriting_taggable_model.rb +5 -0
  66. data/spec/internal/app/models/cached_model.rb +3 -0
  67. data/spec/internal/app/models/cached_model_with_array.rb +11 -0
  68. data/spec/internal/app/models/columns_override_model.rb +5 -0
  69. data/spec/internal/app/models/company.rb +15 -0
  70. data/spec/internal/app/models/inheriting_taggable_model.rb +4 -0
  71. data/spec/internal/app/models/market.rb +2 -0
  72. data/spec/internal/app/models/non_standard_id_taggable_model.rb +8 -0
  73. data/spec/internal/app/models/ordered_taggable_model.rb +4 -0
  74. data/spec/internal/app/models/other_cached_model.rb +3 -0
  75. data/spec/internal/app/models/other_taggable_model.rb +4 -0
  76. data/spec/internal/app/models/student.rb +4 -0
  77. data/spec/internal/app/models/taggable_model.rb +14 -0
  78. data/spec/internal/app/models/untaggable_model.rb +3 -0
  79. data/spec/internal/app/models/user.rb +3 -0
  80. data/spec/internal/config/database.yml.sample +19 -0
  81. data/spec/internal/db/schema.rb +110 -0
  82. data/spec/spec_helper.rb +20 -0
  83. data/spec/support/0-helpers.rb +32 -0
  84. data/spec/support/array.rb +9 -0
  85. data/spec/support/database.rb +36 -0
  86. data/spec/support/database_cleaner.rb +21 -0
  87. metadata +269 -0
@@ -0,0 +1,231 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe 'Single Table Inheritance' do
5
+ let(:taggable) { TaggableModel.new(name: 'taggable model') }
6
+
7
+ let(:inheriting_model) { InheritingTaggableModel.new(name: 'Inheriting Taggable Model') }
8
+ let(:altered_inheriting) { AlteredInheritingTaggableModel.new(name: 'Altered Inheriting Model') }
9
+
10
+ 1.upto(4) do |n|
11
+ let(:"inheriting_#{n}") { InheritingTaggableModel.new(name: "Inheriting Model #{n}") }
12
+ end
13
+
14
+ let(:student) { Student.create! }
15
+
16
+ describe 'tag contexts' do
17
+ it 'should pass on to STI-inherited models' do
18
+ expect(inheriting_model).to respond_to(:tag_list, :skill_list, :language_list)
19
+ expect(altered_inheriting).to respond_to(:tag_list, :skill_list, :language_list)
20
+ end
21
+
22
+ it 'should pass on to altered STI models' do
23
+ expect(altered_inheriting).to respond_to(:part_list)
24
+ end
25
+ end
26
+
27
+ context 'matching contexts' do
28
+
29
+ before do
30
+ inheriting_1.offering_list = 'one, two'
31
+ inheriting_1.need_list = 'one, two'
32
+ inheriting_1.save!
33
+
34
+ inheriting_2.need_list = 'one, two'
35
+ inheriting_2.save!
36
+
37
+ inheriting_3.offering_list = 'one, two'
38
+ inheriting_3.save!
39
+
40
+ inheriting_4.tag_list = 'one, two, three, four'
41
+ inheriting_4.save!
42
+
43
+ taggable.need_list = 'one, two'
44
+ taggable.save!
45
+ end
46
+
47
+ it 'should find objects with tags of matching contexts' do
48
+ expect(inheriting_1.find_matching_contexts(:offerings, :needs)).to include(inheriting_2)
49
+ expect(inheriting_1.find_matching_contexts(:offerings, :needs)).to_not include(inheriting_3)
50
+ expect(inheriting_1.find_matching_contexts(:offerings, :needs)).to_not include(inheriting_4)
51
+ expect(inheriting_1.find_matching_contexts(:offerings, :needs)).to_not include(taggable)
52
+
53
+ expect(inheriting_1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to include(inheriting_2)
54
+ expect(inheriting_1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to_not include(inheriting_3)
55
+ expect(inheriting_1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to_not include(inheriting_4)
56
+ expect(inheriting_1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to include(taggable)
57
+ end
58
+
59
+ it 'should not include the object itself in the list of related objects with tags of matching contexts' do
60
+ expect(inheriting_1.find_matching_contexts(:offerings, :needs)).to_not include(inheriting_1)
61
+ expect(inheriting_1.find_matching_contexts_for(InheritingTaggableModel, :offerings, :needs)).to_not include(inheriting_1)
62
+ expect(inheriting_1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to_not include(inheriting_1)
63
+ end
64
+ end
65
+
66
+ context 'find related tags' do
67
+ before do
68
+ inheriting_1.tag_list = 'one, two'
69
+ inheriting_1.save
70
+
71
+ inheriting_2.tag_list = 'three, four'
72
+ inheriting_2.save
73
+
74
+ inheriting_3.tag_list = 'one, four'
75
+ inheriting_3.save
76
+
77
+ taggable.tag_list = 'one, two, three, four'
78
+ taggable.save
79
+ end
80
+
81
+ it 'should find related objects based on tag names on context' do
82
+ expect(inheriting_1.find_related_tags).to include(inheriting_3)
83
+ expect(inheriting_1.find_related_tags).to_not include(inheriting_2)
84
+ expect(inheriting_1.find_related_tags).to_not include(taggable)
85
+
86
+ expect(inheriting_1.find_related_tags_for(TaggableModel)).to include(inheriting_3)
87
+ expect(inheriting_1.find_related_tags_for(TaggableModel)).to_not include(inheriting_2)
88
+ expect(inheriting_1.find_related_tags_for(TaggableModel)).to include(taggable)
89
+ end
90
+
91
+ it 'should not include the object itself in the list of related objects' do
92
+ expect(inheriting_1.find_related_tags).to_not include(inheriting_1)
93
+ expect(inheriting_1.find_related_tags_for(InheritingTaggableModel)).to_not include(inheriting_1)
94
+ expect(inheriting_1.find_related_tags_for(TaggableModel)).to_not include(inheriting_1)
95
+ end
96
+ end
97
+
98
+ describe 'tag list' do
99
+ before do
100
+ @inherited_same = InheritingTaggableModel.new(name: 'inherited same')
101
+ @inherited_different = AlteredInheritingTaggableModel.new(name: 'inherited different')
102
+ end
103
+
104
+ #TODO, shared example
105
+ it 'should be able to save tags for inherited models' do
106
+ inheriting_model.tag_list = 'bob, kelso'
107
+ inheriting_model.save
108
+ expect(InheritingTaggableModel.tagged_with('bob').first).to eq(inheriting_model)
109
+ end
110
+
111
+ it 'should find STI tagged models on the superclass' do
112
+ inheriting_model.tag_list = 'bob, kelso'
113
+ inheriting_model.save
114
+ expect(TaggableModel.tagged_with('bob').first).to eq(inheriting_model)
115
+ end
116
+
117
+ it 'should be able to add on contexts only to some subclasses' do
118
+ altered_inheriting.part_list = 'fork, spoon'
119
+ altered_inheriting.save
120
+ expect(InheritingTaggableModel.tagged_with('fork', on: :parts)).to be_empty
121
+ expect(AlteredInheritingTaggableModel.tagged_with('fork', on: :parts).first).to eq(altered_inheriting)
122
+ end
123
+
124
+ it 'should have different tag_counts_on for inherited models' do
125
+ inheriting_model.tag_list = 'bob, kelso'
126
+ inheriting_model.save!
127
+ altered_inheriting.tag_list = 'fork, spoon'
128
+ altered_inheriting.save!
129
+
130
+ expect(InheritingTaggableModel.tag_counts_on(:tags, order: "#{ActsAsTaggableOn.tags_table}.id").map(&:name)).to eq(%w(bob kelso))
131
+ expect(AlteredInheritingTaggableModel.tag_counts_on(:tags, order: "#{ActsAsTaggableOn.tags_table}.id").map(&:name)).to eq(%w(fork spoon))
132
+ expect(TaggableModel.tag_counts_on(:tags, order: "#{ActsAsTaggableOn.tags_table}.id").map(&:name)).to eq(%w(bob kelso fork spoon))
133
+ end
134
+
135
+ it 'should have different tags_on for inherited models' do
136
+ inheriting_model.tag_list = 'bob, kelso'
137
+ inheriting_model.save!
138
+ altered_inheriting.tag_list = 'fork, spoon'
139
+ altered_inheriting.save!
140
+
141
+ expect(InheritingTaggableModel.tags_on(:tags, order: "#{ActsAsTaggableOn.tags_table}.id").map(&:name)).to eq(%w(bob kelso))
142
+ expect(AlteredInheritingTaggableModel.tags_on(:tags, order: "#{ActsAsTaggableOn.tags_table}.id").map(&:name)).to eq(%w(fork spoon))
143
+ expect(TaggableModel.tags_on(:tags, order: "#{ActsAsTaggableOn.tags_table}.id").map(&:name)).to eq(%w(bob kelso fork spoon))
144
+ end
145
+
146
+ it 'should store same tag without validation conflict' do
147
+ taggable.tag_list = 'one'
148
+ taggable.save!
149
+
150
+ inheriting_model.tag_list = 'one'
151
+ inheriting_model.save!
152
+
153
+ inheriting_model.update! name: 'foo'
154
+ end
155
+
156
+ it "should only join with taggable's table to check type for inherited models" do
157
+ expect(TaggableModel.tag_counts_on(:tags).to_sql).to_not match /INNER JOIN taggable_models ON/
158
+ expect(InheritingTaggableModel.tag_counts_on(:tags).to_sql).to match /INNER JOIN taggable_models ON/
159
+ end
160
+ end
161
+
162
+ describe 'ownership' do
163
+ it 'should have taggings' do
164
+ student.tag(taggable, with: 'ruby,scheme', on: :tags)
165
+ expect(student.owned_taggings.count).to eq(2)
166
+ end
167
+
168
+ it 'should have tags' do
169
+ student.tag(taggable, with: 'ruby,scheme', on: :tags)
170
+ expect(student.owned_tags.count).to eq(2)
171
+ end
172
+
173
+ it 'should return tags for the inheriting tagger' do
174
+ student.tag(taggable, with: 'ruby, scheme', on: :tags)
175
+ expect(taggable.tags_from(student)).to eq(%w(ruby scheme))
176
+ end
177
+
178
+ it 'returns all owner tags on the taggable' do
179
+ student.tag(taggable, with: 'ruby, scheme', on: :tags)
180
+ student.tag(taggable, with: 'skill_one', on: :skills)
181
+ student.tag(taggable, with: 'english, spanish', on: :language)
182
+ expect(taggable.owner_tags(student).count).to eq(5)
183
+ expect(taggable.owner_tags(student).sort == %w(english ruby scheme skill_one spanish))
184
+ end
185
+
186
+
187
+ it 'returns owner tags on the tagger' do
188
+ student.tag(taggable, with: 'ruby, scheme', on: :tags)
189
+ expect(taggable.owner_tags_on(student, :tags).count).to eq(2)
190
+ end
191
+
192
+ it 'returns owner tags on the taggable for an array of contexts' do
193
+ student.tag(taggable, with: 'ruby, scheme', on: :tags)
194
+ student.tag(taggable, with: 'skill_one, skill_two', on: :skills)
195
+ expect(taggable.owner_tags_on(student, [:tags, :skills]).count).to eq(4)
196
+ expect(taggable.owner_tags_on(student, [:tags, :skills]).sort == %w(ruby scheme skill_one skill_two))
197
+ end
198
+
199
+ it 'should scope objects returned by tagged_with by owners' do
200
+ student.tag(taggable, with: 'ruby, scheme', on: :tags)
201
+ expect(TaggableModel.tagged_with(%w(ruby scheme), owned_by: student).count).to eq(1)
202
+ end
203
+ end
204
+
205
+ describe 'a subclass of Tag' do
206
+ let(:company) { Company.new(:name => 'Dewey, Cheatham & Howe') }
207
+ let(:user) { User.create! }
208
+
209
+ subject { Market.create! :name => 'finance' }
210
+
211
+ its(:type) { should eql 'Market' }
212
+
213
+ it 'sets STI type through string list' do
214
+ company.market_list = 'law, accounting'
215
+ company.save!
216
+ expect(Market.count).to eq(2)
217
+ end
218
+
219
+ it 'does not interfere with a normal Tag context on the same model' do
220
+ company.location_list = 'cambridge'
221
+ company.save!
222
+ expect(ActsAsTaggableOn::Tag.where(name: 'cambridge', type: nil)).to_not be_empty
223
+ end
224
+
225
+ it 'is returned with proper type through ownership' do
226
+ user.tag(company, :with => 'ripoffs, rackets', :on => :markets)
227
+ tags = company.owner_tags_on(user, :markets)
228
+ expect(tags.all? { |tag| tag.is_a? Market }).to be_truthy
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,176 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ActsAsTaggableOn::TagList do
6
+ let(:tag_list) { ActsAsTaggableOn::TagList.new('awesome', 'radical') }
7
+ let(:another_tag_list) { ActsAsTaggableOn::TagList.new('awesome','crazy', 'alien') }
8
+
9
+ it { should be_kind_of Array }
10
+
11
+
12
+
13
+ describe '#add' do
14
+ it 'should be able to be add a new tag word' do
15
+ tag_list.add('cool')
16
+ expect(tag_list.include?('cool')).to be_truthy
17
+ end
18
+
19
+ it 'should be able to add delimited lists of words' do
20
+ tag_list.add('cool, wicked', parse: true)
21
+ expect(tag_list).to include('cool', 'wicked')
22
+ end
23
+
24
+ it 'should be able to add delimited list of words with quoted delimiters' do
25
+ tag_list.add("'cool, wicked', \"really cool, really wicked\"", parse: true)
26
+ expect(tag_list).to include('cool, wicked', 'really cool, really wicked')
27
+ end
28
+
29
+ it 'should be able to handle other uses of quotation marks correctly' do
30
+ tag_list.add("john's cool car, mary's wicked toy", parse: true)
31
+ expect(tag_list).to include("john's cool car", "mary's wicked toy")
32
+ end
33
+
34
+ it 'should be able to add an array of words' do
35
+ tag_list.add(%w(cool wicked), parse: true)
36
+ expect(tag_list).to include('cool', 'wicked')
37
+ end
38
+
39
+ it 'should quote escape tags with commas in them' do
40
+ tag_list.add('cool', 'rad,bodacious')
41
+ expect(tag_list.to_s).to eq("awesome, radical, cool, \"rad,bodacious\"")
42
+ end
43
+
44
+ end
45
+
46
+ describe '#remove' do
47
+ it 'should be able to remove words' do
48
+ tag_list.remove('awesome')
49
+ expect(tag_list).to_not include('awesome')
50
+ end
51
+
52
+ it 'should be able to remove delimited lists of words' do
53
+ tag_list.remove('awesome, radical', parse: true)
54
+ expect(tag_list).to be_empty
55
+ end
56
+
57
+ it 'should be able to remove an array of words' do
58
+ tag_list.remove(%w(awesome radical), parse: true)
59
+ expect(tag_list).to be_empty
60
+ end
61
+ end
62
+
63
+ describe '#+' do
64
+ it 'should not have duplicate tags' do
65
+ new_tag_list = tag_list + another_tag_list
66
+ expect(tag_list).to eq(%w[awesome radical])
67
+ expect(another_tag_list).to eq(%w[awesome crazy alien])
68
+ expect(new_tag_list).to eq(%w[awesome radical crazy alien])
69
+ end
70
+
71
+ it 'should have class : ActsAsTaggableOn::TagList' do
72
+ new_tag_list = tag_list + another_tag_list
73
+ expect(new_tag_list.class).to eq(ActsAsTaggableOn::TagList)
74
+ end
75
+ end
76
+
77
+ describe '#concat' do
78
+ it 'should not have duplicate tags' do
79
+ expect(tag_list.concat(another_tag_list)).to eq(%w[awesome radical crazy alien])
80
+ end
81
+
82
+ it 'should have class : ActsAsTaggableOn::TagList' do
83
+ new_tag_list = tag_list.concat(another_tag_list)
84
+ expect(new_tag_list.class).to eq(ActsAsTaggableOn::TagList)
85
+ end
86
+
87
+ context 'without duplicates' do
88
+ let(:arr) { ['crazy', 'alien'] }
89
+ let(:another_tag_list) { ActsAsTaggableOn::TagList.new(*arr) }
90
+ it 'adds other list' do
91
+ expect(tag_list.concat(another_tag_list)).to eq(%w[awesome radical crazy alien])
92
+ end
93
+
94
+ it 'adds other array' do
95
+ expect(tag_list.concat(arr)).to eq(%w[awesome radical crazy alien])
96
+ end
97
+ end
98
+ end
99
+
100
+ describe '#to_s' do
101
+ it 'should give a delimited list of words when converted to string' do
102
+ expect(tag_list.to_s).to eq('awesome, radical')
103
+ end
104
+
105
+ it 'should be able to call to_s on a frozen tag list' do
106
+ tag_list.freeze
107
+ expect(-> { tag_list.add('cool', 'rad,bodacious') }).to raise_error(RuntimeError)
108
+ expect(-> { tag_list.to_s }).to_not raise_error
109
+ end
110
+ end
111
+
112
+ describe 'cleaning' do
113
+ it 'should parameterize if force_parameterize is set to true' do
114
+ ActsAsTaggableOn.force_parameterize = true
115
+ tag_list = ActsAsTaggableOn::TagList.new('awesome()', 'radical)(cc')
116
+
117
+ expect(tag_list.to_s).to eq('awesome, radical-cc')
118
+ ActsAsTaggableOn.force_parameterize = false
119
+ end
120
+
121
+ it 'should lowercase if force_lowercase is set to true' do
122
+ ActsAsTaggableOn.force_lowercase = true
123
+
124
+ tag_list = ActsAsTaggableOn::TagList.new('aweSomE', 'RaDicaL', 'Entrée')
125
+ expect(tag_list.to_s).to eq('awesome, radical, entrée')
126
+
127
+ ActsAsTaggableOn.force_lowercase = false
128
+ end
129
+
130
+ it 'should ignore case when removing duplicates if strict_case_match is false' do
131
+ tag_list = ActsAsTaggableOn::TagList.new('Junglist', 'JUNGLIST', 'Junglist', 'Massive', 'MASSIVE', 'MASSIVE')
132
+
133
+ expect(tag_list.to_s).to eq('Junglist, Massive')
134
+ end
135
+
136
+ it 'should not ignore case when removing duplicates if strict_case_match is true' do
137
+ ActsAsTaggableOn.strict_case_match = true
138
+ tag_list = ActsAsTaggableOn::TagList.new('Junglist', 'JUNGLIST', 'Junglist', 'Massive', 'MASSIVE', 'MASSIVE')
139
+
140
+ expect(tag_list.to_s).to eq('Junglist, JUNGLIST, Massive, MASSIVE')
141
+ ActsAsTaggableOn.strict_case_match = false
142
+ end
143
+ end
144
+
145
+ describe 'custom parser' do
146
+ let(:parser) { double(parse: %w(cool wicked)) }
147
+ let(:parser_class) { stub_const('MyParser', Class) }
148
+
149
+ it 'should use a the default parser if none is set as parameter' do
150
+ allow(ActsAsTaggableOn.default_parser).to receive(:new).and_return(parser)
151
+ ActsAsTaggableOn::TagList.new('cool, wicked', parse: true)
152
+
153
+ expect(parser).to have_received(:parse)
154
+ end
155
+
156
+ it 'should use the custom parser passed as parameter' do
157
+ allow(parser_class).to receive(:new).and_return(parser)
158
+
159
+ ActsAsTaggableOn::TagList.new('cool, wicked', parser: parser_class)
160
+
161
+ expect(parser).to have_received(:parse)
162
+ end
163
+
164
+ it 'should use the parser setted as attribute' do
165
+ allow(parser_class).to receive(:new).with('new, tag').and_return(parser)
166
+
167
+ tag_list = ActsAsTaggableOn::TagList.new('example')
168
+ tag_list.parser = parser_class
169
+ tag_list.add('new, tag', parse: true)
170
+
171
+ expect(parser).to have_received(:parse)
172
+ end
173
+ end
174
+
175
+
176
+ end
@@ -0,0 +1,340 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+ require 'db/migrate/2_add_missing_unique_indices.rb'
4
+
5
+
6
+ shared_examples_for 'without unique index' do
7
+ prepend_before(:all) { AddMissingUniqueIndices.down }
8
+ append_after(:all) do
9
+ ActsAsTaggableOn::Tag.delete_all
10
+ AddMissingUniqueIndices.up
11
+ end
12
+ end
13
+
14
+ describe ActsAsTaggableOn::Tag do
15
+ before(:each) do
16
+ @tag = ActsAsTaggableOn::Tag.new
17
+ @user = TaggableModel.create(name: 'Pablo')
18
+ end
19
+
20
+
21
+ describe 'named like any' do
22
+ context 'case insensitive collation and unique index on tag name', if: using_case_insensitive_collation? 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
32
+
33
+ context 'case insensitive collation without indexes or case sensitive collation with indexes' do
34
+ if using_case_insensitive_collation?
35
+ include_context 'without unique index'
36
+ end
37
+
38
+ before(:each) do
39
+ ActsAsTaggableOn::Tag.create(name: 'Awesome')
40
+ ActsAsTaggableOn::Tag.create(name: 'awesome')
41
+ ActsAsTaggableOn::Tag.create(name: 'epic')
42
+ end
43
+
44
+ it 'should find both tags' do
45
+ expect(ActsAsTaggableOn::Tag.named_like_any(%w(awesome epic)).count).to eq(3)
46
+ end
47
+ end
48
+ end
49
+
50
+ describe 'named any' do
51
+ context 'with some special characters combinations', if: using_mysql? do
52
+ it 'should not raise an invalid encoding exception' do
53
+ expect{ActsAsTaggableOn::Tag.named_any(["holä", "hol'ä"])}.not_to raise_error
54
+ end
55
+ end
56
+ end
57
+
58
+ describe 'for context' do
59
+ before(:each) do
60
+ @user.skill_list.add('ruby')
61
+ @user.save
62
+ end
63
+
64
+ it 'should return tags that have been used in the given context' do
65
+ expect(ActsAsTaggableOn::Tag.for_context('skills').pluck(:name)).to include('ruby')
66
+ end
67
+
68
+ it 'should not return tags that have been used in other contexts' do
69
+ expect(ActsAsTaggableOn::Tag.for_context('needs').pluck(:name)).to_not include('ruby')
70
+ end
71
+ end
72
+
73
+ describe 'find or create by name' do
74
+ before(:each) do
75
+ @tag.name = 'awesome'
76
+ @tag.save
77
+ end
78
+
79
+ it 'should find by name' do
80
+ expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('awesome')).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('AWESOME')).to eq(@tag)
85
+ end
86
+
87
+ it 'should create by name' do
88
+ expect(-> {
89
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('epic')
90
+ }).to change(ActsAsTaggableOn::Tag, :count).by(1)
91
+ end
92
+ end
93
+
94
+ describe 'find or create by unicode name', unless: using_sqlite? do
95
+ before(:each) do
96
+ @tag.name = 'привет'
97
+ @tag.save
98
+ end
99
+
100
+ it 'should find by name' do
101
+ expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('привет')).to eq(@tag)
102
+ end
103
+
104
+ it 'should find by name case insensitive' do
105
+ expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('ПРИВЕТ')).to eq(@tag)
106
+ end
107
+
108
+ it 'should find by name accent insensitive', if: using_case_insensitive_collation? do
109
+ @tag.name = 'inupiat'
110
+ @tag.save
111
+ expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('Iñupiat')).to eq(@tag)
112
+ end
113
+ end
114
+
115
+ describe 'find or create all by any name' do
116
+ before(:each) do
117
+ @tag.name = 'awesome'
118
+ @tag.save
119
+ end
120
+
121
+ it 'should find by name' do
122
+ expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('awesome')).to eq([@tag])
123
+ end
124
+
125
+ it 'should find by name case insensitive' do
126
+ expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('AWESOME')).to eq([@tag])
127
+ end
128
+
129
+ context 'case sensitive' do
130
+ if using_case_insensitive_collation?
131
+ include_context 'without unique index'
132
+ end
133
+
134
+ it 'should find by name case sensitive' do
135
+ ActsAsTaggableOn.strict_case_match = true
136
+ expect {
137
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('AWESOME')
138
+ }.to change(ActsAsTaggableOn::Tag, :count).by(1)
139
+ end
140
+ end
141
+
142
+ it 'should create by name' do
143
+ expect {
144
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('epic')
145
+ }.to change(ActsAsTaggableOn::Tag, :count).by(1)
146
+ end
147
+
148
+ context 'case sensitive' do
149
+ if using_case_insensitive_collation?
150
+ include_context 'without unique index'
151
+ end
152
+
153
+ it 'should find or create by name case sensitive' do
154
+ ActsAsTaggableOn.strict_case_match = true
155
+ expect {
156
+ expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('AWESOME', 'awesome').map(&:name)).to eq(%w(AWESOME awesome))
157
+ }.to change(ActsAsTaggableOn::Tag, :count).by(1)
158
+ end
159
+ end
160
+
161
+ it 'should find or create by name' do
162
+ expect {
163
+ expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('awesome', 'epic').map(&:name)).to eq(%w(awesome epic))
164
+ }.to change(ActsAsTaggableOn::Tag, :count).by(1)
165
+ end
166
+
167
+ it 'should return an empty array if no tags are specified' do
168
+ expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name([])).to be_empty
169
+ end
170
+ end
171
+
172
+ it 'should require a name' do
173
+ @tag.valid?
174
+ #TODO, we should find another way to check this
175
+ expect(@tag.errors[:name]).to eq(["can't be blank"])
176
+
177
+ @tag.name = 'something'
178
+ @tag.valid?
179
+
180
+ expect(@tag.errors[:name]).to be_empty
181
+ end
182
+
183
+ it 'should limit the name length to 255 or less characters' do
184
+ @tag.name = 'fgkgnkkgjymkypbuozmwwghblmzpqfsgjasflblywhgkwndnkzeifalfcpeaeqychjuuowlacmuidnnrkprgpcpybarbkrmziqihcrxirlokhnzfvmtzixgvhlxzncyywficpraxfnjptxxhkqmvicbcdcynkjvziefqzyndxkjmsjlvyvbwraklbalykyxoliqdlreeykuphdtmzfdwpphmrqvwvqffojkqhlzvinqajsxbszyvrqqyzusxranr'
185
+ @tag.valid?
186
+ #TODO, we should find another way to check this
187
+ expect(@tag.errors[:name]).to eq(['is too long (maximum is 255 characters)'])
188
+
189
+ @tag.name = 'fgkgnkkgjymkypbuozmwwghblmzpqfsgjasflblywhgkwndnkzeifalfcpeaeqychjuuowlacmuidnnrkprgpcpybarbkrmziqihcrxirlokhnzfvmtzixgvhlxzncyywficpraxfnjptxxhkqmvicbcdcynkjvziefqzyndxkjmsjlvyvbwraklbalykyxoliqdlreeykuphdtmzfdwpphmrqvwvqffojkqhlzvinqajsxbszyvrqqyzusxran'
190
+ @tag.valid?
191
+ expect(@tag.errors[:name]).to be_empty
192
+ end
193
+
194
+ it 'should equal a tag with the same name' do
195
+ @tag.name = 'awesome'
196
+ new_tag = ActsAsTaggableOn::Tag.new(name: 'awesome')
197
+ expect(new_tag).to eq(@tag)
198
+ end
199
+
200
+ it 'should return its name when to_s is called' do
201
+ @tag.name = 'cool'
202
+ expect(@tag.to_s).to eq('cool')
203
+ end
204
+
205
+ it 'have named_scope named(something)' do
206
+ @tag.name = 'cool'
207
+ @tag.save!
208
+ expect(ActsAsTaggableOn::Tag.named('cool')).to include(@tag)
209
+ end
210
+
211
+ it 'have named_scope named_like(something)' do
212
+ @tag.name = 'cool'
213
+ @tag.save!
214
+ @another_tag = ActsAsTaggableOn::Tag.create!(name: 'coolip')
215
+ expect(ActsAsTaggableOn::Tag.named_like('cool')).to include(@tag, @another_tag)
216
+ end
217
+
218
+ describe 'escape wildcard symbols in like requests' do
219
+ before(:each) do
220
+ @tag.name = 'cool'
221
+ @tag.save
222
+ @another_tag = ActsAsTaggableOn::Tag.create!(name: 'coo%')
223
+ @another_tag2 = ActsAsTaggableOn::Tag.create!(name: 'coolish')
224
+ end
225
+
226
+ it "return escaped result when '%' char present in tag" do
227
+ expect(ActsAsTaggableOn::Tag.named_like('coo%')).to_not include(@tag)
228
+ expect(ActsAsTaggableOn::Tag.named_like('coo%')).to include(@another_tag)
229
+ end
230
+
231
+ end
232
+
233
+ describe 'when using strict_case_match' do
234
+ before do
235
+ ActsAsTaggableOn.strict_case_match = true
236
+ @tag.name = 'awesome'
237
+ @tag.save!
238
+ end
239
+
240
+ after do
241
+ ActsAsTaggableOn.strict_case_match = false
242
+ end
243
+
244
+ it 'should find by name' do
245
+ expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('awesome')).to eq(@tag)
246
+ end
247
+
248
+ context 'case sensitive' do
249
+ if using_case_insensitive_collation?
250
+ include_context 'without unique index'
251
+ end
252
+
253
+ it 'should find by name case sensitively' do
254
+ expect {
255
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('AWESOME')
256
+ }.to change(ActsAsTaggableOn::Tag, :count)
257
+
258
+ expect(ActsAsTaggableOn::Tag.last.name).to eq('AWESOME')
259
+ end
260
+ end
261
+
262
+ context 'case sensitive' do
263
+ if using_case_insensitive_collation?
264
+ include_context 'without unique index'
265
+ end
266
+
267
+ it 'should have a named_scope named(something) that matches exactly' do
268
+ uppercase_tag = ActsAsTaggableOn::Tag.create(name: 'Cool')
269
+ @tag.name = 'cool'
270
+ @tag.save!
271
+
272
+ expect(ActsAsTaggableOn::Tag.named('cool')).to include(@tag)
273
+ expect(ActsAsTaggableOn::Tag.named('cool')).to_not include(uppercase_tag)
274
+ end
275
+ end
276
+
277
+ it 'should not change encoding' do
278
+ name = "\u3042"
279
+ original_encoding = name.encoding
280
+ record = ActsAsTaggableOn::Tag.find_or_create_with_like_by_name(name)
281
+ record.reload
282
+ expect(record.name.encoding).to eq(original_encoding)
283
+ end
284
+
285
+ context 'named any with some special characters combinations', if: using_mysql? do
286
+ it 'should not raise an invalid encoding exception' do
287
+ expect{ActsAsTaggableOn::Tag.named_any(["holä", "hol'ä"])}.not_to raise_error
288
+ end
289
+ end
290
+ end
291
+
292
+ describe 'name uniqeness validation' do
293
+ let(:duplicate_tag) { ActsAsTaggableOn::Tag.new(name: 'ror') }
294
+
295
+ before { ActsAsTaggableOn::Tag.create(name: 'ror') }
296
+
297
+ context "when don't need unique names" do
298
+ include_context 'without unique index'
299
+ it 'should not run uniqueness validation' do
300
+ allow(duplicate_tag).to receive(:validates_name_uniqueness?) { false }
301
+ duplicate_tag.save
302
+ expect(duplicate_tag).to be_persisted
303
+ end
304
+ end
305
+
306
+ context 'when do need unique names' do
307
+ it 'should run uniqueness validation' do
308
+ expect(duplicate_tag).to_not be_valid
309
+ end
310
+
311
+ it 'add error to name' do
312
+ duplicate_tag.save
313
+
314
+ expect(duplicate_tag.errors.size).to eq(1)
315
+ expect(duplicate_tag.errors.messages[:name]).to include('has already been taken')
316
+ end
317
+ end
318
+ end
319
+
320
+ describe 'popular tags' do
321
+ before do
322
+ %w(sports rails linux tennis golden_syrup).each_with_index do |t, i|
323
+ tag = ActsAsTaggableOn::Tag.new(name: t)
324
+ tag.taggings_count = i
325
+ tag.save!
326
+ end
327
+ end
328
+
329
+ it 'should find the most popular tags' do
330
+ expect(ActsAsTaggableOn::Tag.most_used(3).first.name).to eq("golden_syrup")
331
+ expect(ActsAsTaggableOn::Tag.most_used(3).length).to eq(3)
332
+ end
333
+
334
+ it 'should find the least popular tags' do
335
+ expect(ActsAsTaggableOn::Tag.least_used(3).first.name).to eq("sports")
336
+ expect(ActsAsTaggableOn::Tag.least_used(3).length).to eq(3)
337
+ end
338
+ end
339
+
340
+ end