acts-as-taggable-on-mongoid 6.0.1.4 → 6.1.1.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +2 -1
  3. data/.rubocop.yml +402 -38
  4. data/.ruby-version +1 -1
  5. data/Gemfile.lock +110 -110
  6. data/README.md +103 -28
  7. data/acts-as-taggable-on-mongoid.gemspec +5 -5
  8. data/lib/acts-as-taggable-on-mongoid.rb +11 -4
  9. data/lib/acts_as_taggable_on_mongoid/models/concerns/tag_associations.rb +14 -0
  10. data/lib/acts_as_taggable_on_mongoid/models/concerns/tag_fields.rb +6 -1
  11. data/lib/acts_as_taggable_on_mongoid/models/concerns/tag_hooks.rb +68 -0
  12. data/lib/acts_as_taggable_on_mongoid/models/concerns/tag_methods.rb +36 -22
  13. data/lib/acts_as_taggable_on_mongoid/models/concerns/tag_migration.rb +46 -0
  14. data/lib/acts_as_taggable_on_mongoid/models/concerns/tag_model.rb +3 -0
  15. data/lib/acts_as_taggable_on_mongoid/models/concerns/tag_scopes.rb +5 -2
  16. data/lib/acts_as_taggable_on_mongoid/models/concerns/tag_validations.rb +3 -1
  17. data/lib/acts_as_taggable_on_mongoid/models/concerns/tagging_associations.rb +20 -2
  18. data/lib/acts_as_taggable_on_mongoid/models/concerns/tagging_fields.rb +6 -2
  19. data/lib/acts_as_taggable_on_mongoid/models/concerns/tagging_methods.rb +4 -2
  20. data/lib/acts_as_taggable_on_mongoid/models/concerns/tagging_migration.rb +46 -0
  21. data/lib/acts_as_taggable_on_mongoid/models/concerns/tagging_model.rb +2 -0
  22. data/lib/acts_as_taggable_on_mongoid/models/concerns/tagging_scopes.rb +9 -5
  23. data/lib/acts_as_taggable_on_mongoid/models/concerns/tagging_validations.rb +4 -6
  24. data/lib/acts_as_taggable_on_mongoid/tag_list.rb +91 -4
  25. data/lib/acts_as_taggable_on_mongoid/taggable.rb +22 -3
  26. data/lib/acts_as_taggable_on_mongoid/taggable/cache.rb +30 -0
  27. data/lib/acts_as_taggable_on_mongoid/taggable/changeable.rb +5 -4
  28. data/lib/acts_as_taggable_on_mongoid/taggable/core.rb +157 -34
  29. data/lib/acts_as_taggable_on_mongoid/taggable/list_tags.rb +1 -0
  30. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition.rb +43 -50
  31. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/attributes.rb +70 -6
  32. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/changeable.rb +52 -39
  33. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/list_methods.rb +77 -0
  34. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/names.rb +12 -0
  35. data/lib/acts_as_taggable_on_mongoid/taggable/tagged_with_query/all_tags_query.rb +1 -1
  36. data/lib/acts_as_taggable_on_mongoid/taggable/tagged_with_query/any_tags_query.rb +1 -1
  37. data/lib/acts_as_taggable_on_mongoid/taggable/tagged_with_query/exclude_tags_query.rb +1 -1
  38. data/lib/acts_as_taggable_on_mongoid/taggable/tagged_with_query/match_all_tags_query.rb +1 -1
  39. data/lib/acts_as_taggable_on_mongoid/taggable/tagger_relation.rb +53 -0
  40. data/lib/acts_as_taggable_on_mongoid/taggable/utils/tag_list_diff.rb +9 -7
  41. data/lib/acts_as_taggable_on_mongoid/tagger.rb +67 -0
  42. data/lib/acts_as_taggable_on_mongoid/tagger/tag_methods.rb +74 -0
  43. data/lib/acts_as_taggable_on_mongoid/tagger_tag_list.rb +171 -0
  44. data/lib/acts_as_taggable_on_mongoid/version.rb +1 -1
  45. metadata +43 -29
@@ -31,11 +31,10 @@ Gem::Specification.new do |spec|
31
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
32
  spec.require_paths = ["lib"]
33
33
 
34
- spec.add_dependency "activesupport", "~> 4.2"
35
- spec.add_dependency "mongoid", "~> 5.2"
34
+ spec.add_dependency "activesupport", ">= 5.0"
35
+ spec.add_dependency "mongoid", ">= 6.1.1", "<= 7.0.2"
36
36
 
