acts-as-taggable-on 9.0.1 → 10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8092ec2f51d34ba23b735fbae7cce7a4f68e4ad5900d04903b054e3607667c6b
4
- data.tar.gz: 1a813362d4fd80918b68b5b1ffbf2a3ffcad35a3967d77f3f9ef8f71979b4c29
3
+ metadata.gz: 2a72bfb5bb88aea5ec247738610ca9010077e75742abbc6ae28b0fecb441a01e
4
+ data.tar.gz: 1772385e3921e9b85bbcb0d4407b5dea817f22a62dad50a268375819885541e2
5
5
  SHA512:
6
- metadata.gz: e2ff8897bea6054e879775a8e50cd9f1e63624b86ec4accdfa488fb43fcff35a5e2d90ea4a4cdb7aa7ea400e00826d5e67b1585549c79d4af950662c86aa28a3
7
- data.tar.gz: 05aa619c3f4b174398d20675ebc91fc04ce0ef496d54b402262acaa338a43f5ffc0f62bf69c4d9ccb5f41270b71fdeada04b74c0521a758b013b072a4ac4bafb
6
+ metadata.gz: 72de9a02773b616bc6a55fb4b21879aa104c5100dd9f3b6538334c08bccef904a4e680796c46699e51cafab205301be0288ca831ef26e59c011cd37ab0f9210d
7
+ data.tar.gz: 74a5235a9c37a7c3793bcb8a3b4c5e2a2a6fe2a74a499a5b1fb2ef208e9c8b1f778c2b120f6921019eb7961a9020f0b05bede0aab97234612039b67171a1f7a7
@@ -10,16 +10,17 @@ jobs:
10
10
  DB: ${{ matrix.db }}
11
11
  BUNDLE_GEMFILE: ${{ matrix.gemfile }}
12
12
  strategy:
13
+ fail-fast: false
13
14
  matrix:
14
15
  ruby:
16
+ - 3.2
17
+ - 3.1
15
18
  - "3.0"
16
19
  - 2.7
17
- - 2.6
18
- - 2.5
19
20
  gemfile:
20
- - gemfiles/activerecord_6.0.gemfile
21
- - gemfiles/activerecord_6.1.gemfile
21
+ - gemfiles/activerecord_7.1.gemfile
22
22
  - gemfiles/activerecord_7.0.gemfile
23
+ - gemfiles/activerecord_6.1.gemfile
23
24
  db:
24
25
  - mysql
25
26
  - postgresql
@@ -32,10 +33,8 @@ jobs:
32
33
  db: postgresql
33
34
  gemfile: gemfiles/activerecord_7.0.gemfile
34
35
  exclude:
35
- - ruby: 2.5
36
- gemfile: gemfiles/activerecord_7.0.gemfile
37
- - ruby: 2.6
38
- gemfile: gemfiles/activerecord_7.0.gemfile
36
+ - ruby: 3.2
37
+ gemfile: gemfiles/activerecord_6.0.gemfile
39
38
 
40
39
  services:
41
40
  postgres:
@@ -61,7 +60,7 @@ jobs:
61
60
  --health-timeout 5s
62
61
  --health-retries 5
63
62
  steps:
64
- - uses: actions/checkout@v2
63
+ - uses: actions/checkout@v4
65
64
  - name: Set up Ruby
66
65
  uses: ruby/setup-ruby@v1
67
66
  with:
data/Appraisals CHANGED
@@ -1,11 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- appraise 'activerecord-6.0' do
4
- gem 'activerecord', '~> 6.0.0'
5
- gem 'pg'
6
- gem 'mysql2', '~> 0.5'
7
- end
8
-
9
3
  appraise 'activerecord-6.1' do
10
4
  gem 'activerecord', '~> 6.1.0'
11
5
  gem 'pg'
@@ -17,3 +11,9 @@ appraise 'activerecord-7.0' do
17
11
  gem 'pg'
18
12
  gem 'mysql2', '~> 0.5'
19
13
  end
14
+
15
+ appraise 'activerecord-7.1' do
16
+ gem 'activerecord', '~> 7.1.0'
17
+ gem 'pg'
18
+ gem 'mysql2', '~> 0.5'
19
+ end
data/CHANGELOG.md CHANGED
@@ -10,7 +10,13 @@ Each change should fall into categories that would affect whether the release is
10
10
 
