acts-as-taggable-on 6.0.0 → 8.1.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 +95 -0
- data/Appraisals +8 -0
- data/CHANGELOG.md +199 -140
- data/Gemfile +1 -0
- data/README.md +38 -4
- data/acts-as-taggable-on.gemspec +2 -2
- data/db/migrate/1_acts_as_taggable_on_migration.rb +8 -7
- data/db/migrate/2_add_missing_unique_indices.rb +8 -8
- data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +3 -3
- data/db/migrate/4_add_missing_taggable_index.rb +2 -2
- data/db/migrate/5_change_collation_for_tag_names.rb +1 -1
- data/db/migrate/6_add_missing_indexes_on_taggings.rb +9 -9
- data/db/migrate/7_add_tenant_to_taggings.rb +16 -0
- data/gemfiles/activerecord_5.0.gemfile +2 -4
- data/gemfiles/activerecord_5.1.gemfile +2 -4
- data/gemfiles/activerecord_5.2.gemfile +2 -4
- data/gemfiles/activerecord_6.0.gemfile +21 -0
- data/gemfiles/activerecord_6.1.gemfile +23 -0
- data/lib/acts-as-taggable-on.rb +6 -2
- data/lib/acts_as_taggable_on/tag.rb +16 -19
- data/lib/acts_as_taggable_on/taggable/cache.rb +38 -34
- data/lib/acts_as_taggable_on/taggable/collection.rb +8 -6
- data/lib/acts_as_taggable_on/taggable/core.rb +22 -6
- data/lib/acts_as_taggable_on/taggable/tag_list_type.rb +4 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +4 -4
- data/lib/acts_as_taggable_on/taggable.rb +18 -0
- data/lib/acts_as_taggable_on/tagger.rb +1 -1
- data/lib/acts_as_taggable_on/tagging.rb +6 -2
- data/lib/acts_as_taggable_on/utils.rb +4 -0
- data/lib/acts_as_taggable_on/version.rb +1 -2
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +4 -12
- data/spec/acts_as_taggable_on/caching_spec.rb +16 -10
- data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +12 -7
- data/spec/acts_as_taggable_on/tag_spec.rb +16 -1
- data/spec/acts_as_taggable_on/taggable_spec.rb +16 -12
- data/spec/acts_as_taggable_on/tagger_spec.rb +2 -2
- data/spec/acts_as_taggable_on/tagging_spec.rb +26 -0
- data/spec/internal/app/models/altered_inheriting_taggable_model.rb +2 -0
- data/spec/internal/app/models/cached_model_with_array.rb +6 -0
- data/spec/internal/app/models/columns_override_model.rb +5 -0
- data/spec/internal/app/models/company.rb +1 -1
- data/spec/internal/app/models/inheriting_taggable_model.rb +2 -0
- data/spec/internal/app/models/market.rb +1 -1
- data/spec/internal/app/models/non_standard_id_taggable_model.rb +1 -1
- data/spec/internal/app/models/student.rb +2 -0
- data/spec/internal/app/models/taggable_model.rb +3 -0
- data/spec/internal/app/models/user.rb +1 -1
- data/spec/internal/config/database.yml.sample +4 -8
- data/spec/internal/db/schema.rb +14 -5
- data/spec/spec_helper.rb +0 -1
- data/spec/support/database.rb +4 -4
- metadata +20 -20
- data/.travis.yml +0 -33
- data/UPGRADING.md +0 -8
- data/spec/internal/app/models/models.rb +0 -90
data/README.md
CHANGED
@@ -32,7 +32,7 @@
|
|
32
32
|
|
33
33
|
[![Join the chat at https://gitter.im/mbleigh/acts-as-taggable-on](https://badges.gitter.im/mbleigh/acts-as-taggable-on.svg)](https://gitter.im/mbleigh/acts-as-taggable-on?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
34
34
|
[![Gem Version](https://badge.fury.io/rb/acts-as-taggable-on.svg)](http://badge.fury.io/rb/acts-as-taggable-on)
|
35
|
-
[![Build Status](https://
|
35
|
+
[![Build Status](https://github.com/mbleigh/acts-as-taggable-on/workflows/spec/badge.svg)](https://github.com/mbleigh/acts-as-taggable-on/actions)
|
36
36
|
[![Code Climate](https://codeclimate.com/github/mbleigh/acts-as-taggable-on.svg)](https://codeclimate.com/github/mbleigh/acts-as-taggable-on)
|
37
37
|
[![Inline docs](http://inch-ci.org/github/mbleigh/acts-as-taggable-on.svg)](http://inch-ci.org/github/mbleigh/acts-as-taggable-on)
|
38
38
|
[![Security](https://hakiri.io/github/mbleigh/acts-as-taggable-on/master.svg)](https://hakiri.io/github/mbleigh/acts-as-taggable-on/master)
|
@@ -57,7 +57,7 @@ was used.
|
|
57
57
|
To use it, add it to your Gemfile:
|
58
58
|
|
59
59
|
```ruby
|
60
|
-
gem 'acts-as-taggable-on', '~>
|
60
|
+
gem 'acts-as-taggable-on', '~> 7.0'
|
61
61
|
```
|
62
62
|
|
63
63
|
and bundle:
|
@@ -80,6 +80,8 @@ Review the generated migrations then migrate :
|
|
80
80
|
rake db:migrate
|
81
81
|
```
|
82
82
|
|
83
|
+
If you do not wish or need to support multi-tenancy, the migration for `add_tenant_to_taggings` is optional and can be discarded safely.
|
84
|
+
|
83
85
|
#### For MySql users
|
84
86
|
You can circumvent at any time the problem of special characters [issue 623](https://github.com/mbleigh/acts-as-taggable-on/issues/623) by setting in an initializer file:
|
85
87
|
|
@@ -103,8 +105,8 @@ Setup
|
|
103
105
|
|
104
106
|
```ruby
|
105
107
|
class User < ActiveRecord::Base
|
106
|
-
|
107
|
-
acts_as_taggable_on :skills, :interests
|
108
|
+
acts_as_taggable_on :tags
|
109
|
+
acts_as_taggable_on :skills, :interests #You can also configure multiple tag types per model
|
108
110
|
end
|
109
111
|
|
110
112
|
class UsersController < ApplicationController
|
@@ -121,6 +123,7 @@ Add and remove a single tag
|
|
121
123
|
```ruby
|
122
124
|
@user.tag_list.add("awesome") # add a single tag. alias for <<
|
123
125
|
@user.tag_list.remove("awesome") # remove a single tag
|
126
|
+
@user.save # save to persist tag_list
|
124
127
|
```
|
125
128
|
|
126
129
|
Add and remove multiple tags in an array
|
@@ -128,6 +131,7 @@ Add and remove multiple tags in an array
|
|
128
131
|
```ruby
|
129
132
|
@user.tag_list.add("awesome", "slick")
|
130
133
|
@user.tag_list.remove("awesome", "slick")
|
134
|
+
@user.save
|
131
135
|
```
|
132
136
|
|
133
137
|
You can also add and remove tags in format of String. This would
|
@@ -388,6 +392,27 @@ def remove_owned_tag
|
|
388
392
|
end
|
389
393
|
```
|
390
394
|
|
395
|
+
### Tag Tenancy
|
396
|
+
|
397
|
+
Tags support multi-tenancy. This is useful for applications where a Tag belongs to a scoped set of models:
|
398
|
+
|
399
|
+
```ruby
|
400
|
+
class Account < ActiveRecord::Base
|
401
|
+
has_many :photos
|
402
|
+
end
|
403
|
+
|
404
|
+
class User < ActiveRecord::Base
|
405
|
+
belongs_to :account
|
406
|
+
acts_as_taggable_on :tags
|
407
|
+
acts_as_taggable_tenant :account_id
|
408
|
+
end
|
409
|
+
|
410
|
+
@user1.tag_list = ["foo", "bar"] # these taggings will automatically have the tenant saved
|
411
|
+
@user2.tag_list = ["bar", "baz"]
|
412
|
+
|
413
|
+
ActsAsTaggableOn::Tag.for_tenant(@user1.account.id) # returns Tag models for "foo" and "bar", but not "baz"
|
414
|
+
```
|
415
|
+
|
391
416
|
### Dirty objects
|
392
417
|
|
393
418
|
```ruby
|
@@ -486,6 +511,13 @@ If you would like to have an exact match covering special characters with MySql:
|
|
486
511
|
ActsAsTaggableOn.force_binary_collation = true
|
487
512
|
```
|
488
513
|
|
514
|
+
If you would like to specify table names:
|
515
|
+
|
516
|
+
```ruby
|
517
|
+
ActsAsTaggableOn.tags_table = 'aato_tags'
|
518
|
+
ActsAsTaggableOn.taggings_table = 'aato_taggings'
|
519
|
+
```
|
520
|
+
|
489
521
|
If you want to change the default delimiter (it defaults to ','). You can also pass in an array of delimiters such as ([',', '|']):
|
490
522
|
|
491
523
|
```ruby
|
@@ -521,6 +553,8 @@ Versions >= 3.x are compatible with Ruby 1.9.3+ and Rails 3 and 4.
|
|
521
553
|
|
522
554
|
Versions >= 4.x are compatible with Ruby 2.0.0+ and Rails 4 and 5.
|
523
555
|
|
556
|
+
Versions >= 7.x are compatible with Ruby 2.3.7+ and Rails 5 and 6.
|
557
|
+
|
524
558
|
For an up-to-date roadmap, see https://github.com/mbleigh/acts-as-taggable-on/milestones
|
525
559
|
|
526
560
|
## TODO
|
data/acts-as-taggable-on.gemspec
CHANGED
@@ -16,13 +16,13 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.files = `git ls-files`.split($/)
|
17
17
|
gem.test_files = gem.files.grep(%r{^spec/})
|
18
18
|
gem.require_paths = ['lib']
|
19
|
-
gem.required_ruby_version = '>= 2.
|
19
|
+
gem.required_ruby_version = '>= 2.3.7'
|
20
20
|
|
21
21
|
if File.exist?('UPGRADING.md')
|
22
22
|
gem.post_install_message = File.read('UPGRADING.md')
|
23
23
|
end
|
24
24
|
|
25
|
-
gem.add_runtime_dependency 'activerecord',
|
25
|
+
gem.add_runtime_dependency 'activerecord', '>= 5.0', '< 6.2'
|
26
26
|
|
27
27
|
gem.add_development_dependency 'rspec-rails'
|
28
28
|
gem.add_development_dependency 'rspec-its'
|
@@ -5,12 +5,13 @@ else
|
|
5
5
|
end
|
6
6
|
ActsAsTaggableOnMigration.class_eval do
|
7
7
|
def self.up
|
8
|
-
create_table
|
8
|
+
create_table ActsAsTaggableOn.tags_table do |t|
|
9
9
|
t.string :name
|
10
|
+
t.timestamps
|
10
11
|
end
|
11
12
|
|
12
|
-
create_table
|
13
|
-
t.references :tag
|
13
|
+
create_table ActsAsTaggableOn.taggings_table do |t|
|
14
|
+
t.references :tag, foreign_key: { to_table: ActsAsTaggableOn.tags_table }
|
14
15
|
|
15
16
|
# You should make sure that the column created is
|
16
17
|
# long enough to store the required class names.
|
@@ -24,12 +25,12 @@ ActsAsTaggableOnMigration.class_eval do
|
|
24
25
|
t.datetime :created_at
|
25
26
|
end
|
26
27
|
|
27
|
-
add_index
|
28
|
-
add_index
|
28
|
+
add_index ActsAsTaggableOn.taggings_table, :tag_id
|
29
|
+
add_index ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :context], name: 'taggings_taggable_context_idx'
|
29
30
|
end
|
30
31
|
|
31
32
|
def self.down
|
32
|
-
drop_table
|
33
|
-
drop_table
|
33
|
+
drop_table ActsAsTaggableOn.taggings_table
|
34
|
+
drop_table ActsAsTaggableOn.tags_table
|
34
35
|
end
|
35
36
|
end
|
@@ -5,21 +5,21 @@ else
|
|
5
5
|
end
|
6
6
|
AddMissingUniqueIndices.class_eval do
|
7
7
|
def self.up
|
8
|
-
add_index
|
8
|
+
add_index ActsAsTaggableOn.tags_table, :name, unique: true
|
9
9
|
|
10
|
-
remove_index
|
11
|
-
remove_index
|
12
|
-
add_index
|
10
|
+
remove_index ActsAsTaggableOn.taggings_table, :tag_id if index_exists?(ActsAsTaggableOn.taggings_table, :tag_id)
|
11
|
+
remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_taggable_context_idx'
|
12
|
+
add_index ActsAsTaggableOn.taggings_table,
|
13
13
|
[:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type],
|
14
14
|
unique: true, name: 'taggings_idx'
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.down
|
18
|
-
remove_index
|
18
|
+
remove_index ActsAsTaggableOn.tags_table, :name
|
19
19
|
|
20
|
-
remove_index
|
20
|
+
remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_idx'
|
21
21
|
|
22
|
-
add_index
|
23
|
-
add_index
|
22
|
+
add_index ActsAsTaggableOn.taggings_table, :tag_id unless index_exists?(ActsAsTaggableOn.taggings_table, :tag_id)
|
23
|
+
add_index ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :context], name: 'taggings_taggable_context_idx'
|
24
24
|
end
|
25
25
|
end
|
@@ -5,15 +5,15 @@ else
|
|
5
5
|
end
|
6
6
|
AddTaggingsCounterCacheToTags.class_eval do
|
7
7
|
def self.up
|
8
|
-
add_column
|
8
|
+
add_column ActsAsTaggableOn.tags_table, :taggings_count, :integer, default: 0
|
9
9
|
|
10
10
|
ActsAsTaggableOn::Tag.reset_column_information
|
11
11
|
ActsAsTaggableOn::Tag.find_each do |tag|
|
12
|
-
ActsAsTaggableOn::Tag.reset_counters(tag.id,
|
12
|
+
ActsAsTaggableOn::Tag.reset_counters(tag.id, ActsAsTaggableOn.taggings_table)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.down
|
17
|
-
remove_column
|
17
|
+
remove_column ActsAsTaggableOn.tags_table, :taggings_count
|
18
18
|
end
|
19
19
|
end
|
@@ -5,10 +5,10 @@ else
|
|
5
5
|
end
|
6
6
|
AddMissingTaggableIndex.class_eval do
|
7
7
|
def self.up
|
8
|
-
add_index
|
8
|
+
add_index ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :context], name: 'taggings_taggable_context_idx'
|
9
9
|
end
|
10
10
|
|
11
11
|
def self.down
|
12
|
-
remove_index
|
12
|
+
remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_taggable_context_idx'
|
13
13
|
end
|
14
14
|
end
|
@@ -8,7 +8,7 @@ end
|
|
8
8
|
ChangeCollationForTagNames.class_eval do
|
9
9
|
def up
|
10
10
|
if ActsAsTaggableOn::Utils.using_mysql?
|
11
|
-
execute("ALTER TABLE
|
11
|
+
execute("ALTER TABLE #{ActsAsTaggableOn.tags_table} MODIFY name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin;")
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -5,18 +5,18 @@ else
|
|
5
5
|
end
|
6
6
|
AddMissingIndexesOnTaggings.class_eval do
|
7
7
|
def change
|
8
|
-
add_index
|
9
|
-
add_index
|
10
|
-
add_index
|
11
|
-
add_index
|
12
|
-
add_index
|
8
|
+
add_index ActsAsTaggableOn.taggings_table, :tag_id unless index_exists? ActsAsTaggableOn.taggings_table, :tag_id
|
9
|
+
add_index ActsAsTaggableOn.taggings_table, :taggable_id unless index_exists? ActsAsTaggableOn.taggings_table, :taggable_id
|
10
|
+
add_index ActsAsTaggableOn.taggings_table, :taggable_type unless index_exists? ActsAsTaggableOn.taggings_table, :taggable_type
|
11
|
+
add_index ActsAsTaggableOn.taggings_table, :tagger_id unless index_exists? ActsAsTaggableOn.taggings_table, :tagger_id
|
12
|
+
add_index ActsAsTaggableOn.taggings_table, :context unless index_exists? ActsAsTaggableOn.taggings_table, :context
|
13
13
|
|
14
|
-
unless index_exists?
|
15
|
-
add_index
|
14
|
+
unless index_exists? ActsAsTaggableOn.taggings_table, [:tagger_id, :tagger_type]
|
15
|
+
add_index ActsAsTaggableOn.taggings_table, [:tagger_id, :tagger_type]
|
16
16
|
end
|
17
17
|
|
18
|
-
unless index_exists?
|
19
|
-
add_index
|
18
|
+
unless index_exists? ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy'
|
19
|
+
add_index ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy'
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
if ActiveRecord.gem_version >= Gem::Version.new('5.0')
|
2
|
+
class AddTenantToTaggings < ActiveRecord::Migration[4.2]; end
|
3
|
+
else
|
4
|
+
class AddTenantToTaggings < ActiveRecord::Migration; end
|
5
|
+
end
|
6
|
+
AddTenantToTaggings.class_eval do
|
7
|
+
def self.up
|
8
|
+
add_column :taggings, :tenant, :string, limit: 128
|
9
|
+
add_index :taggings, :tenant unless index_exists? :taggings, :tenant
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.down
|
13
|
+
remove_index :taggings, :tenant
|
14
|
+
remove_column :taggings, :tenant
|
15
|
+
end
|
16
|
+
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# This file was generated by Appraisal
|
2
|
-
|
3
1
|
source "https://rubygems.org"
|
4
2
|
|
5
3
|
gem "activerecord", "~> 5.0.3"
|
@@ -9,7 +7,7 @@ when "postgresql"
|
|
9
7
|
when "mysql"
|
10
8
|
gem 'mysql2', '~> 0.3'
|
11
9
|
else
|
12
|
-
gem
|
10
|
+
gem "sqlite3", "~> 1.3", "< 1.4"
|
13
11
|
end
|
14
12
|
|
15
13
|
group :local_development do
|
@@ -17,7 +15,7 @@ group :local_development do
|
|
17
15
|
gem "guard-rspec"
|
18
16
|
gem "appraisal"
|
19
17
|
gem "rake"
|
20
|
-
gem "byebug", platforms: [:
|
18
|
+
gem "byebug", platforms: [:mri]
|
21
19
|
end
|
22
20
|
|
23
21
|
gemspec path: "../"
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# This file was generated by Appraisal
|
2
|
-
|
3
1
|
source "https://rubygems.org"
|
4
2
|
|
5
3
|
gem "activerecord", "~> 5.1.1"
|
@@ -9,7 +7,7 @@ when "postgresql"
|
|
9
7
|
when "mysql"
|
10
8
|
gem 'mysql2', '~> 0.3'
|
11
9
|
else
|
12
|
-
gem
|
10
|
+
gem "sqlite3", "~> 1.3", "< 1.4"
|
13
11
|
end
|
14
12
|
|
15
13
|
group :local_development do
|
@@ -17,7 +15,7 @@ group :local_development do
|
|
17
15
|
gem "guard-rspec"
|
18
16
|
gem "appraisal"
|
19
17
|
gem "rake"
|
20
|
-
gem "byebug", platforms: [:
|
18
|
+
gem "byebug", platforms: [:mri]
|
21
19
|
end
|
22
20
|
|
23
21
|
gemspec path: "../"
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# This file was generated by Appraisal
|
2
|
-
|
3
1
|
source "https://rubygems.org"
|
4
2
|
|
5
3
|
gem "activerecord", "~> 5.2.0"
|
@@ -9,7 +7,7 @@ when "postgresql"
|
|
9
7
|
when "mysql"
|
10
8
|
gem 'mysql2', '~> 0.3'
|
11
9
|
else
|
12
|
-
gem
|
10
|
+
gem "sqlite3", "~> 1.3", "< 1.4"
|
13
11
|
end
|
14
12
|
|
15
13
|
group :local_development do
|
@@ -17,7 +15,7 @@ group :local_development do
|
|
17
15
|
gem "guard-rspec"
|
18
16
|
gem "appraisal"
|
19
17
|
gem "rake"
|
20
|
-
gem "byebug", platforms: [:
|
18
|
+
gem "byebug", platforms: [:mri]
|
21
19
|
end
|
22
20
|
|
23
21
|
gemspec path: "../"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
gem "activerecord", "~> 6.0.0"
|
4
|
+
case ENV["DB"]
|
5
|
+
when "postgresql"
|
6
|
+
gem 'pg'
|
7
|
+
when "mysql"
|
8
|
+
gem 'mysql2', '~> 0.4'
|
9
|
+
else
|
10
|
+
gem 'sqlite3'
|
11
|
+
end
|
12
|
+
|
13
|
+
group :local_development do
|
14
|
+
gem "guard"
|
15
|
+
gem "guard-rspec"
|
16
|
+
gem "appraisal"
|
17
|
+
gem "rake"
|
18
|
+
gem "byebug", platforms: [:mri]
|
19
|
+
end
|
20
|
+
|
21
|
+
gemspec path: "../"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activerecord", "~> 6.1.0"
|
6
|
+
case ENV["DB"]
|
7
|
+
when "postgresql"
|
8
|
+
gem 'pg'
|
9
|
+
when "mysql"
|
10
|
+
gem 'mysql2', '~> 0.5'
|
11
|
+
else
|
12
|
+
gem 'sqlite3'
|
13
|
+
end
|
14
|
+
|
15
|
+
group :local_development do
|
16
|
+
gem "guard"
|
17
|
+
gem "guard-rspec"
|
18
|
+
gem "appraisal"
|
19
|
+
gem "rake"
|
20
|
+
gem "byebug", platforms: [:mri]
|
21
|
+
end
|
22
|
+
|
23
|
+
gemspec path: "../"
|
data/lib/acts-as-taggable-on.rb
CHANGED
@@ -31,6 +31,7 @@ module ActsAsTaggableOn
|
|
31
31
|
autoload :Dirty
|
32
32
|
autoload :Ownership
|
33
33
|
autoload :Related
|
34
|
+
autoload :TagListType
|
34
35
|
end
|
35
36
|
|
36
37
|
autoload :Utils
|
@@ -57,13 +58,14 @@ module ActsAsTaggableOn
|
|
57
58
|
def self.glue
|
58
59
|
setting = @configuration.delimiter
|
59
60
|
delimiter = setting.kind_of?(Array) ? setting[0] : setting
|
60
|
-
delimiter.
|
61
|
+
delimiter.end_with?(' ') ? delimiter : "#{delimiter} "
|
61
62
|
end
|
62
63
|
|
63
64
|
class Configuration
|
64
65
|
attr_accessor :force_lowercase, :force_parameterize,
|
65
66
|
:remove_unused_tags, :default_parser,
|
66
|
-
:tags_counter
|
67
|
+
:tags_counter, :tags_table,
|
68
|
+
:taggings_table
|
67
69
|
attr_reader :delimiter, :strict_case_match
|
68
70
|
|
69
71
|
def initialize
|
@@ -75,6 +77,8 @@ module ActsAsTaggableOn
|
|
75
77
|
@tags_counter = true
|
76
78
|
@default_parser = DefaultParser
|
77
79
|
@force_binary_collation = false
|
80
|
+
@tags_table = :tags
|
81
|
+
@taggings_table = :taggings
|
78
82
|
end
|
79
83
|
|
80
84
|
def strict_case_match=(force_cs)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module ActsAsTaggableOn
|
3
3
|
class Tag < ::ActiveRecord::Base
|
4
|
+
self.table_name = ActsAsTaggableOn.tags_table
|
4
5
|
|
5
6
|
### ASSOCIATIONS:
|
6
7
|
|
@@ -9,7 +10,7 @@ module ActsAsTaggableOn
|
|
9
10
|
### VALIDATIONS:
|
10
11
|
|
11
12
|
validates_presence_of :name
|
12
|
-
validates_uniqueness_of :name, if: :validates_name_uniqueness
|
13
|
+
validates_uniqueness_of :name, if: :validates_name_uniqueness?, case_sensitive: true
|
13
14
|
validates_length_of :name, maximum: 255
|
14
15
|
|
15
16
|
# monkey patch this method if don't need name uniqueness validation
|
@@ -50,8 +51,14 @@ module ActsAsTaggableOn
|
|
50
51
|
|
51
52
|
def self.for_context(context)
|
52
53
|
joins(:taggings).
|
53
|
-
where(["
|
54
|
-
select("DISTINCT
|
54
|
+
where(["#{ActsAsTaggableOn.taggings_table}.context = ?", context]).
|
55
|
+
select("DISTINCT #{ActsAsTaggableOn.tags_table}.*")
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.for_tenant(tenant)
|
59
|
+
joins(:taggings).
|
60
|
+
where("#{ActsAsTaggableOn.taggings_table}.tenant = ?", tenant.to_s).
|
61
|
+
select("DISTINCT #{ActsAsTaggableOn.tags_table}.*")
|
55
62
|
end
|
56
63
|
|
57
64
|
### CLASS METHODS:
|
@@ -69,17 +76,17 @@ module ActsAsTaggableOn
|
|
69
76
|
|
70
77
|
return [] if list.empty?
|
71
78
|
|
79
|
+
existing_tags = named_any(list)
|
72
80
|
list.map do |tag_name|
|
73
81
|
begin
|
74
82
|
tries ||= 3
|
75
|
-
|
76
|
-
existing_tags = named_any(list)
|
77
83
|
comparable_tag_name = comparable_name(tag_name)
|
78
84
|
existing_tag = existing_tags.find { |tag| comparable_name(tag.name) == comparable_tag_name }
|
79
85
|
existing_tag || create(name: tag_name)
|
80
86
|
rescue ActiveRecord::RecordNotUnique
|
81
87
|
if (tries -= 1).positive?
|
82
88
|
ActiveRecord::Base.connection.execute 'ROLLBACK'
|
89
|
+
existing_tags = named_any(list)
|
83
90
|
retry
|
84
91
|
end
|
85
92
|
|
@@ -104,8 +111,6 @@ module ActsAsTaggableOn
|
|
104
111
|
|
105
112
|
class << self
|
106
113
|
|
107
|
-
|
108
|
-
|
109
114
|
private
|
110
115
|
|
111
116
|
def comparable_name(str)
|
@@ -120,20 +125,12 @@ module ActsAsTaggableOn
|
|
120
125
|
ActsAsTaggableOn::Utils.using_mysql? ? 'BINARY ' : nil
|
121
126
|
end
|
122
127
|
|
123
|
-
def
|
124
|
-
|
125
|
-
ActiveSupport::Multibyte::Unicode.downcase(string)
|
126
|
-
else
|
127
|
-
ActiveSupport::Multibyte::Chars.new(string).downcase.to_s
|
128
|
-
end
|
128
|
+
def as_8bit_ascii(string)
|
129
|
+
string.to_s.mb_chars
|
129
130
|
end
|
130
131
|
|
131
|
-
def
|
132
|
-
|
133
|
-
string.to_s.dup.force_encoding('BINARY')
|
134
|
-
else
|
135
|
-
string.to_s.mb_chars
|
136
|
-
end
|
132
|
+
def unicode_downcase(string)
|
133
|
+
as_8bit_ascii(string).downcase
|
137
134
|
end
|
138
135
|
|
139
136
|
def sanitize_sql_for_named_any(tag)
|
@@ -3,47 +3,51 @@ module ActsAsTaggableOn::Taggable
|
|
3
3
|
def self.included(base)
|
4
4
|
# When included, conditionally adds tag caching methods when the model
|
5
5
|
# has any "cached_#{tag_type}_list" column
|
6
|
-
base.
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
base.extend Columns
|
7
|
+
end
|
8
|
+
|
9
|
+
module Columns
|
10
|
+
# ActiveRecord::Base.columns makes a database connection and caches the
|
11
|
+
# calculated columns hash for the record as @columns. Since we don't
|
12
|
+
# want to add caching methods until we confirm the presence of a
|
13
|
+
# caching column, and we don't want to force opening a database
|
14
|
+
# connection when the class is loaded, here we intercept and cache
|
15
|
+
# the call to :columns as @acts_as_taggable_on_cache_columns
|
16
|
+
# to mimic the underlying behavior. While processing this first
|
17
|
+
# call to columns, we do the caching column check and dynamically add
|
18
|
+
# the class and instance methods
|
19
|
+
# FIXME: this method cannot compile in rubinius
|
20
|
+
def columns
|
21
|
+
@acts_as_taggable_on_cache_columns ||= begin
|
22
|
+
db_columns = super
|
23
|
+
_add_tags_caching_methods if _has_tags_cache_columns?(db_columns)
|
24
|
+
db_columns
|
13
25
|
end
|
26
|
+
end
|
14
27
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
28
|
+
def reset_column_information
|
29
|
+
super
|
30
|
+
@acts_as_taggable_on_cache_columns = nil
|
31
|
+
end
|
19
32
|
|
20
|
-
|
33
|
+
private
|
21
34
|
|
22
|
-
|
35
|
+
# @private
|
36
|
+
def _has_tags_cache_columns?(db_columns)
|
37
|
+
db_column_names = db_columns.map(&:name)
|
38
|
+
tag_types.any? do |context|
|
39
|
+
db_column_names.include?("cached_#{context.to_s.singularize}_list")
|
23
40
|
end
|
41
|
+
end
|
24
42
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
# connection when the class is loaded, here we intercept and cache
|
30
|
-
# the call to :columns as @acts_as_taggable_on_cache_columns
|
31
|
-
# to mimic the underlying behavior. While processing this first
|
32
|
-
# call to columns, we do the caching column check and dynamically add
|
33
|
-
# the class and instance methods
|
34
|
-
# FIXME: this method cannot compile in rubinius
|
35
|
-
def columns
|
36
|
-
@acts_as_taggable_on_cache_columns ||= begin
|
37
|
-
db_columns = super
|
38
|
-
_add_tags_caching_methods if _has_tags_cache_columns?(db_columns)
|
39
|
-
db_columns
|
40
|
-
end
|
41
|
-
end
|
43
|
+
# @private
|
44
|
+
def _add_tags_caching_methods
|
45
|
+
send :include, ActsAsTaggableOn::Taggable::Cache::InstanceMethods
|
46
|
+
extend ActsAsTaggableOn::Taggable::Cache::ClassMethods
|
42
47
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
48
|
+
before_save :save_cached_tag_list
|
49
|
+
|
50
|
+
initialize_tags_cache
|
47
51
|
end
|
48
52
|
end
|
49
53
|
|
@@ -94,16 +94,18 @@ module ActsAsTaggableOn::Taggable
|
|
94
94
|
## Generate conditions:
|
95
95
|
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
96
96
|
|
97
|
-
## Generate joins:
|
98
|
-
taggable_join = "INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id"
|
99
|
-
taggable_join << " AND #{table_name}.#{inheritance_column} = '#{name}'" unless descends_from_active_record? # Current model is STI descendant, so add type checking to the join condition
|
100
|
-
|
101
97
|
## Generate scope:
|
102
98
|
tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id, COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) AS tags_count")
|
103
99
|
tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*, #{ActsAsTaggableOn::Tagging.table_name}.tags_count AS count").order(options[:order]).limit(options[:limit])
|
104
100
|
|
105
|
-
#
|
106
|
-
|
101
|
+
# Current model is STI descendant, so add type checking to the join condition
|
102
|
+
unless descends_from_active_record?
|
103
|
+
taggable_join = "INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id"
|
104
|
+
taggable_join << " AND #{table_name}.#{inheritance_column} = '#{name}'"
|
105
|
+
tagging_scope = tagging_scope.joins(taggable_join)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Conditions
|
107
109
|
tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) }
|
108
110
|
tag_scope = tag_scope.where(options[:conditions])
|
109
111
|
|