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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/spec.yml +95 -0
  3. data/Appraisals +8 -0
  4. data/CHANGELOG.md +199 -140
  5. data/Gemfile +1 -0
  6. data/README.md +38 -4
  7. data/acts-as-taggable-on.gemspec +2 -2
  8. data/db/migrate/1_acts_as_taggable_on_migration.rb +8 -7
  9. data/db/migrate/2_add_missing_unique_indices.rb +8 -8
  10. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +3 -3
  11. data/db/migrate/4_add_missing_taggable_index.rb +2 -2
  12. data/db/migrate/5_change_collation_for_tag_names.rb +1 -1
  13. data/db/migrate/6_add_missing_indexes_on_taggings.rb +9 -9
  14. data/db/migrate/7_add_tenant_to_taggings.rb +16 -0
  15. data/gemfiles/activerecord_5.0.gemfile +2 -4
  16. data/gemfiles/activerecord_5.1.gemfile +2 -4
  17. data/gemfiles/activerecord_5.2.gemfile +2 -4
  18. data/gemfiles/activerecord_6.0.gemfile +21 -0
  19. data/gemfiles/activerecord_6.1.gemfile +23 -0
  20. data/lib/acts-as-taggable-on.rb +6 -2
  21. data/lib/acts_as_taggable_on/tag.rb +16 -19
  22. data/lib/acts_as_taggable_on/taggable/cache.rb +38 -34
  23. data/lib/acts_as_taggable_on/taggable/collection.rb +8 -6
  24. data/lib/acts_as_taggable_on/taggable/core.rb +22 -6
  25. data/lib/acts_as_taggable_on/taggable/tag_list_type.rb +4 -0
  26. data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +4 -4
  27. data/lib/acts_as_taggable_on/taggable.rb +18 -0
  28. data/lib/acts_as_taggable_on/tagger.rb +1 -1
  29. data/lib/acts_as_taggable_on/tagging.rb +6 -2
  30. data/lib/acts_as_taggable_on/utils.rb +4 -0
  31. data/lib/acts_as_taggable_on/version.rb +1 -2
  32. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +4 -12
  33. data/spec/acts_as_taggable_on/caching_spec.rb +16 -10
  34. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +12 -7
  35. data/spec/acts_as_taggable_on/tag_spec.rb +16 -1
  36. data/spec/acts_as_taggable_on/taggable_spec.rb +16 -12
  37. data/spec/acts_as_taggable_on/tagger_spec.rb +2 -2
  38. data/spec/acts_as_taggable_on/tagging_spec.rb +26 -0
  39. data/spec/internal/app/models/altered_inheriting_taggable_model.rb +2 -0
  40. data/spec/internal/app/models/cached_model_with_array.rb +6 -0
  41. data/spec/internal/app/models/columns_override_model.rb +5 -0
  42. data/spec/internal/app/models/company.rb +1 -1
  43. data/spec/internal/app/models/inheriting_taggable_model.rb +2 -0
  44. data/spec/internal/app/models/market.rb +1 -1
  45. data/spec/internal/app/models/non_standard_id_taggable_model.rb +1 -1
  46. data/spec/internal/app/models/student.rb +2 -0
  47. data/spec/internal/app/models/taggable_model.rb +3 -0
  48. data/spec/internal/app/models/user.rb +1 -1
  49. data/spec/internal/config/database.yml.sample +4 -8
  50. data/spec/internal/db/schema.rb +14 -5
  51. data/spec/spec_helper.rb +0 -1
  52. data/spec/support/database.rb +4 -4
  53. metadata +20 -20
  54. data/.travis.yml +0 -33
  55. data/UPGRADING.md +0 -8
  56. 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://secure.travis-ci.org/mbleigh/acts-as-taggable-on.svg)](http://travis-ci.org/mbleigh/acts-as-taggable-on)
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', '~> 5.0'
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
- acts_as_taggable # Alias for acts_as_taggable_on :tags
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
@@ -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.2.7'
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', ['~> 5.0']
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 :tags do |t|
8
+ create_table ActsAsTaggableOn.tags_table do |t|
9
9
  t.string :name