37
- spec.add_development_dependency "bundler", "~> 1.16"
38
- spec.add_development_dependency "codecov", "~> 0.1", "~> 0.1.0"
37
+ spec.add_development_dependency "codecov", "~> 0.1", "~> 0.2.2"
39
38
  spec.add_development_dependency "cornucopia"
40
39
  spec.add_development_dependency "database_cleaner"
41
40
  spec.add_development_dependency "pronto"
@@ -45,10 +44,11 @@ Gem::Specification.new do |spec|
45
44
  spec.add_development_dependency "pronto-rails_best_practices"
46
45
  spec.add_development_dependency "pronto-reek"
47
46
  spec.add_development_dependency "pronto-rubocop"
48
- spec.add_development_dependency "rake", "~> 12.3"
47
+ spec.add_development_dependency "rake", "~> 13.0"
49
48
  spec.add_development_dependency "rspec", "~> 3.0"
50
49
  spec.add_development_dependency "rspec_junit_formatter", "~> 0.4.1"
51
50
  spec.add_development_dependency "rubocop"
51
+ spec.add_development_dependency "rubocop-performance"
52
52
  spec.add_development_dependency "simplecov"
53
53
  spec.add_development_dependency "simplecov-rcov"
54
54
  spec.add_development_dependency "timecop"
@@ -11,11 +11,13 @@ module ActsAsTaggableOnMongoid
11
11
  eager_autoload do
12
12
  autoload :Configuration
13
13
  autoload :TagList
14
+ autoload :TaggerTagList
14
15
  autoload :GenericParser
15
16
  autoload :DefaultParser
16
17
  # autoload :TagsHelper
17
18
 
18
19
  autoload_under "taggable/tag_type_definition" do
20
+ autoload :ListMethods
19
21
  autoload :Attributes
20
22
  autoload "Changeable"
21
23
  autoload :Names
@@ -34,7 +36,7 @@ module ActsAsTaggableOnMongoid
34
36
  end
35
37
 
36
38
  autoload_under :Taggable do
37
- # autoload :Cache
39
+ autoload :Cache
38
40
  # autoload :Collection
39
41
  autoload :Core
40
42
  autoload :Changeable
@@ -42,14 +44,20 @@ module ActsAsTaggableOnMongoid
42
44
  autoload :ListTags
43
45
  autoload :TaggedWith
44
46
  autoload :TaggedWithQuery
45
- # autoload :Ownership
47
+ autoload :TaggerRelation
46
48
  # autoload :Related
47
49
  end
48
50
 
51
+ autoload_under :Tagger do
52
+ autoload "TagMethods"
53
+ end
54
+
49
55
  autoload :Taggable
56
+ autoload :Tagger
50
57
 
51
58
  autoload_under "models/concerns" do
52
59
  autoload :TagFields
60
+ autoload :TagHooks
53
61
  autoload :TagAssociations
54
62
  autoload :TagValidations
55
63
  autoload :TagScopes
@@ -67,7 +75,6 @@ module ActsAsTaggableOnMongoid
67
75
  autoload_under :Models do
68
76
  autoload :Tag
69
77
  autoload :Tagging
70
- # autoload :Tagger
71
78
  end
72
79
 
73
80
  autoload_under :Errors do
@@ -102,5 +109,5 @@ end
102
109
 
103
110
  ::ActiveSupport.on_load(:mongoid) do
104
111
  include ActsAsTaggableOnMongoid::Taggable
105
- # include ActsAsTaggableOn::Tagger
112
+ include ActsAsTaggableOnMongoid::Tagger
106
113
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsTaggableOnMongoid
2
4
  module Models
3
5
  module Concerns
@@ -8,6 +10,18 @@ module ActsAsTaggableOnMongoid
8
10
  ### ASSOCIATIONS:
9
11
 
10
12
  has_many :taggings, dependent: :destroy, class_name: "ActsAsTaggableOnMongoid::Models::Tagging"
13
+ belongs_to :owner, polymorphic: true, optional: true, index: true
14
+
15
+ after_save :atom_unset_blank_owner
16
+ end
17
+
18
+ private
19
+
20
+ def atom_unset_blank_owner
21
+ return if !attributes.key?("owner_id") && !attributes.key?("owner_type")
22
+ return if owner_id.present? && owner_type.present?
23
+
24
+ unset(:owner_id, :owner_type)
11
25
  end
12
26
  end
