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 +4 -4
- data/.github/workflows/spec.yml +8 -9
- data/Appraisals +6 -6
- data/CHANGELOG.md +8 -2
- data/CONTRIBUTING.md +3 -3
- data/README.md +14 -1
- data/acts-as-taggable-on.gemspec +4 -5
- data/gemfiles/activerecord_7.0.gemfile +1 -1
- data/gemfiles/activerecord_7.1.gemfile +18 -0
- data/lib/acts-as-taggable-on.rb +7 -1
- data/lib/acts_as_taggable_on/tag.rb +4 -3
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +11 -2
- data/lib/acts_as_taggable_on/tagger.rb +3 -9
- data/lib/acts_as_taggable_on/tagging.rb +1 -1
- data/lib/acts_as_taggable_on/utils.rb +0 -4
- data/lib/acts_as_taggable_on/version.rb +1 -1
- 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_tagger_spec.rb +2 -2
- data/spec/acts_as_taggable_on/tag_list_spec.rb +2 -2
- data/spec/acts_as_taggable_on/tag_spec.rb +45 -2
- data/spec/acts_as_taggable_on/taggable_spec.rb +50 -43
- 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 +2 -6
- data/spec/support/database_cleaner.rb +4 -0
- metadata +11 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a72bfb5bb88aea5ec247738610ca9010077e75742abbc6ae28b0fecb441a01e
|
4
|
+
data.tar.gz: 1772385e3921e9b85bbcb0d4407b5dea817f22a62dad50a268375819885541e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 72de9a02773b616bc6a55fb4b21879aa104c5100dd9f3b6538334c08bccef904a4e680796c46699e51cafab205301be0288ca831ef26e59c011cd37ab0f9210d
|
7
|
+
data.tar.gz: 74a5235a9c37a7c3793bcb8a3b4c5e2a2a6fe2a74a499a5b1fb2ef208e9c8b1f778c2b120f6921019eb7961a9020f0b05bede0aab97234612039b67171a1f7a7
|
data/.github/workflows/spec.yml
CHANGED
@@ -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/
|
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
|
36
|
-
gemfile: gemfiles/
|
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@
|
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
|
-
### [
|
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/
|
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
|
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/
|
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](
|
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
|
-
|
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:
|
data/acts-as-taggable-on.gemspec
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
|
3
|
-
|
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.
|
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.
|
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'
|
@@ -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: "../"
|
data/lib/acts-as-taggable-on.rb
CHANGED
@@ -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 <
|
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
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
89
|
-
tagger?
|
90
|
-
end
|
84
|
+
alias is_tagger? tagger?
|
91
85
|
end
|
92
86
|
end
|
93
87
|
end
|
@@ -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}" }
|
@@ -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
|
-
}
|
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
|
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
|
|
@@ -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)
|
@@ -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
@@ -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 =
|
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' })
|
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:
|
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:
|
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.
|
20
|
+
version: '6.1'
|
21
21
|
- - "<"
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: '7.
|
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.
|
30
|
+
version: '6.1'
|
31
31
|
- - "<"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '7.
|
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.
|
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.
|
220
|
+
rubygems_version: 3.4.12
|
218
221
|
signing_key:
|
219
222
|
specification_version: 4
|
220
223
|
summary: Advanced tagging for Rails.
|