10
+ t.timestamps
10
11
  end
11
12
 
12
- create_table :taggings do |t|
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 :taggings, :tag_id
28
- add_index :taggings, [:taggable_id, :taggable_type, :context]
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 :taggings
33
- drop_table :tags
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 :tags, :name, unique: true
8
+ add_index ActsAsTaggableOn.tags_table, :name, unique: true
9
9
 
10
- remove_index :taggings, :tag_id if index_exists?(:taggings, :tag_id)
11
- remove_index :taggings, [:taggable_id, :taggable_type, :context]
12
- add_index :taggings,
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 :tags, :name
18
+ remove_index ActsAsTaggableOn.tags_table, :name
19
19
 
20
- remove_index :taggings, name: 'taggings_idx'
20
+ remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_idx'
21
21
 
22
- add_index :taggings, :tag_id unless index_exists?(:taggings, :tag_id)
23
- add_index :taggings, [:taggable_id, :taggable_type, :context]
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 :tags, :taggings_count, :integer, default: 0
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, :taggings)
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 :tags, :taggings_count
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 :taggings, [:taggable_id, :taggable_type, :context]
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 :taggings, [:taggable_id, :taggable_type, :context]
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 tags MODIFY name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin;")
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 :taggings, :tag_id unless index_exists? :taggings, :tag_id
9
- add_index :taggings, :taggable_id unless index_exists? :taggings, :taggable_id
10
- add_index :taggings, :taggable_type unless index_exists? :taggings, :taggable_type
11
- add_index :taggings, :tagger_id unless index_exists? :taggings, :tagger_id
12
- add_index :taggings, :context unless index_exists? :taggings, :context
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? :taggings, [:tagger_id, :tagger_type]
15
- add_index :taggings, [:tagger_id, :tagger_type]
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? :taggings, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy'
19
- add_index :taggings, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy'
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 'sqlite3'
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: [:mri_21, :mri_22, :mri_23]
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 'sqlite3'
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: [:mri_21, :mri_22, :mri_23]
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 'sqlite3'
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: [:mri_21, :mri_22, :mri_23]
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: "../"
@@ -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.ends_with?(' ') ? delimiter : "#{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(["taggings.context = ?", context]).
54
- select("DISTINCT tags.*")
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 unicode_downcase(string)
124
- if ActiveSupport::Multibyte::Unicode.respond_to?(:downcase)
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 as_8bit_ascii(string)
132
- if defined?(Encoding)
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.instance_eval do
7
- # @private
8
- def _has_tags_cache_columns?(db_columns)
9
- db_column_names = db_columns.map(&:name)
10
- tag_types.any? do |context|
11
- db_column_names.include?("cached_#{context.to_s.singularize}_list")
12
- end
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
- # @private
16
- def _add_tags_caching_methods
17
- send :include, ActsAsTaggableOn::Taggable::Cache::InstanceMethods
18
- extend ActsAsTaggableOn::Taggable::Cache::ClassMethods
28
+ def reset_column_information
29
+ super
30
+ @acts_as_taggable_on_cache_columns = nil
31
+ end
19
32
 
20
- before_save :save_cached_tag_list
33
+ private
21
34
 
22
- initialize_tags_cache
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
- # ActiveRecord::Base.columns makes a database connection and caches the
26
- # calculated columns hash for the record as @columns. Since we don't
27
- # want to add caching methods until we confirm the presence of a
28
- # caching column, and we don't want to force opening a database
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
- def reset_column_information
44
- super
45
- @acts_as_taggable_on_cache_columns = nil
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
- # Joins and conditions
106
- tagging_scope = tagging_scope.joins(taggable_join)
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