13
27
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsTaggableOnMongoid
2
4
  module Models
3
5
  module Concerns
@@ -15,7 +17,10 @@ module ActsAsTaggableOnMongoid
15
17
 
16
18
  # field :type, type: String
17
19
 
18
- index({ name: 1, taggable_type: 1, context: 1 }, unique: true)
20
+ index({ name: 1, context: 1, taggable_type: 1, owner_id: 1, owner_type: 1 },
21
+ unique: true,
22
+ name: "name_taggable_type_context_owner")
23
+ index(owner_id: 1, owner_type: 1)
19
24
  end
20
25
  end
21
26
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOnMongoid
4
+ module Models
5
+ module Concerns
6
+ # Update and Destroy hooks for tags to update denormalized data.
7
+ #
8
+ # NOTE: If the tag is cached AND owned, assumptions are made about the
9
+ # relationship of a Tag to the Taggings and the taggable_type.
10
+ #
11
+ # Specifically, the taggable_type is assumed to have an ID field that
12
+ # matches the owner. This field can be specified in the tag_definition
13
+ # as owner_id_field.
14
+ #
15
+ # If there isn't a simple relationship like this then override the following
16
+ # methods to update the cached data properly:
17
+ # * remove_cached_taggings
18
+ # * update_cached_taggings
19
+ module TagHooks
20
+ extend ActiveSupport::Concern
21
+
22
+ included do
23
+ after_update :denormalize_tag_name
24
+ after_destroy :remove_cached_taggings
25
+ end
26
+
27
+ private
28
+
29
+ def denormalize_tag_name
30
+ return unless name_changed?
31
+
32
+ update_taggings
33
+ update_cached_taggings
34
+ end
35
+
36
+ def update_taggings
37
+ taggings.update_all(tag_name: name)
38
+ end
39
+
40
+ def remove_cached_taggings
41
+ return if tag_definition.blank?
42
+ return unless tag_definition.cached_in_model?
43
+
44
+ cached_fields_query(name).update_all("$pull" => { "cached_#{tag_definition.tag_list_name}" => name })
45
+ end
46
+
47
+ def update_cached_taggings
48
+ return if tag_definition.blank?
49
+ return unless tag_definition.cached_in_model?
50
+
51
+ cached_fields_query(name_was).update_all("$set" => { "cached_#{tag_definition.tag_list_name}.$" => name })
52
+ end
53
+
54
+ def cached_fields_query(chached_field_value)
55
+ query = { "cached_#{tag_definition.tag_list_name}" => chached_field_value }
56
+
57
+ if owner_id.present?
58
+ id_field = tag_definition.owner_id_field || "#{owner_type.underscore}_id"
59
+
60
+ query[id_field] = owner_id
61
+ end
62
+
63
+ taggable_type.constantize.unscoped.where(query)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsTaggableOnMongoid
2
4
  module Models
3
5
  module Concerns
@@ -6,49 +8,55 @@ module ActsAsTaggableOnMongoid
6
8
 
7
9
  ### CLASS METHODS:
8
10
 
11
+ # rubocop:disable Metrics/BlockLength
9
12
  class_methods do
10
- def find_or_create_all_with_like_by_name(tag_definition, *list)
13
+ def find_or_create_tagger_list_with_like_by_name(tag_definition, tagger_list)
14
+ tagger_list.each_with_object([]) do |(tagger, tag_list), array|
15
+ array.concat find_or_create_all_with_like_by_name_owner tag_definition, tagger, tag_list
16
+ end
17
+ end
18
+
19
+ def find_or_create_all_with_like_by_name_owner(tag_definition, owner, *list)
11
20
  list = ActsAsTaggableOnMongoid::TagList.new(tag_definition, *Array.wrap(list).flatten)
12
21
 
13
22
  return [] if list.empty?
14
23
 
15
24
  list.map do |tag_name|
16
- begin
17
- tries ||= 3
25
+ tries ||= 3
18
26
 
19
- existing_tag = tag_definition.tags_table.for_tag(tag_definition).named(tag_name).first
20
-
21
- existing_tag || create_tag(tag_definition, tag_name)
22
- rescue Mongoid::Errors::Validations
23
- # :nocov:
24
- if (tries -= 1).positive?
25
- retry
26
- end
27
-
28
- raise ActsAsTaggableOnMongoid::Errors::DuplicateTagError.new, "'#{tag_name}' has already been taken"
29
- # :nocov:
27
+ find_or_create_tag(tag_name, tag_definition, owner)
28
+ rescue StandardError
29
+ if (tries -= 1).positive?
30
+ retry
30
31
  end