11
11
  As such, _Breaking Changes_ are major. _Features_ would map to either major or minor. _Fixes_, _Performance_, and _Misc_ are either minor or patch, the difference being kind of fuzzy for the purposes of history. Adding _Documentation_ (including tests) would be patch level.
12
12
 
13
- ### [v9.0.1) / 2022-01-07](https://github.com/mbleigh/acts-as-taggable-on/compare/v8.1.0...v9.0.0)
13
+ ### [v10.0.0) / unreleased](https://github.com/mbleigh/acts-as-taggable-on/compare/v9.0.1...master)
14
+ * Features
15
+ * [@glampr Add support for prefix and suffix searches alongside previously supported containment (wildcard) searches](https://github.com/mbleigh/acts-as-taggable-on/pull/1082)
16
+ * [@donquxiote Add support for horizontally sharded databases](https://github.com/mbleigh/acts-as-taggable-on/pull/1079)
17
+ * [aovertus Remove restriction around ActiveRecord 7.x versions allowing support until next major is released](https://github.com/mbleigh/acts-as-taggable-on/pull/1110)
18
+
19
+ ### [v9.0.1) / 2022-01-07](https://github.com/mbleigh/acts-as-taggable-on/compare/v9.0.0..v9.0.1)
14
20
  * Fixes
15
21
  * Fix migration that generate default index
16
22
 
@@ -39,7 +45,7 @@ As such, _Breaking Changes_ are major. _Features_ would map to either major or m
39
45
  * Features
40
46
  * [@kvokka Rails 6.1 support](https://github.com/mbleigh/acts-as-taggable-on/pull/1013)
41
47
  * Fixes
42
- * [@nbulaj Add support for Ruby 2.7 and it's kwargs](https://github.com/mbleigh/acts-as-taggable-on/pull/910)
48
+ * [@nbulaj Add support for Ruby 2.7 and it's kwargs](https://github.com/mbleigh/acts-as-taggable-on/pull/999)
43
49
  * [@Andythurlow @endorfin case sensitivity fix for tagged_with](https://github.com/mbleigh/acts-as-taggable-on/pull/965)
44
50
 
45
51
  ### [6.5.0 / 2019-11-07](https://github.com/mbleigh/acts-as-taggable-on/compare/v6.0.0...v6.5.0)
data/CONTRIBUTING.md CHANGED
@@ -29,7 +29,7 @@
29
29
  * Use well-described, small (atomic) commits.
30
30
  * Include links to any relevant github issues.
31
31
  * *Don't* change the VERSION file.
32
- 6. Extra Credit: [Confirm it runs and tests pass on the rubies specified in the travis config](.travis.yml). I will otherwise confirm it runs on these.
32
+ 6. Extra Credit: [Confirm it runs and tests pass on the rubies specified in the Github Actions config](.github/workflows/spec.yml). I will otherwise confirm it runs on these.
33
33
 
34
34
  How I handle pull requests:
35
35
 
@@ -44,13 +44,13 @@ How I handle pull requests:
44
44
 
45
45
  * [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
46
46
  * [http://stopwritingramblingcommitmessages.com/](http://stopwritingramblingcommitmessages.com/)
47
- * [ThoughtBot style guide](https://github.com/thoughtbot/guides/tree/master/style#git)
47
+ * [ThoughtBot style guide](https://github.com/thoughtbot/guides/tree/main/git)
48
48
 
49
49
  ### About Pull Requests (PR's)
50
50
 
51
51
  * [All Your Open Source Code Are Belong To Us](http://www.benjaminfleischer.com/2013/07/30/all-your-open-source-code-are-belong-to-us/)
52
52
  * [Using Pull Requests](https://help.github.com/articles/using-pull-requests)
53
- * [Github pull requests made easy](http://www.element84.com/github-pull-requests-made-easy.html)
53
+ * [Github pull requests made easy](https://www.element84.com/blog/github-pull-requests-made-easy)
54
54
 
55
55
  ## Documentation
56
56
 
data/README.md CHANGED
@@ -255,7 +255,11 @@ User.tagged_with(["awesome", "cool"], :exclude => true)
255
255
  User.tagged_with(['awesome', 'cool'], :on => :tags, :any => true).tagged_with(['smart', 'shy'], :on => :skills, :any => true)
256
256
  ```
257
257
 
258
- You can also use `:wild => true` option along with `:any` or `:exclude` option. It will be looking for `%awesome%` and `%cool%` in SQL.
258
+ #### Wildcard tag search
259
+ You now have the following options for prefix, suffix and containment search, along with `:any` or `:exclude` option.
260
+ Use `wild: :suffix` to place a wildcard at the end of the tag. It will be looking for `awesome%` and `cool%` in SQL.
261
+ Use `wild: :prefix` to place a wildcard at the beginning of the tag. It will be looking for `%awesome` and `%cool` in SQL.
262
+ Use `wild: true` to place a wildcard both at the beginning and the end of the tag. It will be looking for `%awesome%` and `%cool%` in SQL.
259
263
 
260
264
  __Tip:__ `User.tagged_with([])` or `User.tagged_with('')` will return `[]`, an empty set of records.
261
265
 
@@ -296,6 +300,15 @@ to allow for dynamic tag contexts (this could be user generated tag contexts!)
296
300
  User.tagged_with("same", :on => :customs) # => [@user]
297
301
  ```
298
302
 
303
+ ### Finding tags based on context
304
+
305
+ You can find tags for a specific context by using the ```for_context``` scope:
306
+
307
+ ```ruby
308
+ ActsAsTaggableOn::Tag.for_context(:tags)
309
+ ActsAsTaggableOn::Tag.for_context(:skills)
310
+ ```
311
+
299
312
  ### Tag Parsers
300
313
 
301
314
  If you want to change how tags are parsed, you can define your own implementation:
@@ -1,7 +1,6 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'acts_as_taggable_on/version'
2
+
3
+ require_relative 'lib/acts_as_taggable_on/version'
5
4
 
6
5
  Gem::Specification.new do |gem|
7
6
  gem.name = 'acts-as-taggable-on'
@@ -16,13 +15,13 @@ Gem::Specification.new do |gem|
16
15
  gem.files = `git ls-files`.split($/)
17
16
  gem.test_files = gem.files.grep(%r{^spec/})
18
17
  gem.require_paths = ['lib']
19
- gem.required_ruby_version = '>= 2.5.0'
18
+ gem.required_ruby_version = '>= 2.7.0'
20
19
 
21
20
  if File.exist?('UPGRADING.md')
22
21
  gem.post_install_message = File.read('UPGRADING.md')
23
22
  end
24
23
 
25
- gem.add_runtime_dependency 'activerecord', '>= 6.0', '< 7.1'
24
+ gem.add_runtime_dependency 'activerecord', '>= 6.1', '< 7.2'
26
25
 
27
26
  gem.add_development_dependency 'rspec-rails'
28
27
  gem.add_development_dependency 'rspec-its'
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activerecord", "~> 7.0.0"
5
+ gem "activerecord", "~> 7.0.1"
6
6
  gem "pg"
7
7
  gem "mysql2", "~> 0.5"
8
8
 
@@ -0,0 +1,18 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 7.1.0"
6
+ gem "pg"
7
+ gem "mysql2", "~> 0.5"
8
+
9
+ group :local_development do
10
+ gem "guard"
11
+ gem "guard-rspec"
12
+ gem "appraisal"
13
+ gem "rake"
14
+ gem "sqlite3"
15
+ gem "byebug", platforms: [:mri]
16
+ end
17
+
18
+ gemspec path: "../"
@@ -66,7 +66,7 @@ module ActsAsTaggableOn
66
66
  :remove_unused_tags, :default_parser,
67
67
  :tags_counter, :tags_table,
68
68
  :taggings_table
69
- attr_reader :delimiter, :strict_case_match
69
+ attr_reader :delimiter, :strict_case_match, :base_class
70
70
 
71
71
  def initialize
72
72
  @delimiter = ','
@@ -79,6 +79,7 @@ module ActsAsTaggableOn
79
79
  @force_binary_collation = false
80
80
  @tags_table = :tags
81
81
  @taggings_table = :taggings
82
+ @base_class = '::ActiveRecord::Base'
82
83
  end
83
84
 
84
85
  def strict_case_match=(force_cs)
@@ -119,6 +120,11 @@ WARNING
119
120
  end
120
121
  end
121
122
 
123
+ def base_class=(base_class)
124
+ raise "base_class must be a String" unless base_class.is_a?(String)
125
+ @base_class = base_class
126
+ end
127
+
122
128
  end
123
129
 
124
130
  setup
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActsAsTaggableOn
4
- class Tag < ::ActiveRecord::Base
4
+ class Tag < ActsAsTaggableOn.base_class.constantize
5
5
  self.table_name = ActsAsTaggableOn.tags_table
6
6
 
7
7
  ### ASSOCIATIONS:
@@ -84,10 +84,11 @@ module ActsAsTaggableOn
84
84
  tries ||= 3
85
85
  comparable_tag_name = comparable_name(tag_name)
86
86
  existing_tag = existing_tags.find { |tag| comparable_name(tag.name) == comparable_tag_name }
87
- existing_tag || create(name: tag_name)
87
+ next existing_tag if existing_tag
88
+
89
+ transaction(requires_new: true) { create(name: tag_name) }
88
90
  rescue ActiveRecord::RecordNotUnique
89
91
  if (tries -= 1).positive?
90
- ActiveRecord::Base.connection.execute 'ROLLBACK'
91
92
  existing_tags = named_any(list)
92
93
  retry
93
94
  end
@@ -33,7 +33,7 @@ module ActsAsTaggableOn
33
33
  matches_attribute = matches_attribute.lower unless ActsAsTaggableOn.strict_case_match
34
34
 
35
35
  if options[:wild].present?
36
- matches_attribute.matches("%#{escaped_tag(tag)}%", '!', ActsAsTaggableOn.strict_case_match)
36
+ matches_attribute.matches(wildcard_escaped_tag(tag), '!', ActsAsTaggableOn.strict_case_match)
37
37
  else
38
38
  matches_attribute.matches(escaped_tag(tag), '!', ActsAsTaggableOn.strict_case_match)
39
39
  end
@@ -45,7 +45,7 @@ module ActsAsTaggableOn
45
45
 
46
46
  if options[:wild].present?
47
47
  matches_attribute.matches_any(tag_list.map do |tag|
48
- "%#{escaped_tag(tag)}%"
48
+ wildcard_escaped_tag(tag)
49
49
  end, '!', ActsAsTaggableOn.strict_case_match)
50
50
  else
51
51
  matches_attribute.matches_any(tag_list.map do |tag|
@@ -59,6 +59,15 @@ module ActsAsTaggableOn
59
59
  ActsAsTaggableOn::Utils.escape_like(tag)
60
60
  end
61
61
 
62
+ def wildcard_escaped_tag(tag)
63
+ case options[:wild]
64
+ when :suffix then "#{escaped_tag(tag)}%"
65
+ when :prefix then "%#{escaped_tag(tag)}"
66
+ when true then "%#{escaped_tag(tag)}%"
67
+ else escaped_tag(tag)
68
+ end
69
+ end
70
+
62
71
  def adjust_taggings_alias(taggings_alias)
63
72
  taggings_alias = "taggings_alias_#{Digest::SHA1.hexdigest(taggings_alias)}" if taggings_alias.size > 75
64
73
  taggings_alias
@@ -40,9 +40,7 @@ module ActsAsTaggableOn
40
40
  false
41
41
  end
42
42
 
43
- def is_tagger?
44
- tagger?
45
- end
43
+ alias is_tagger? tagger?
46
44
  end
47
45
 
48
46
  module InstanceMethods
@@ -75,9 +73,7 @@ module ActsAsTaggableOn
75
73
  self.class.is_tagger?
76
74
  end
77
75
 
78
- def is_tagger?
79
- tagger?
80
- end
76
+ alias is_tagger? tagger?
81
77
  end
82
78
 
83
79
  module SingletonMethods
@@ -85,9 +81,7 @@ module ActsAsTaggableOn
85
81
  true
86
82
  end
87
83
 
88
- def is_tagger?
89
- tagger?
90
- end
84
+ alias is_tagger? tagger?
91
85
  end
92
86
  end
93
87
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActsAsTaggableOn
4
- class Tagging < ::ActiveRecord::Base # :nodoc:
4
+ class Tagging < ActsAsTaggableOn.base_class.constantize # :nodoc:
5
5
  self.table_name = ActsAsTaggableOn.taggings_table
6
6
 
7
7
  DEFAULT_CONTEXT = 'tags'
@@ -26,10 +26,6 @@ module ActsAsTaggableOn
26
26
  using_postgresql? ? 'ILIKE' : 'LIKE'
27
27
  end
28
28
 
29
- def legacy_activerecord?
30
- ActiveRecord.version <= Gem::Version.new('5.3.0')
31
- end
32
-
33
29
  # escape _ and % characters in strings, since these are wildcards in SQL.
34
30
  def escape_like(str)
35
31
  str.gsub(/[!%_]/) { |x| "!#{x}" }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActsAsTaggableOn
4
- VERSION = '9.0.1'
4
+ VERSION = '10.0.0'
5
5
  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
@@ -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
- }).to raise_error(RuntimeError)
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
@@ -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(-> { tag_list.add('cool', 'rad,bodacious') }).to raise_error(RuntimeError)
108
- expect(-> { tag_list.to_s }).to_not raise_error
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
 
@@ -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
- }).to change(ActsAsTaggableOn::Tag, :count).by(1)
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
- }).to change(ActsAsTaggableOn::Tag, :count).by(3)
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
- }).to change(ActsAsTaggableOn::Tag, :count).by(3)
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
- }).to change(ActsAsTaggableOn::Tag, :count).by(3)
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(lambda {
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
- }).to change(ActsAsTaggableOn::Tagging, :count).by(3)
567
+ }.to change(ActsAsTaggableOn::Tagging, :count).by(3)
564
568
  end
565
569
  it '#<<' do
566
- expect(lambda {
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
- }).to change(ActsAsTaggableOn::Tagging, :count).by(2)
575
+ }.to change(ActsAsTaggableOn::Tagging, :count).by(2)
572
576
 
573
577
  end
574
578
 
575
579
  it 'unicode' do
576
580
 
577
- expect(lambda {
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
- }).to change(ActsAsTaggableOn::Tagging, :count).by(1)
586
+ }.to change(ActsAsTaggableOn::Tagging, :count).by(1)
583
587
 
584
588
  end
585
589
 
586
590
  it '#=' do
587
- expect(lambda {
591
+ expect {
588
592
  bob.tag_list = ['Happy', 'Happy']
589
593
  bob.save
590
- }).to change(ActsAsTaggableOn::Tagging, :count).by(1)
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(lambda {
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
- }).to change(ActsAsTaggableOn::Tagging, :count).by(2)
607
+ }.to change(ActsAsTaggableOn::Tagging, :count).by(2)
604
608
 
605
609
  end
606
610
 
607
611
  it '#add' do
608
- expect(lambda {
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
- }).to change(ActsAsTaggableOn::Tagging, :count).by(2)
617
+ }.to change(ActsAsTaggableOn::Tagging, :count).by(2)
614
618
  end
615
619
 
616
620
  it 'unicode' do
617
621
 
618
- expect(lambda {
622
+ expect {
619
623
  bob.tag_list.add 'ПРИВЕТ'
620
624
  bob.tag_list.add 'привет', 'Привет'
621
625
  bob.save
622
- }).to change(ActsAsTaggableOn::Tagging, :count).by(1)
626
+ }.to change(ActsAsTaggableOn::Tagging, :count).by(1)
623
627
 
624
628
  end
625
629
 
626
630
  it '#=' do
627
- expect(lambda {
631
+ expect {
628
632
  bob.tag_list = ['Happy', 'HAPPY']
629
633
  bob.save
630
- }).to change(ActsAsTaggableOn::Tagging, :count).by(1)
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
- xit 'should not duplicate tags added on different threads', if: supports_concurrency?, skip: 'FIXME, Deadlocks in travis' do
640
- #TODO, try with more threads and fix deadlock
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
- expect {
645
- thread_count.times.map do |idx|
646
- Thread.start do
647
- connor = TaggableModel.first_or_create(name: 'Connor')
648
- connor.tag_list = 'There, can, be, only, one'
649
- barrier.wait
650
- begin
651
- connor.save
652
- rescue ActsAsTaggableOn::DuplicateTagError
653
- # second save should succeed
654
- connor.save
655
- end
656
- end
657
- end.map(&:join)
658
- }.to change(ActsAsTaggableOn::Tag, :count).by(5)
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
- }).to change(ActsAsTaggableOn::Tag, :count).by(3)
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)
@@ -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
- }).to change(ActsAsTaggableOn::Tagging, :count).by(6)
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
- }).to change(ActsAsTaggableOn::Tagging, :count).by(-1)
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
- }).to change(ActsAsTaggableOn::Tagging, :count).by(-1)
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
- }).to change(ActsAsTaggableOn::Tagging, :count).by(-1)
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
- }).to change(ActsAsTaggableOn::Tagging, :count).by(1)
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
@@ -18,18 +18,14 @@ if ActiveRecord.version >= Gem::Version.new('7.0.0.alpha2')
18
18
  else
