acts-as-taggable-on 9.0.1 → 10.0.0

Sign up to get free protection for your applications and to get access to all the features.
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.