acts-as-taggable-on 9.0.0 → 11.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/spec.yml +12 -14
- data/Appraisals +10 -7
- data/CHANGELOG.md +22 -3
- data/CONTRIBUTING.md +3 -3
- data/Gemfile +0 -1
- data/README.md +16 -4
- data/acts-as-taggable-on.gemspec +5 -5
- data/db/migrate/1_acts_as_taggable_on_migration.rb +0 -1
- data/gemfiles/activerecord_7.0.gemfile +2 -2
- data/gemfiles/{activerecord_6.0.gemfile → activerecord_7.1.gemfile} +2 -2
- data/gemfiles/{activerecord_6.1.gemfile → activerecord_7.2.gemfile} +2 -2
- data/lib/{acts_as_taggable_on → acts-as-taggable-on}/tag.rb +4 -3
- data/lib/acts-as-taggable-on/taggable/caching.rb +46 -0
- data/lib/{acts_as_taggable_on → acts-as-taggable-on}/taggable/collection.rb +5 -4
- data/lib/{acts_as_taggable_on → acts-as-taggable-on}/taggable/core.rb +4 -6
- data/lib/{acts_as_taggable_on → acts-as-taggable-on}/taggable/ownership.rb +5 -7
- data/lib/{acts_as_taggable_on → acts-as-taggable-on}/taggable/tagged_with_query/query_base.rb +11 -2
- data/lib/{acts_as_taggable_on → acts-as-taggable-on}/taggable.rb +8 -8
- data/lib/{acts_as_taggable_on → acts-as-taggable-on}/tagger.rb +5 -13
- data/lib/{acts_as_taggable_on → acts-as-taggable-on}/tagging.rb +1 -1
- data/lib/{acts_as_taggable_on → acts-as-taggable-on}/utils.rb +0 -4
- data/lib/{acts_as_taggable_on → acts-as-taggable-on}/version.rb +1 -1
- data/lib/acts-as-taggable-on.rb +14 -29
- data/lib/tasks/example/acts-as-taggable-on.rb.example +8 -0
- data/lib/tasks/install_initializer.rake +23 -0
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +5 -10
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +2 -2
- data/spec/acts_as_taggable_on/caching_spec.rb +0 -4
- data/spec/acts_as_taggable_on/tag_list_spec.rb +3 -3
- data/spec/acts_as_taggable_on/tag_spec.rb +45 -2
- data/spec/acts_as_taggable_on/taggable_spec.rb +51 -44
- data/spec/acts_as_taggable_on/tagger_spec.rb +8 -8
- data/spec/acts_as_taggable_on/tagging_spec.rb +28 -2
- data/spec/support/database.rb +3 -11
- data/spec/support/database_cleaner.rb +4 -0
- metadata +57 -36
- data/lib/acts_as_taggable_on/taggable/cache.rb +0 -92
- data/lib/acts_as_taggable_on.rb +0 -6
- /data/lib/{acts_as_taggable_on → acts-as-taggable-on}/default_parser.rb +0 -0
- /data/lib/{acts_as_taggable_on → acts-as-taggable-on}/engine.rb +0 -0
- /data/lib/{acts_as_taggable_on → acts-as-taggable-on}/generic_parser.rb +0 -0
- /data/lib/{acts_as_taggable_on → acts-as-taggable-on}/tag_list.rb +0 -0
- /data/lib/{acts_as_taggable_on → acts-as-taggable-on}/taggable/related.rb +0 -0
- /data/lib/{acts_as_taggable_on → acts-as-taggable-on}/taggable/tag_list_type.rb +0 -0
- /data/lib/{acts_as_taggable_on → acts-as-taggable-on}/taggable/tagged_with_query/all_tags_query.rb +0 -0
- /data/lib/{acts_as_taggable_on → acts-as-taggable-on}/taggable/tagged_with_query/any_tags_query.rb +0 -0
- /data/lib/{acts_as_taggable_on → acts-as-taggable-on}/taggable/tagged_with_query/exclude_tags_query.rb +0 -0
- /data/lib/{acts_as_taggable_on → acts-as-taggable-on}/taggable/tagged_with_query.rb +0 -0
- /data/lib/{acts_as_taggable_on → acts-as-taggable-on}/tags_helper.rb +0 -0
data/lib/acts-as-taggable-on.rb
CHANGED
@@ -1,43 +1,21 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
require 'active_record/version'
|
3
3
|
require 'active_support/core_ext/module'
|
4
|
+
require 'zeitwerk'
|
5
|
+
|
6
|
+
loader = Zeitwerk::Loader.for_gem
|
7
|
+
loader.inflector.inflect "acts-as-taggable-on" => "ActsAsTaggableOn"
|
8
|
+
loader.setup
|
4
9
|
|
5
10
|
begin
|
6
11
|
require 'rails/engine'
|
7
|
-
require '
|
12
|
+
require 'acts-as-taggable-on/engine'
|
8
13
|
rescue LoadError
|
9
|
-
|
10
14
|
end
|
11
15
|
|
12
16
|
require 'digest/sha1'
|
13
17
|
|
14
18
|
module ActsAsTaggableOn
|
15
|
-
extend ActiveSupport::Autoload
|
16
|
-
|
17
|
-
autoload :Tag
|
18
|
-
autoload :TagList
|
19
|
-
autoload :GenericParser
|
20
|
-
autoload :DefaultParser
|
21
|
-
autoload :Taggable
|
22
|
-
autoload :Tagger
|
23
|
-
autoload :Tagging
|
24
|
-
autoload :TagsHelper
|
25
|
-
autoload :VERSION
|
26
|
-
|
27
|
-
autoload_under 'taggable' do
|
28
|
-
autoload :Cache
|
29
|
-
autoload :Collection
|
30
|
-
autoload :Core
|
31
|
-
autoload :Dirty
|
32
|
-
autoload :Ownership
|
33
|
-
autoload :Related
|
34
|
-
autoload :TagListType
|
35
|
-
end
|
36
|
-
|
37
|
-
autoload :Utils
|
38
|
-
autoload :Compatibility
|
39
|
-
|
40
|
-
|
41
19
|
class DuplicateTagError < StandardError
|
42
20
|
end
|
43
21
|
|
@@ -66,7 +44,7 @@ module ActsAsTaggableOn
|
|
66
44
|
:remove_unused_tags, :default_parser,
|
67
45
|
:tags_counter, :tags_table,
|
68
46
|
:taggings_table
|
69
|
-
attr_reader :delimiter, :strict_case_match
|
47
|
+
attr_reader :delimiter, :strict_case_match, :base_class
|
70
48
|
|
71
49
|
def initialize
|
72
50
|
@delimiter = ','
|
@@ -79,6 +57,7 @@ module ActsAsTaggableOn
|
|
79
57
|
@force_binary_collation = false
|
80
58
|
@tags_table = :tags
|
81
59
|
@taggings_table = :taggings
|
60
|
+
@base_class = '::ActiveRecord::Base'
|
82
61
|
end
|
83
62
|
|
84
63
|
def strict_case_match=(force_cs)
|
@@ -119,6 +98,11 @@ WARNING
|
|
119
98
|
end
|
120
99
|
end
|
121
100
|
|
101
|
+
def base_class=(base_class)
|
102
|
+
raise "base_class must be a String" unless base_class.is_a?(String)
|
103
|
+
@base_class = base_class
|
104
|
+
end
|
105
|
+
|
122
106
|
end
|
123
107
|
|
124
108
|
setup
|
@@ -128,6 +112,7 @@ ActiveSupport.on_load(:active_record) do
|
|
128
112
|
extend ActsAsTaggableOn::Taggable
|
129
113
|
include ActsAsTaggableOn::Tagger
|
130
114
|
end
|
115
|
+
|
131
116
|
ActiveSupport.on_load(:action_view) do
|
132
117
|
include ActsAsTaggableOn::TagsHelper
|
133
118
|
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
ActsAsTaggableOn.setup do |config|
|
2
|
+
# This works because the classes where the base class is a concern, Tag and Tagging
|
3
|
+
# are autoloaded, and won't be started until after the initializers run. The value
|
4
|
+
# must be a String, as the Rails Zeitwerk autoloader will not allow models to be
|
5
|
+
# referenced at initialization time.
|
6
|
+
#
|
7
|
+
# config.base_class = 'ApplicationRecord'
|
8
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
namespace :acts_as_taggable_on do
|
2
|
+
|
3
|
+
namespace :sharded_db do
|
4
|
+
|
5
|
+
desc "Install initializer setting custom base class"
|
6
|
+
task :install_initializer => [:environment, "config/initializers/foo"] do
|
7
|
+
source = File.join(
|
8
|
+
Gem.loaded_specs["acts-as-taggable-on"].full_gem_path,
|
9
|
+
"lib",
|
10
|
+
"tasks",
|
11
|
+
"examples",
|
12
|
+
"acts-as-taggable-on.rb.example"
|
13
|
+
)
|
14
|
+
|
15
|
+
destination = "config/initializers/acts-as-taggable-on.rb"
|
16
|
+
|
17
|
+
cp source, destination
|
18
|
+
end
|
19
|
+
|
20
|
+
directory "config/initializers"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -197,7 +197,7 @@ describe 'Acts As Taggable On' do
|
|
197
197
|
|
198
198
|
its(:language_list) { should == ['ruby', '.net']}
|
199
199
|
its(:cached_language_list) { should == 'ruby, .net' } # passes
|
200
|
-
its(:instance_variables) { should include(
|
200
|
+
its(:instance_variables) { should include(:@language_list) }
|
201
201
|
end
|
202
202
|
|
203
203
|
context 'status taggings cache after update' do
|
@@ -207,8 +207,8 @@ describe 'Acts As Taggable On' do
|
|
207
207
|
its(:status_list) { should == ['happy', 'married'] }
|
208
208
|
its(:cached_status_list) { should == 'happy, married' } # fails
|
209
209
|
its(:cached_status_list) { should_not == '' } # fails, is blank
|
210
|
-
its(:instance_variables) { should include(
|
211
|
-
its(:instance_variables) { should_not include(
|
210
|
+
its(:instance_variables) { should include(:@status_list) }
|
211
|
+
its(:instance_variables) { should_not include(:@statu_list) } # fails, note: one "s"
|
212
212
|
|
213
213
|
end
|
214
214
|
|
@@ -221,13 +221,8 @@ describe 'Acts As Taggable On' do
|
|
221
221
|
its(:glass_list) { should == ['rectangle', 'aviator'] }
|
222
222
|
its(:cached_glass_list) { should == 'rectangle, aviator' } # fails
|
223
223
|
its(:cached_glass_list) { should_not == '' } # fails, is blank
|
224
|
-
|
225
|
-
|
226
|
-
its(:instance_variables) { should_not include('@glas_list') } # fails, note: one "s"
|
227
|
-
else
|
228
|
-
its(:instance_variables) { should include(:@glass_list) }
|
229
|
-
its(:instance_variables) { should_not include(:@glas_list) } # fails, note: one "s"
|
230
|
-
end
|
224
|
+
its(:instance_variables) { should include(:@glass_list) }
|
225
|
+
its(:instance_variables) { should_not include(:@glas_list) } # fails, note: one "s"
|
231
226
|
|
232
227
|
end
|
233
228
|
end
|
@@ -70,9 +70,9 @@ describe 'acts_as_tagger' do
|
|
70
70
|
|
71
71
|
it 'should throw an exception when the default is over-ridden' do
|
72
72
|
expect(@taggable.tag_list_on(:foo_boo)).to be_empty
|
73
|
-
expect
|
73
|
+
expect {
|
74
74
|
@tagger.tag(@taggable, with: 'this, and, that', on: :foo_boo, force: false)
|
75
|
-
}
|
75
|
+
}.to raise_error(RuntimeError)
|
76
76
|
end
|
77
77
|
|
78
78
|
it 'should not create the tag context on-the-fly when the default is over-ridden' do
|
@@ -16,10 +16,6 @@ describe 'Acts As Taggable On' do
|
|
16
16
|
expect(@taggable).to respond_to(:save_tags)
|
17
17
|
end
|
18
18
|
|
19
|
-
it 'should add cached tag lists to the instance if cached column is not present' do
|
20
|
-
expect(TaggableModel.new(name: 'Art Kram')).to_not respond_to(:save_cached_tag_list)
|
21
|
-
end
|
22
|
-
|
23
19
|
it 'should generate a cached column checker for each tag type' do
|
24
20
|
expect(CachedModel).to respond_to(:caching_tag_list?)
|
25
21
|
expect(OtherCachedModel).to respond_to(:caching_language_list?)
|
@@ -104,8 +104,8 @@ describe ActsAsTaggableOn::TagList do
|
|
104
104
|
|
105
105
|
it 'should be able to call to_s on a frozen tag list' do
|
106
106
|
tag_list.freeze
|
107
|
-
expect
|
108
|
-
expect
|
107
|
+
expect { tag_list.add('cool', 'rad,bodacious') }.to raise_error(RuntimeError)
|
108
|
+
expect { tag_list.to_s }.to_not raise_error
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
@@ -161,7 +161,7 @@ describe ActsAsTaggableOn::TagList do
|
|
161
161
|
expect(parser).to have_received(:parse)
|
162
162
|
end
|
163
163
|
|
164
|
-
it 'should use the parser
|
164
|
+
it 'should use the parser set as attribute' do
|
165
165
|
allow(parser_class).to receive(:new).with('new, tag').and_return(parser)
|
166
166
|
|
167
167
|
tag_list = ActsAsTaggableOn::TagList.new('example')
|
@@ -100,9 +100,9 @@ describe ActsAsTaggableOn::Tag do
|
|
100
100
|
end
|
101
101
|
|
102
102
|
it 'should create by name' do
|
103
|
-
expect
|
103
|
+
expect {
|
104
104
|
ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('epic')
|
105
|
-
}
|
105
|
+
}.to change(ActsAsTaggableOn::Tag, :count).by(1)
|
106
106
|
end
|
107
107
|
end
|
108
108
|
|
@@ -182,6 +182,25 @@ describe ActsAsTaggableOn::Tag do
|
|
182
182
|
it 'should return an empty array if no tags are specified' do
|
183
183
|
expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name([])).to be_empty
|
184
184
|
end
|
185
|
+
|
186
|
+
context 'another tag is created concurrently', :database_cleaner_delete, if: supports_concurrency? do
|
187
|
+
it 'retries and finds tag if tag with same name created concurrently' do
|
188
|
+
tag_name = 'super'
|
189
|
+
|
190
|
+
expect(ActsAsTaggableOn::Tag).to receive(:create).with(name: tag_name) do
|
191
|
+
# Simulate concurrent tag creation
|
192
|
+
Thread.new do
|
193
|
+
ActsAsTaggableOn::Tag.new(name: tag_name).save!
|
194
|
+
end.join
|
195
|
+
|
196
|
+
raise ActiveRecord::RecordNotUnique
|
197
|
+
end
|
198
|
+
|
199
|
+
expect {
|
200
|
+
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_name)
|
201
|
+
}.to change(ActsAsTaggableOn::Tag, :count).by(1)
|
202
|
+
end
|
203
|
+
end
|
185
204
|
end
|
186
205
|
|
187
206
|
it 'should require a name' do
|
@@ -352,4 +371,28 @@ describe ActsAsTaggableOn::Tag do
|
|
352
371
|
end
|
353
372
|
end
|
354
373
|
|
374
|
+
describe 'base_class' do
|
375
|
+
before do
|
376
|
+
class Foo < ActiveRecord::Base; end
|
377
|
+
end
|
378
|
+
|
379
|
+
context "default" do
|
380
|
+
it "inherits from ActiveRecord::Base" do
|
381
|
+
|
382
|
+
expect(ActsAsTaggableOn::Tag.ancestors).to include(ActiveRecord::Base)
|
383
|
+
expect(ActsAsTaggableOn::Tag.ancestors).to_not include(Foo)
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
context "custom" do
|
388
|
+
it "inherits from custom class" do
|
389
|
+
|
390
|
+
ActsAsTaggableOn.base_class = 'Foo'
|
391
|
+
hide_const("ActsAsTaggableOn::Tag")
|
392
|
+
load("lib/acts-as-taggable-on/tag.rb")
|
393
|
+
|
394
|
+
expect(ActsAsTaggableOn::Tag.ancestors).to include(Foo)
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
355
398
|
end
|
@@ -27,9 +27,9 @@ describe 'Taggable To Preserve Order' do
|
|
27
27
|
@taggable.tag_list = 'rails, ruby, css'
|
28
28
|
expect(@taggable.instance_variable_get('@tag_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy
|
29
29
|
|
30
|
-
expect
|
30
|
+
expect{
|
31
31
|
@taggable.save
|
32
|
-
}
|
32
|
+
}.to change(ActsAsTaggableOn::Tag, :count).by(3)
|
33
33
|
|
34
34
|
@taggable.reload
|
35
35
|
expect(@taggable.tag_list).to eq(%w(rails ruby css))
|
@@ -61,9 +61,9 @@ describe 'Taggable To Preserve Order' do
|
|
61
61
|
@taggable.tag_list = 'pow, ruby, rails'
|
62
62
|
expect(@taggable.instance_variable_get('@tag_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy
|
63
63
|
|
64
|
-
expect
|
64
|
+
expect {
|
65
65
|
@taggable.save
|
66
|
-
}
|
66
|
+
}.to change(ActsAsTaggableOn::Tag, :count).by(3)
|
67
67
|
|
68
68
|
@taggable.reload
|
69
69
|
expect(@taggable.tags.map { |t| t.name }).to eq(%w(pow ruby rails))
|
@@ -157,9 +157,9 @@ describe 'Taggable' do
|
|
157
157
|
@taggable.skill_list = 'ruby, rails, css'
|
158
158
|
expect(@taggable.instance_variable_get('@skill_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy
|
159
159
|
|
160
|
-
expect
|
160
|
+
expect{
|
161
161
|
@taggable.save
|
162
|
-
}
|
162
|
+
}.to change(ActsAsTaggableOn::Tag, :count).by(3)
|
163
163
|
|
164
164
|
@taggable.reload
|
165
165
|
expect(@taggable.skill_list.sort).to eq(%w(ruby rails css).sort)
|
@@ -480,6 +480,10 @@ describe 'Taggable' do
|
|
480
480
|
jim = TaggableModel.create(name: 'Jim', tag_list: 'jim, steve')
|
481
481
|
|
482
482
|
expect(TaggableModel.tagged_with(%w(bob tricia), wild: true, any: true).to_a.sort_by { |o| o.id }).to eq([bob, frank, steve])
|
483
|
+
expect(TaggableModel.tagged_with(%w(bob tricia), wild: :prefix, any: true).to_a.sort_by { |o| o.id }).to eq([bob, steve])
|
484
|
+
expect(TaggableModel.tagged_with(%w(bob tricia), wild: :suffix, any: true).to_a.sort_by { |o| o.id }).to eq([bob, frank])
|
485
|
+
expect(TaggableModel.tagged_with(%w(cia), wild: :prefix, any: true).to_a.sort_by { |o| o.id }).to eq([bob, steve])
|
486
|
+
expect(TaggableModel.tagged_with(%w(j), wild: :suffix, any: true).to_a.sort_by { |o| o.id }).to eq([frank, steve, jim])
|
483
487
|
expect(TaggableModel.tagged_with(%w(bob tricia), wild: true, exclude: true).to_a).to eq([jim])
|
484
488
|
expect(TaggableModel.tagged_with('ji', wild: true, any: true).to_a).to match_array([frank, jim])
|
485
489
|
end
|
@@ -555,39 +559,39 @@ describe 'Taggable' do
|
|
555
559
|
let(:bob) { TaggableModel.create(name: 'Bob') }
|
556
560
|
context 'case sensitive' do
|
557
561
|
it '#add' do
|
558
|
-
expect
|
562
|
+
expect {
|
559
563
|
bob.tag_list.add 'happier'
|
560
564
|
bob.tag_list.add 'happier'
|
561
565
|
bob.tag_list.add 'happier', 'rich', 'funny'
|
562
566
|
bob.save
|
563
|
-
}
|
567
|
+
}.to change(ActsAsTaggableOn::Tagging, :count).by(3)
|
564
568
|
end
|
565
569
|
it '#<<' do
|
566
|
-
expect
|
570
|
+
expect {
|
567
571
|
bob.tag_list << 'social'
|
568
572
|
bob.tag_list << 'social'
|
569
573
|
bob.tag_list << 'social' << 'wow'
|
570
574
|
bob.save
|
571
|
-
}
|
575
|
+
}.to change(ActsAsTaggableOn::Tagging, :count).by(2)
|
572
576
|
|
573
577
|
end
|
574
578
|
|
575
579
|
it 'unicode' do
|
576
580
|
|
577
|
-
expect
|
581
|
+
expect {
|
578
582
|
bob.tag_list.add 'ПРИВЕТ'
|
579
583
|
bob.tag_list.add 'ПРИВЕТ'
|
580
584
|
bob.tag_list.add 'ПРИВЕТ', 'ПРИВЕТ'
|
581
585
|
bob.save
|
582
|
-
}
|
586
|
+
}.to change(ActsAsTaggableOn::Tagging, :count).by(1)
|
583
587
|
|
584
588
|
end
|
585
589
|
|
586
590
|
it '#=' do
|
587
|
-
expect
|
591
|
+
expect {
|
588
592
|
bob.tag_list = ['Happy', 'Happy']
|
589
593
|
bob.save
|
590
|
-
}
|
594
|
+
}.to change(ActsAsTaggableOn::Tagging, :count).by(1)
|
591
595
|
end
|
592
596
|
end
|
593
597
|
context 'case insensitive' do
|
@@ -595,39 +599,39 @@ describe 'Taggable' do
|
|
595
599
|
after(:all) { ActsAsTaggableOn.force_lowercase = false }
|
596
600
|
|
597
601
|
it '#<<' do
|
598
|
-
expect
|
602
|
+
expect {
|
599
603
|
bob.tag_list << 'Alone'
|
600
604
|
bob.tag_list << 'AloNe'
|
601
605
|
bob.tag_list << 'ALONE' << 'In The dark'
|
602
606
|
bob.save
|
603
|
-
}
|
607
|
+
}.to change(ActsAsTaggableOn::Tagging, :count).by(2)
|
604
608
|
|
605
609
|
end
|
606
610
|
|
607
611
|
it '#add' do
|
608
|
-
expect
|
612
|
+
expect {
|
609
613
|
bob.tag_list.add 'forever'
|
610
614
|
bob.tag_list.add 'ForEver'
|
611
615
|
bob.tag_list.add 'FOREVER', 'ALONE'
|
612
616
|
bob.save
|
613
|
-
}
|
617
|
+
}.to change(ActsAsTaggableOn::Tagging, :count).by(2)
|
614
618
|
end
|
615
619
|
|
616
620
|
it 'unicode' do
|
617
621
|
|
618
|
-
expect
|
622
|
+
expect {
|
619
623
|
bob.tag_list.add 'ПРИВЕТ'
|
620
624
|
bob.tag_list.add 'привет', 'Привет'
|
621
625
|
bob.save
|
622
|
-
}
|
626
|
+
}.to change(ActsAsTaggableOn::Tagging, :count).by(1)
|
623
627
|
|
624
628
|
end
|
625
629
|
|
626
630
|
it '#=' do
|
627
|
-
expect
|
631
|
+
expect {
|
628
632
|
bob.tag_list = ['Happy', 'HAPPY']
|
629
633
|
bob.save
|
630
|
-
}
|
634
|
+
}.to change(ActsAsTaggableOn::Tagging, :count).by(1)
|
631
635
|
end
|
632
636
|
|
633
637
|
|
@@ -636,26 +640,29 @@ describe 'Taggable' do
|
|
636
640
|
|
637
641
|
end
|
638
642
|
|
639
|
-
|
640
|
-
|
641
|
-
thread_count = 4
|
642
|
-
barrier = Barrier.new thread_count
|
643
|
+
it 'should not duplicate tags' do
|
644
|
+
connor = TaggableModel.new(name: 'Connor', tag_list: 'There, can, be, only, one')
|
643
645
|
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
646
|
+
allow(ActsAsTaggableOn::Tag).to receive(:create).and_call_original
|
647
|
+
expect(ActsAsTaggableOn::Tag).to receive(:create).with(name: 'can') do
|
648
|
+
# Simulate concurrent tag creation
|
649
|
+
ActsAsTaggableOn::Tag.new(name: 'can').save!
|
650
|
+
|
651
|
+
raise ActiveRecord::RecordNotUnique
|
652
|
+
end
|
653
|
+
|
654
|
+
expect(ActsAsTaggableOn::Tag).to receive(:create).with(name: 'be') do
|
655
|
+
# Simulate concurrent tag creation
|
656
|
+
ActsAsTaggableOn::Tag.new(name: 'be').save!
|
657
|
+
|
658
|
+
raise ActiveRecord::RecordNotUnique
|
659
|
+
end
|
660
|
+
|
661
|
+
expect { connor.save! }.to change(ActsAsTaggableOn::Tag, :count).by(5)
|
662
|
+
|
663
|
+
%w[There can only be one].each do |tag|
|
664
|
+
expect(TaggableModel.tagged_with(tag).count).to eq(1)
|
665
|
+
end
|
659
666
|
end
|
660
667
|
end
|
661
668
|
|
@@ -726,9 +733,9 @@ describe 'Taggable' do
|
|
726
733
|
@taggable.skill_list = 'ruby, rails, css'
|
727
734
|
expect(@taggable.instance_variable_get('@skill_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy
|
728
735
|
|
729
|
-
expect
|
736
|
+
expect {
|
730
737
|
@taggable.save
|
731
|
-
}
|
738
|
+
}.to change(ActsAsTaggableOn::Tag, :count).by(3)
|
732
739
|
|
733
740
|
@taggable.reload
|
734
741
|
expect(@taggable.skill_list.sort).to eq(%w(ruby rails css).sort)
|
@@ -754,7 +761,7 @@ describe 'Taggable' do
|
|
754
761
|
end
|
755
762
|
|
756
763
|
# See https://github.com/mbleigh/acts-as-taggable-on/pull/457 for details
|
757
|
-
context 'tag_counts and aggreating scopes,
|
764
|
+
context 'tag_counts and aggreating scopes, compatibility with MySQL ' do
|
758
765
|
before(:each) do
|
759
766
|
TaggableModel.new(:name => 'Barb Jones').tap { |t| t.tag_list = %w(awesome fun) }.save
|
760
767
|
TaggableModel.new(:name => 'John Doe').tap { |t| t.tag_list = %w(cool fun hella) }.save
|
@@ -61,10 +61,10 @@ describe 'Tagger' do
|
|
61
61
|
|
62
62
|
it 'should not overlap tags from different taggers' do
|
63
63
|
@user2 = User.new
|
64
|
-
expect
|
64
|
+
expect {
|
65
65
|
@user.tag(@taggable, with: 'ruby, scheme', on: :tags)
|
66
66
|
@user2.tag(@taggable, with: 'java, python, lisp, ruby', on: :tags)
|
67
|
-
}
|
67
|
+
}.to change(ActsAsTaggableOn::Tagging, :count).by(6)
|
68
68
|
|
69
69
|
[@user, @user2, @taggable].each(&:reload)
|
70
70
|
|
@@ -83,9 +83,9 @@ describe 'Tagger' do
|
|
83
83
|
@user2.tag(@taggable, with: 'java, python, lisp, ruby', on: :tags)
|
84
84
|
@user.tag(@taggable, with: 'ruby, scheme', on: :tags)
|
85
85
|
|
86
|
-
expect
|
86
|
+
expect {
|
87
87
|
@user2.tag(@taggable, with: 'java, python, lisp', on: :tags)
|
88
|
-
}
|
88
|
+
}.to change(ActsAsTaggableOn::Tagging, :count).by(-1)
|
89
89
|
|
90
90
|
[@user, @user2, @taggable].each(&:reload)
|
91
91
|
|
@@ -102,9 +102,9 @@ describe 'Tagger' do
|
|
102
102
|
@user.tag(@taggable, with: 'awesome', on: :tags)
|
103
103
|
@user2.tag(@taggable, with: 'awesome, epic', on: :tags)
|
104
104
|
|
105
|
-
expect
|
105
|
+
expect {
|
106
106
|
@user2.tag(@taggable, with: 'epic', on: :tags)
|
107
|
-
}
|
107
|
+
}.to change(ActsAsTaggableOn::Tagging, :count).by(-1)
|
108
108
|
|
109
109
|
@taggable.reload
|
110
110
|
expect(@taggable.all_tags_list).to include('awesome')
|
@@ -119,9 +119,9 @@ describe 'Tagger' do
|
|
119
119
|
expect(@taggable.tag_list).to eq(%w(ruby))
|
120
120
|
expect(@taggable.all_tags_list.sort).to eq(%w(ruby scheme).sort)
|
121
121
|
|
122
|
-
expect
|
122
|
+
expect {
|
123
123
|
@taggable.update(tag_list: '')
|
124
|
-
}
|
124
|
+
}.to change(ActsAsTaggableOn::Tagging, :count).by(-1)
|
125
125
|
|
126
126
|
expect(@taggable.tag_list).to be_empty
|
127
127
|
expect(@taggable.all_tags_list.sort).to eq(%w(ruby scheme).sort)
|
@@ -20,9 +20,9 @@ describe ActsAsTaggableOn::Tagging do
|
|
20
20
|
@taggable = TaggableModel.create(name: 'Bob Jones')
|
21
21
|
@tag = ActsAsTaggableOn::Tag.create(name: 'awesome')
|
22
22
|
|
23
|
-
expect
|
23
|
+
expect {
|
24
24
|
2.times { ActsAsTaggableOn::Tagging.create(taggable: @taggable, tag: @tag, context: 'tags') }
|
25
|
-
}
|
25
|
+
}.to change(ActsAsTaggableOn::Tagging, :count).by(1)
|
26
26
|
end
|
27
27
|
|
28
28
|
it 'should not delete tags of other records' do
|
@@ -140,4 +140,30 @@ describe ActsAsTaggableOn::Tagging do
|
|
140
140
|
end
|
141
141
|
end
|
142
142
|
end
|
143
|
+
|
144
|
+
describe 'base_class' do
|
145
|
+
before do
|
146
|
+
class Foo < ActiveRecord::Base; end
|
147
|
+
end
|
148
|
+
|
149
|
+
context "default" do
|
150
|
+
it "inherits from ActiveRecord::Base" do
|
151
|
+
|
152
|
+
expect(ActsAsTaggableOn::Tagging.ancestors).to include(ActiveRecord::Base)
|
153
|
+
expect(ActsAsTaggableOn::Tagging.ancestors).to_not include(Foo)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context "custom" do
|
158
|
+
it "inherits from custom class" do
|
159
|
+
|
160
|
+
ActsAsTaggableOn.base_class = 'Foo'
|
161
|
+
hide_const("ActsAsTaggableOn::Tagging")
|
162
|
+
load("lib/acts-as-taggable-on/tagging.rb")
|
163
|
+
|
164
|
+
expect(ActsAsTaggableOn::Tagging.ancestors).to include(Foo)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
143
169
|
end
|
data/spec/support/database.rb
CHANGED
@@ -13,23 +13,15 @@ ActiveRecord::Base.configurations = YAML.load_file(database_yml)
|
|
13
13
|
ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), '../debug.log'))
|
14
14
|
ActiveRecord::Base.logger.level = ENV['CI'] ? ::Logger::ERROR : ::Logger::DEBUG
|
15
15
|
ActiveRecord::Migration.verbose = false
|
16
|
-
|
17
|
-
|
18
|
-
else
|
19
|
-
ActiveRecord::Base.default_timezone = :utc
|
20
|
-
end
|
21
|
-
config = if ActiveRecord.version >= Gem::Version.new('6.1.0')
|
22
|
-
ActiveRecord::Base.configurations.configs_for(env_name: db_name)
|
23
|
-
else
|
24
|
-
ActiveSupport::HashWithIndifferentAccess.new(ActiveRecord::Base.configurations[db_name])
|
25
|
-
end
|
16
|
+
ActiveRecord.default_timezone = :utc
|
17
|
+
config = ActiveRecord::Base.configurations.configs_for(env_name: db_name)
|
26
18
|
|
27
19
|
begin
|
28
20
|
ActiveRecord::Base.establish_connection(db_name.to_sym)
|
29
21
|
ActiveRecord::Base.connection
|
30
22
|
rescue StandardError
|
31
23
|
case db_name
|
32
|
-
when /mysql/
|
24
|
+
when /(mysql)/
|
33
25
|
ActiveRecord::Base.establish_connection(config.merge('database' => nil))
|
34
26
|
ActiveRecord::Base.connection.create_database(config['database'],
|
35
27
|
{ charset: 'utf8', collation: 'utf8_unicode_ci' })
|