19
19
  ActiveRecord::Base.default_timezone = :utc
20
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
21
+ config = ActiveRecord::Base.configurations.configs_for(env_name: db_name)
26
22
 
27
23
  begin
28
24
  ActiveRecord::Base.establish_connection(db_name.to_sym)
29
25
  ActiveRecord::Base.connection
30
26
  rescue StandardError
31
27
  case db_name
32
- when /mysql/
28
+ when /(mysql)/
33
29
  ActiveRecord::Base.establish_connection(config.merge('database' => nil))
34
30
  ActiveRecord::Base.connection.create_database(config['database'],
35
31
  { charset: 'utf8', collation: 'utf8_unicode_ci' })
@@ -6,6 +6,10 @@ RSpec.configure do |config|
6
6
  DatabaseCleaner.clean
7
7
  end
8
8
 
9
+ config.before(:each, :database_cleaner_delete) do
10
+ DatabaseCleaner.strategy = :truncation
11
+ end
12
+
9
13
  config.after(:suite) do
10
14
  DatabaseCleaner.clean
11
15
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts-as-taggable-on
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.0.1
4
+ version: 10.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-01-07 00:00:00.000000000 Z
12
+ date: 2023-10-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -17,20 +17,20 @@ dependencies:
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '6.0'
20
+ version: '6.1'
21
21
  - - "<"
