acts-as-taggable-on 6.0.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +17 -7
  3. data/Appraisals +4 -0
  4. data/CHANGELOG.md +179 -140
  5. data/README.md +10 -1
  6. data/acts-as-taggable-on.gemspec +2 -2
  7. data/db/migrate/1_acts_as_taggable_on_migration.rb +8 -7
  8. data/db/migrate/2_add_missing_unique_indices.rb +8 -8
  9. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +3 -3
  10. data/db/migrate/4_add_missing_taggable_index.rb +2 -2
  11. data/db/migrate/5_change_collation_for_tag_names.rb +1 -1
  12. data/db/migrate/6_add_missing_indexes_on_taggings.rb +9 -9
  13. data/gemfiles/activerecord_5.0.gemfile +2 -4
  14. data/gemfiles/activerecord_5.1.gemfile +2 -4
  15. data/gemfiles/activerecord_5.2.gemfile +2 -4
  16. data/gemfiles/activerecord_6.0.gemfile +21 -0
  17. data/lib/acts-as-taggable-on.rb +5 -1
  18. data/lib/acts_as_taggable_on/tag.rb +9 -18
  19. data/lib/acts_as_taggable_on/taggable/cache.rb +38 -34
  20. data/lib/acts_as_taggable_on/taggable/collection.rb +8 -6
  21. data/lib/acts_as_taggable_on/taggable/core.rb +11 -5
  22. data/lib/acts_as_taggable_on/taggable/tag_list_type.rb +4 -0
  23. data/lib/acts_as_taggable_on/tagging.rb +3 -1
  24. data/lib/acts_as_taggable_on/utils.rb +4 -0
  25. data/lib/acts_as_taggable_on/version.rb +1 -2
  26. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +4 -12
  27. data/spec/acts_as_taggable_on/caching_spec.rb +16 -10
  28. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +12 -7
  29. data/spec/acts_as_taggable_on/taggable_spec.rb +9 -9
  30. data/spec/acts_as_taggable_on/tagger_spec.rb +2 -2
  31. data/spec/internal/app/models/altered_inheriting_taggable_model.rb +2 -0
  32. data/spec/internal/app/models/cached_model_with_array.rb +6 -0
  33. data/spec/internal/app/models/columns_override_model.rb +5 -0
  34. data/spec/internal/app/models/company.rb +1 -1
  35. data/spec/internal/app/models/inheriting_taggable_model.rb +2 -0
  36. data/spec/internal/app/models/market.rb +1 -1
  37. data/spec/internal/app/models/non_standard_id_taggable_model.rb +1 -1
  38. data/spec/internal/app/models/student.rb +2 -0
  39. data/spec/internal/app/models/taggable_model.rb +1 -0
  40. data/spec/internal/app/models/user.rb +1 -1
  41. data/spec/internal/db/schema.rb +11 -5
  42. data/spec/spec_helper.rb +0 -1
  43. data/spec/support/database.rb +3 -3
  44. metadata +16 -9
  45. data/spec/internal/app/models/models.rb +0 -90
data/README.md CHANGED
@@ -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', '~> 6.0'
61
61
  ```
62
62
 
63
63
  and bundle:
@@ -121,6 +121,7 @@ Add and remove a single tag
121
121
  ```ruby
122
122
  @user.tag_list.add("awesome") # add a single tag. alias for <<
123
123
  @user.tag_list.remove("awesome") # remove a single tag
124
+ @user.save # save to persist tag_list
124
125
  ```
125
126
 
126
127
  Add and remove multiple tags in an array
@@ -128,6 +129,7 @@ Add and remove multiple tags in an array
128
129
  ```ruby
129
130
  @user.tag_list.add("awesome", "slick")
130
131
  @user.tag_list.remove("awesome", "slick")
132
+ @user.save
131
133
  ```
132
134
 
133
135
  You can also add and remove tags in format of String. This would
@@ -486,6 +488,13 @@ If you would like to have an exact match covering special characters with MySql:
486
488
  ActsAsTaggableOn.force_binary_collation = true
487
489
  ```
488
490
 
491
+ If you would like to specify table names:
492
+
493
+ ```ruby
494
+ ActsAsTaggableOn.tags_table = 'aato_tags'
495
+ ActsAsTaggableOn.taggings_table = 'aato_taggings'
496
+ ```
497
+
489
498
  If you want to change the default delimiter (it defaults to ','). You can also pass in an array of delimiters such as ([',', '|']):
490
499
 
491
500
  ```ruby
@@ -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.1'
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
@@ -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.3'
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: "../"
@@ -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
@@ -63,7 +64,8 @@ module ActsAsTaggableOn
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
 
@@ -50,8 +51,8 @@ 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}.*")
55
56
  end
56
57
 
57
58
  ### CLASS METHODS:
@@ -69,17 +70,17 @@ module ActsAsTaggableOn
69
70
 
70
71
  return [] if list.empty?
71
72
 
73
+ existing_tags = named_any(list)
72
74
  list.map do |tag_name|
73
75
  begin
74
76
  tries ||= 3
75
-
76
- existing_tags = named_any(list)
77
77
  comparable_tag_name = comparable_name(tag_name)
78
78
  existing_tag = existing_tags.find { |tag| comparable_name(tag.name) == comparable_tag_name }
79
79
  existing_tag || create(name: tag_name)
80
80
  rescue ActiveRecord::RecordNotUnique
81
81
  if (tries -= 1).positive?
82
82
  ActiveRecord::Base.connection.execute 'ROLLBACK'
83
+ existing_tags = named_any(list)
83
84
  retry
84
85
  end
85
86
 
@@ -104,8 +105,6 @@ module ActsAsTaggableOn
104
105
 
105
106
  class << self
106
107
 
107
-
108
-
109
108
  private
110
109
 
111
110
  def comparable_name(str)
@@ -120,20 +119,12 @@ module ActsAsTaggableOn
120
119
  ActsAsTaggableOn::Utils.using_mysql? ? 'BINARY ' : nil
121
120
  end
122
121
 
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
122
+ def as_8bit_ascii(string)
123
+ string.to_s.mb_chars
129
124
  end
130
125
 
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
126
+ def unicode_downcase(string)
127
+ as_8bit_ascii(string).downcase
137
128
  end
138
129
 
139
130
  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