acts-as-taggable-on 9.0.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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' })
|