32
+
33
+ raise
31
34
  end
32
35
  end
33
36
 
34
- def create_tag(tag_definition, name)
37
+ # :reek:UtilityFunction
38
+ def create_tag(tag_definition, owner, name)
35
39
  tag_definition.tags_table.create!(name: name,
40
+ owner: owner,
36
41
  context: tag_definition.tag_type,
37
42
  taggable_type: tag_definition.owner.name)
38
43
  end
39
44
 
40
45
  def as_8bit_ascii(string)
41
46
  string = string.to_s
42
- if defined?(Encoding)
43
- string.dup.force_encoding("BINARY")
44
- else
45
- # :nocov:
46
- string.mb_chars
47
- # :nocov:
48
- end
47
+
48
+ string.mb_chars
49
+ end
50
+
51
+ def find_or_create_tag(tag_name, tag_definition, owner)
52
+ existing_tag = tag_definition.tags_table.for_tag(tag_definition).named(tag_name).owned_by(owner).first
53
+
54
+ existing_tag || create_tag(tag_definition, owner, tag_name)
49
55
  end
50
56
  end
51
57
 
58
+ # rubocop:enable Metrics/BlockLength
59
+
52
60
  ### INSTANCE METHODS:
53
61
 
54
62
  def ==(other)
@@ -61,6 +69,12 @@ module ActsAsTaggableOnMongoid
61
69
  def to_s
62
70
  name
63
71
  end
72
+
73
+ private
74
+
75
+ def tag_definition
76
+ @tag_definition ||= taggable_type.constantize.tag_types[context]
77
+ end
64
78
  end
65
79
  end
66
80
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOnMongoid
4
+ module Models
5
+ module Concerns
6
+ # A module that defines methods to migrate a Tag model.
7
+ #
8
+ # Mongoid does not have a standardized migration scheme, but migrations of one kind or another
9
+ # are often a fact of life, and are in this situation because of the need to change an existing
10
+ # index.
11
+ #
12
+ # Using whatever migration methodology you prefer, you need to run the appropriate migration method
13
+ # or methods on the Tag module used by your project.
14
+ #
15
+ # The migrations are named for the ActsAsTaggableMongoid versions upon which they need to be run.
16
+ # Run the correct migration(s) for the version of ActsAsTaggableOnMongoid you are currently using
17
+ # This module does not and cannot verify the version you are migrating from nor if the migration
18
+ # has been run before. Doing so is assumed to be the responsibility of your chosen
19
+ # Migration methodology.
20
+ #
21
+ # Example:
22
+ #
23
+ # # When a migration is needed, the method "up" is called:
24
+ # def up
25
+ # TagModel.include ActsAsTaggableOnMongoid::Models::Concerns::TagMigration
26
+ # TagModel.atom_migrate_up_6_0_1_to_6_1_1
27
+ # end
28
+ #
29
+ # The migration methods should only be run once if possible, but every reasonable effort is made to
30
+ # ensure that the migration methods are safe to re-run.
31
+ module TagMigration
32
+ extend ActiveSupport::Concern
33
+
34
+ class_methods do
35
+ # :reek:UncommunicativeMethodName - The name indicates what gem versions the migration is from/to
36
+ def atom_migrate_up_6_0_1_to_6_1_1
37
+ indexes = collection.indexes
38
+ indexes.drop_one "name_1_taggable_type_1_context_1" if indexes.get "name_1_taggable_type_1_context_1"
39
+
40
+ create_indexes
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsTaggableOnMongoid
2
4
  module Models
3
5
  module Concerns
@@ -10,6 +12,7 @@ module ActsAsTaggableOnMongoid
10
12
  include ActsAsTaggableOnMongoid::Models::Concerns::TagAssociations
11
13
  include ActsAsTaggableOnMongoid::Models::Concerns::TagValidations
12
14
  include ActsAsTaggableOnMongoid::Models::Concerns::TagScopes
15
+ include ActsAsTaggableOnMongoid::Models::Concerns::TagHooks
13
16
  end
14
17
  end
15
18
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsTaggableOnMongoid
2
4
  module Models
3
5
  module Concerns
@@ -13,9 +15,10 @@ module ActsAsTaggableOnMongoid
13
15
  scope :named_any, ->(*names) { where(:name.in => names.map { |name| as_8bit_ascii(name) }) }
