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