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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +39 -0
- data/Appraisals +15 -0
- data/CHANGELOG.md +330 -0
- data/CONTRIBUTING.md +57 -0
- data/Gemfile +11 -0
- data/Guardfile +5 -0
- data/LICENSE.md +20 -0
- data/README.md +555 -0
- data/Rakefile +21 -0
- data/UPGRADING.md +8 -0
- data/acts-as-taggable-on.gemspec +32 -0
- data/db/migrate/1_acts_as_taggable_on_migration.rb +36 -0
- data/db/migrate/2_add_missing_unique_indices.rb +25 -0
- data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +19 -0
- data/db/migrate/4_add_missing_taggable_index.rb +14 -0
- data/db/migrate/5_change_collation_for_tag_names.rb +14 -0
- data/db/migrate/6_add_missing_indexes_on_taggings.rb +22 -0
- data/gemfiles/activerecord_5.0.gemfile +21 -0
- data/gemfiles/activerecord_5.1.gemfile +21 -0
- data/gemfiles/activerecord_5.2.gemfile +21 -0
- data/gemfiles/activerecord_6.0.gemfile +21 -0
- data/lib/acts-as-taggable-on.rb +133 -0
- data/lib/acts_as_taggable_on.rb +6 -0
- data/lib/acts_as_taggable_on/default_parser.rb +79 -0
- data/lib/acts_as_taggable_on/engine.rb +4 -0
- data/lib/acts_as_taggable_on/generic_parser.rb +19 -0
- data/lib/acts_as_taggable_on/tag.rb +139 -0
- data/lib/acts_as_taggable_on/tag_list.rb +106 -0
- data/lib/acts_as_taggable_on/taggable.rb +101 -0
- data/lib/acts_as_taggable_on/taggable/cache.rb +90 -0
- data/lib/acts_as_taggable_on/taggable/collection.rb +183 -0
- data/lib/acts_as_taggable_on/taggable/core.rb +322 -0
- data/lib/acts_as_taggable_on/taggable/ownership.rb +136 -0
- data/lib/acts_as_taggable_on/taggable/related.rb +71 -0
- data/lib/acts_as_taggable_on/taggable/tag_list_type.rb +4 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query.rb +16 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb +111 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +70 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb +82 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +61 -0
- data/lib/acts_as_taggable_on/tagger.rb +89 -0
- data/lib/acts_as_taggable_on/tagging.rb +36 -0
- data/lib/acts_as_taggable_on/tags_helper.rb +15 -0
- data/lib/acts_as_taggable_on/utils.rb +37 -0
- data/lib/acts_as_taggable_on/version.rb +3 -0
- data/lib/tasks/tags_collate_utf8.rake +21 -0
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +285 -0
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +112 -0
- data/spec/acts_as_taggable_on/caching_spec.rb +129 -0
- data/spec/acts_as_taggable_on/default_parser_spec.rb +47 -0
- data/spec/acts_as_taggable_on/dirty_spec.rb +142 -0
- data/spec/acts_as_taggable_on/generic_parser_spec.rb +14 -0
- data/spec/acts_as_taggable_on/related_spec.rb +99 -0
- data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +231 -0
- data/spec/acts_as_taggable_on/tag_list_spec.rb +176 -0
- data/spec/acts_as_taggable_on/tag_spec.rb +340 -0
- data/spec/acts_as_taggable_on/taggable_spec.rb +817 -0
- data/spec/acts_as_taggable_on/tagger_spec.rb +153 -0
- data/spec/acts_as_taggable_on/tagging_spec.rb +117 -0
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +45 -0
- data/spec/acts_as_taggable_on/utils_spec.rb +23 -0
- data/spec/internal/app/models/altered_inheriting_taggable_model.rb +5 -0
- data/spec/internal/app/models/cached_model.rb +3 -0
- data/spec/internal/app/models/cached_model_with_array.rb +11 -0
- data/spec/internal/app/models/columns_override_model.rb +5 -0
- data/spec/internal/app/models/company.rb +15 -0
- data/spec/internal/app/models/inheriting_taggable_model.rb +4 -0
- data/spec/internal/app/models/market.rb +2 -0
- data/spec/internal/app/models/non_standard_id_taggable_model.rb +8 -0
- data/spec/internal/app/models/ordered_taggable_model.rb +4 -0
- data/spec/internal/app/models/other_cached_model.rb +3 -0
- data/spec/internal/app/models/other_taggable_model.rb +4 -0
- data/spec/internal/app/models/student.rb +4 -0
- data/spec/internal/app/models/taggable_model.rb +14 -0
- data/spec/internal/app/models/untaggable_model.rb +3 -0
- data/spec/internal/app/models/user.rb +3 -0
- data/spec/internal/config/database.yml.sample +19 -0
- data/spec/internal/db/schema.rb +110 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/0-helpers.rb +32 -0
- data/spec/support/array.rb +9 -0
- data/spec/support/database.rb +36 -0
- data/spec/support/database_cleaner.rb +21 -0
- 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
|