14
16
  scope :named_like, ->(name) { where(name: /#{as_8bit_ascii(name)}/i) }
15
17
  scope :named_like_any, ->(*names) { where(:name.in => names.map { |name| /#{as_8bit_ascii(name)}/i }) }
16
- scope :for_context, ->(context) { where(context: context) }
18
+ scope :owned_by, ->(owner) { owner ? where(owner: owner) : where(:owner_id.exists => false) }
19
+ scope :for_tag_type, ->(context) { where(context: context) }
17
20
  scope :for_taggable_class, ->(taggable_type) { where(taggable_type: taggable_type.name) }
18
- scope :for_tag, ->(tag_definition) { for_taggable_class(tag_definition.owner).for_context(tag_definition.tag_type) }
21
+ scope :for_tag, ->(tag_definition) { for_taggable_class(tag_definition.owner).for_tag_type(tag_definition.tag_type) }
19
22
  end
20
23
  end
21
24
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsTaggableOnMongoid
2
4
  module Models
3
5
  module Concerns
@@ -10,7 +12,7 @@ module ActsAsTaggableOnMongoid
10
12
  validates :name, presence: true
11
13
  validates :context, presence: true
12
14
  validates :taggable_type, presence: true
13
- validates :name, uniqueness: { scope: %i[context taggable_type] }
15
+ validates :name, uniqueness: { scope: %i[context taggable_type owner] }
14
16
  end
15
17
  end
16
18
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsTaggableOnMongoid
2
4
  module Models
3
5
  module Concerns
@@ -5,9 +7,25 @@ module ActsAsTaggableOnMongoid
5
7
  extend ActiveSupport::Concern
6
8
 
7
9
  included do
10
+ ### ASSOCIATIONS:
11
+
8
12
  belongs_to :tag, counter_cache: true, inverse_of: :taggings
9
- belongs_to :taggable, polymorphic: true
10
- # belongs_to :tagger, { polymorphic: true, optional: true }
13
+ belongs_to :tagger, polymorphic: true, optional: true
14
+
15
+ # Tags and Taggings are created in an after_save event on taggable.
16
+ # If autosave is not false, then this will cause the taggable object
17
+ # to be saved AGAIN, that could cause a number of unwanted side-effects.
18
+ belongs_to :taggable, polymorphic: true, autosave: false
19
+
20
+ before_validation :atom_unset_blank_owner
21
+ end
22
+
23
+ private
24
+
25
+ def atom_unset_blank_owner
26
+ return if tagger_id.present? && tagger_type.present?
27
+
28
+ unset(:tagger_id, :tagger_type)
11
29
  end
12
30
  end
13
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsTaggableOnMongoid
2
4
  module Models
3
5
  module Concerns
@@ -11,10 +13,12 @@ module ActsAsTaggableOnMongoid
11
13
  field :tag_name, type: String
12
14
  field :context, type: String
13
15
 
14
- # If/when adding the concept of a tagger, this index will need to be changed.
15
- index({ taggable_id: 1, taggable_type: 1, context: 1, tag_name: 1 }, unique: true, name: "tagging_taggable_context_tag_name")
16
+ index({ taggable_id: 1, taggable_type: 1, context: 1, tagger_id: 1, tagger_type: 1, tag_name: 1 },
17
+ unique: true,
18
+ name: "tagging_taggable_tagger_context_tag_name")
16
19
  index(tag_name: 1)
17
20
  index(tag_id: 1, tag_type: 1)
21
+ index(tagger_id: 1, tagger_type: 1)
18
22
  end
19
23
  end
20
24
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsTaggableOnMongoid
2
4
  module Models
3
5
  module Concerns
@@ -30,7 +32,7 @@ module ActsAsTaggableOnMongoid
30
32
 
31
33
  return unless tag_definition
32
34
 
33
- tag_list = taggable.public_send(tag_definition.tag_list_name)
35
+ tag_list = taggable.public_send(tag_definition.tagger_tag_lists_name)[tagger]
34
36
  tag_list.add_tagging(self)
35
37
  end
36
38
 
@@ -43,7 +45,7 @@ module ActsAsTaggableOnMongoid
43
45
 
44
46
  return unless tag_definition
45
47
 
46
- taggable_was.public_send(tag_definition.tag_list_name).remove(tag_name_was)
48
+ taggable_was.public_send(tag_definition.tagger_tag_lists_name)[tagger].remove(tag_name_was)
47
49
  end
48
50
  end
49
51
  end