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