22
22
  - !ruby/object:Gem::Version
23
- version: '7.1'
23
+ version: '7.2'
24
24
  type: :runtime
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
27
27
  requirements:
28
28
  - - ">="
29
29
  - !ruby/object:Gem::Version
30
- version: '6.0'
30
+ version: '6.1'
31
31
  - - "<"
32
32
  - !ruby/object:Gem::Version
33
- version: '7.1'
33
+ version: '7.2'
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: rspec-rails
36
36
  requirement: !ruby/object:Gem::Requirement
@@ -133,6 +133,7 @@ files:
133
133
  - gemfiles/activerecord_6.0.gemfile
134
134
  - gemfiles/activerecord_6.1.gemfile
135
135
  - gemfiles/activerecord_7.0.gemfile
136
+ - gemfiles/activerecord_7.1.gemfile
136
137
  - lib/acts-as-taggable-on.rb
137
138
  - lib/acts_as_taggable_on.rb
138
139
  - lib/acts_as_taggable_on/default_parser.rb
@@ -157,6 +158,8 @@ files:
157
158
  - lib/acts_as_taggable_on/tags_helper.rb
158
159
  - lib/acts_as_taggable_on/utils.rb
159
160
  - lib/acts_as_taggable_on/version.rb
161
+ - lib/tasks/example/acts_as_taggable_on.rb.example
162
+ - lib/tasks/install_initializer.rake
160
163
  - lib/tasks/tags_collate_utf8.rake
161
164
  - spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb
162
165
  - spec/acts_as_taggable_on/acts_as_tagger_spec.rb
@@ -207,14 +210,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
207
210
  requirements:
208
211
  - - ">="
209
212
  - !ruby/object:Gem::Version
210
- version: 2.5.0
213
+ version: 2.7.0
211
214
  required_rubygems_version: !ruby/object:Gem::Requirement
212
215
  requirements:
213
216
  - - ">="
214
217
  - !ruby/object:Gem::Version
215
218
  version: '0'
216
219
  requirements: []
217
- rubygems_version: 3.2.22
220
+ rubygems_version: 3.4.12
218
221
  signing_key:
219
222
  specification_version: 4
220
223
  summary: Advanced tagging for Rails.