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
@@ -10,6 +10,18 @@ module ActsAsTaggableOnMongoid
10
10
  @tag_list_name ||= "#{single_tag_type}_list"
11
11
  end
12
12
 
13
+ def from_list_name
14
+ @from_list_name ||= "#{tag_type.to_s.pluralize}_from"
15
+ end
16
+
17
+ def tagger_tag_list_name
18
+ @tagger_tag_list_name ||= "tagger_#{single_tag_type}_list"
19
+ end
20
+
21
+ def tagger_tag_lists_name
22
+ @tagger_tag_lists_name ||= "tagger_#{single_tag_type}_lists"
23
+ end
24
+
13
25
  def tag_list_variable_name
14
26
  @tag_list_variable_name ||= "@#{tag_list_name}"
15
27
  end
@@ -10,7 +10,7 @@ module ActsAsTaggableOnMongoid
10
10
  end
11
11
 
12
12
  def included_ids
13
- selector = Origin::Selector.new
13
+ selector = Mongoid::Criteria::Queryable::Selector.new
14
14
  selector[:count] = tag_list.count
15
15
 
16
16
  build_ids_from(selector)
@@ -10,7 +10,7 @@ module ActsAsTaggableOnMongoid
10
10
  end
11
11
 
12
12
  def included_ids
13
- selector = Origin::Selector.new
13
+ selector = Mongoid::Criteria::Queryable::Selector.new
14
14
  selector[:count] = { "$gt" => 0 }
15
15
 
16
16
  build_ids_from(selector)
@@ -10,7 +10,7 @@ module ActsAsTaggableOnMongoid
10
10
  end
11
11
 
12
12
  def included_ids
13
- selector = Origin::Selector.new
13
+ selector = Mongoid::Criteria::Queryable::Selector.new
14
14
  selector[:count] = { "$gt" => 0 }
15
15
 
16
16
  ids = build_ids_from(selector)
@@ -10,7 +10,7 @@ module ActsAsTaggableOnMongoid
10
10
  end
11
11
 
12
12
  def included_ids
13
- selector = Origin::Selector.new
13
+ selector = Mongoid::Criteria::Queryable::Selector.new
14
14
  selector[:count] = { "$ne" => tag_list.count }
15
15
 
16
16
  AllTagsQuery.new(tag_definition, tag_list, options).included_ids -
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOnMongoid
4
+ module Taggable
5
+ # Overides of methods from Mongoid::Processing which process attributes for methods like
6
+ # `assign_attributes`, `create`, `new`, and `update_attributes`
7
+ #
8
+ # The need for this override is because the base method splits the order that methods are
9
+ # processed in to process relationships after processing other attributes.
10
+ #
11
+ # However, tag lists may rely upon values set in relationships - forcing us to process
12
+ # tag lists AFTER relationships have been processed - preventing the need to order attributes
13
+ # (which wouldn't help anyway because of the way process_attributes works.)
14
+ #
15
+ # ONLY taggings that have a default and which accept tagger values and which default the
16
+ # tagger based on the taggable object could be affected by other attributes set at the same time
17
+ # as the tag list. Thus only those tag values are delayed until after all other attributes are set.
18
+ module TaggerRelation
19
+ def process_attributes(attrs = nil)
20
+ update_attrs, defaulted_attrs = atom_attributes_without_defaults(attrs)
21
+
22
+ super(update_attrs)
23
+
24
+ defaulted_attrs.each do |key, value|
25
+ public_send("#{key}=", value)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def atom_attributes_without_defaults(attrs)
32
+ return_attributes = {}
33
+ defaulted_attributes = {}
34
+
35
+ sanitize_for_mass_assignment(attrs)&.each do |key, value|
36
+ tag_def = atom_tag_definition_from_type(key)
37
+
38
+ if tag_def&.tag_list_uses_default_tagger?
39
+ defaulted_attributes[key] = value
40
+ else
41
+ return_attributes[key] = value
42
+ end
43
+ end
44
+
45
+ [return_attributes, defaulted_attributes]
46
+ end
47
+
48
+ def atom_tag_definition_from_type(key)
49
+ tag_types.detect { |_type, tag_definition| tag_definition.tag_list_name == key.to_s }&.last
50
+ end
51
+ end
52
+ end
53
+ end
@@ -18,7 +18,7 @@ module ActsAsTaggableOnMongoid
18
18
  def initialize(tag_definition:, tags:, current_tags:)
19
19
  @tag_definition = tag_definition
20
20
  @tags = tags
21
- @current_tags = current_tags
21
+ @current_tags = current_tags.map(&:tag).compact
22
22
 
23
23
  @old_tags = {}
24
24
  @new_tags = {}
@@ -35,7 +35,7 @@ module ActsAsTaggableOnMongoid
35
35
  new_tags.each do |tag|
36
36
  tagging = taggable.
37
37
  public_send(tag_definition.taggings_name).
38
- new(tag_name: tag.name, context: tag_definition.tag_type, taggable: taggable, tag: tag)
38
+ new(tag_name: tag.name, context: tag_definition.tag_type, taggable: taggable, tag: tag, tagger: tag.owner)
39
39
 
40
40
  next if tagging.save
41
41
  next if ignore_tagging_error(tagging)
@@ -50,8 +50,8 @@ module ActsAsTaggableOnMongoid
50
50
 
51
51
  taggable.
52
52
  public_send(tag_definition.taggings_name).
53
- by_context(tag_definition.tag_type).
54
- where(:tag_name.in => old_tags.map(&:name)).
53
+ by_tag_type(tag_definition.tag_type).
54
+ where(:tag_id.in => old_tags.map(&:id)).
55
55
  destroy_all
56
56
  end
57
57
 
@@ -94,14 +94,16 @@ module ActsAsTaggableOnMongoid
94
94
 
95
95
  # :reek:NestedIterators
96
96
  def preserve_new_tag_list_order
97
- preserved_tags = new_tags | current_tags[first_ordered_difference..-1] & shared_tags
98
-
99
97
  # Order the array of tag objects to match the tag list
100
98
  @new_tags = tags.map do |tag|
101
- preserved_tags.find { |preserved_tag| preserved_tag.name == tag.name }
99
+ preserved_tags.detect { |preserved_tag| preserved_tag.name == tag.name && preserved_tag.owner == tag.owner }
102
100
  end.compact
103
101
  end
104
102
 
103
+ def preserved_tags
104
+ @preserved_tags ||= new_tags | current_tags[first_ordered_difference..-1] & shared_tags
105
+ end
106
+
105
107
  def first_ordered_difference
106
108
  return @first_ordered_difference if defined?(@first_ordered_difference)
107
109
 
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOnMongoid
4
+ # This module defines the class methods to be added to the Mongoid model so that
5
+ # tags can be defined on and added to a model.
6
+ #
7
+ # When a tag is added to a model, additional modules will be included to add methods that
8
+ # are needed only if a tag is actually being used.
9
+ module Tagger
10
+ extend ActiveSupport::Concern
11
+
12
+ # rubocop:disable Metrics/BlockLength
13
+
14
+ class_methods do
15
+ # Options include:
16
+ # * tags_table
17
+ # The class to use for Tags
18
+ # * taggings_table
19
+ # The class to use for Taggings
20
+
21
+ # Make a model a tagger. This allows a model to claim ownership of taggings and their tags.
22
+ #
23
+ # Example:
24
+ # class User
25
+ # acts_as_tagger
26
+ # end
27
+ def acts_as_tagger(options = {})
28
+ options = options.with_indifferent_access
29
+
30
+ add_taggings_tagger_relation(options)
31
+ add_tags_owner_relation(options)
32
+
33
+ include ActsAsTaggableOnMongoid::Tagger::TagMethods
34
+ end
35
+
36
+ private
37
+
38
+ def add_tags_owner_relation(options)
39
+ tags_table = options[:tags_table] || ActsAsTaggableOnMongoid.tags_table
40
+ table_name = tags_table.name
41
+ tags_name = "owned_#{table_name.demodulize.underscore.downcase.pluralize.to_sym}"
42
+
43
+ return if relations[tags_name.to_s]
44
+
45
+ has_many tags_name,
46
+ as: :owner,
47
+ dependent: :destroy,
48
+ class_name: table_name
49
+ end
50
+
51
+ def add_taggings_tagger_relation(options)
52
+ taggings_table = options[:taggings_table] || ActsAsTaggableOnMongoid.taggings_table
53
+ table_name = taggings_table.name
54
+ taggings_name = "owned_#{table_name.demodulize.underscore.downcase.pluralize.to_sym}"
55
+
56
+ return if relations[taggings_name.to_s]
57
+
58
+ has_many taggings_name,
59
+ as: :tagger,
60
+ dependent: :destroy,
61
+ class_name: table_name
62
+ end
63
+ end
64
+
65
+ # rubocop:enable Metrics/BlockLength
66
+ end
67
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOnMongoid
4
+ module Tagger
5
+ # A module defining Tagger methods allowing the Tagger object to tag Taggable objects
6
+ #
7
+ # This module is dynamically added to a model when the model calls acts_as_tagger
8
+
9
+ # :reek:DataClump
10
+ module TagMethods
11
+ ##
12
+ # Taggs the passed in taggable object with the tag values passed in to it.
13
+ #
14
+ # Parameters:
15
+ # taggable - the object to tag
16
+ # non-hash values - the values to use to tag taggable with
17
+ # {options}
18
+ # with: - Alternative to non-hash values. If found as an option it will replace
19
+ # any non-hash values. If not specified, any tags for this tagger will be
20
+ # removed from taggable
21
+ # on: - the tag list within taggable to be set. This will default to `:tag`
22
+ # parse: - Boolean indicating if the tags should be parsed. This will default to "true"
23
+ # parser: - Class to be used to parse the values.
24
+ # skip_save: - Do not save the taggable object with the new tagging.
25
+ def tag(taggable, *args)
26
+ options = atom_tag(taggable, *args)
27
+
28
+ taggable.save unless options[:skip_save]
29
+ end
30
+
31
+ ##
32
+ # tag, but uses `save!` instead of `save` to save the taggable model.
33
+ def tag!(taggable, *args)
34
+ options = atom_tag(taggable, *args)
35
+
36
+ taggable.save! unless options[:skip_save]
37
+ end
38
+
39
+ def self.atom_extract_tag_options(set_list)
40
+ options = set_list.extract_options!
41
+
42
+ options.assert_valid_keys :with,
43
+ :on,
44
+ :replace,
45
+ :parse,
46
+ :parser,
47
+ :skip_save
48
+
49
+ options[:parse] = options.fetch(:parse) { true } || options.key?(:parser)
50
+
51
+ options
52
+ end
53
+
54
+ private
55
+
56
+ # :reek:FeatureEnvy
57
+ def atom_tag(taggable, *args)
58
+ set_list = args.dup
59
+ options = ActsAsTaggableOnMongoid::Tagger::TagMethods.atom_extract_tag_options(set_list)
60
+ set_list = Array.wrap(options[:with]) if options.key?(:with)
61
+
62
+ tag_list = taggable.public_send("tagger_#{options.fetch(:on) { :tag }}_list", self)
63
+ list_options = options.slice(:parse, :parser)
64
+ if options[:replace]
65
+ tag_list.set(*set_list, list_options)
66
+ else
67
+ tag_list.add(*set_list, list_options)
68
+ end
69
+
70
+ options
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOnMongoid
4
+ # A hash like collection of tag lists grouped by tagger
5
+
6
+ # :reek:RepeatedConditional
7
+ class TaggerTagList
8
+ include Comparable
9
+
10
+ delegate :keys,
11
+ :values,
12
+ :length,
13
+ :delete,
14
+ :clear,
15
+ :each,
16
+ :each_with_object,
17
+ :detect,
18
+ :reject!,
19
+ :any?,
20
+ :each_value,
21
+ :map,
22
+ to: :tagger_tag_lists
23
+
24
+ attr_reader :taggable,
25
+ :tag_definition
26
+
27
+ def initialize(tag_definition, taggable)
28
+ @tag_definition = tag_definition
29
+ @taggable = taggable
30
+
31
+ @tagger_tag_lists = Hash.new { ActsAsTaggableOnMongoid::TagList.new_taggable_list(tag_definition, taggable) }
32
+ end
33
+
34
+ def compact!
35
+ reject! { |_key, value| value.blank? }
36
+ self
37
+ end
38
+
39
+ def compact
40
+ dup.compact!
41
+ end
42
+
43
+ def flatten
44
+ list = ActsAsTaggableOnMongoid::TagList.new_taggable_list(tag_definition, taggable)
45
+
46
+ each_value do |tag_list|
47
+ list.concat(tag_list)
48
+ end
49
+
50
+ list
51
+ end
52
+
53
+ def <=>(other)
54
+ compact!
55
+
56
+ if other.is_a?(ActsAsTaggableOnMongoid::TagList)
57
+ compare_to_tag_list(other)
58
+ elsif other.is_a?(ActsAsTaggableOnMongoid::TaggerTagList)
59
+ other.compact!
60
+
61
+ compare_to_tagger_tag_list(other)
62
+ else
63
+ super(other)
64
+ end
65
+ end
66
+
67
+ def [](tagger)
68
+ list = tagger_tag_lists[tagger]
69
+
70
+ list.tagger = tagger
71
+
72
+ tagger_tag_lists[tagger] = list
73
+ end
74
+
75
+ def []=(tagger, value)
76
+ tagger_list = self[tagger]
77
+
78
+ if value.is_a?(ActsAsTaggableOnMongoid::TagList)
79
+ tagger_list.set(value)
80
+ else
81
+ value = Array.wrap(value).dup
82
+ options = value.extract_options!
83
+ options[:parse] = options.fetch(:parse) { true }
84
+
85
+ value = [*value, options]
86
+
87
+ tagger_list.set(*value)
88
+ end
89
+ end
90
+
91
+ def dup
92
+ list = ActsAsTaggableOnMongoid::TaggerTagList.new(tag_definition, taggable)
93
+
94
+ each do |tagger, tag_list|
95
+ list[tagger].silent_concat(tag_list) if tag_list.present?
96
+ end
97
+
98
+ list
99
+ end
100
+
101
+ def taggable=(value)
102
+ @taggable = value
103
+
104
+ tagger_tag_lists.each_value do |tag_list|
105
+ tag_list.taggable = taggable
106
+ end
107
+ end
108
+
109
+ def notify_will_change
110
+ return unless taggable
111
+
112
+ taggable.tag_list_on_changed tag_definition
113
+ end
114
+
115
+ def blank?
116
+ tagger_tag_lists.values.all?(&:blank?)
117
+ end
118
+
119
+ private
120
+
121
+ attr_reader :tagger_tag_lists
122
+
123
+ def compare_tagger_tag_list_properties(other)
124
+ sub_compare = keys.length <=> other.keys.length
125
+ return sub_compare unless sub_compare&.zero?
126
+
127
+ taggable <=> other.taggable
128
+ end
129
+
130
+ def compare_to_tagger_tag_list(other)
131
+ sub_compare = compare_tagger_tag_list_properties(other)
132
+ return sub_compare unless sub_compare&.zero?
133
+
134
+ any? do |key, tag_list|
135
+ other_tag_list = other[key]
136
+
137
+ sub_compare = if tag_definition.preserve_tag_order
138
+ tag_list <=> other_tag_list
139
+ else
140
+ tag_list.sort <=> other_tag_list.sort
141
+ end
142
+
143
+ !sub_compare&.zero?
144
+ end
145
+
146
+ sub_compare
147
+ end
148
+
149
+ def compare_tag_list_properties(other)
150
+ sub_compare = tagger_tag_lists.length <=> 1
151
+ return sub_compare unless sub_compare&.zero?
152
+
153
+ sub_compare = keys.first <=> other.tagger
154
+ return sub_compare unless sub_compare&.zero?
155
+
156
+ taggable <=> other.taggable
157
+ end
158
+
159
+ def compare_to_tag_list(other)
160
+ sub_compare = compare_tag_list_properties(other)
161
+ return sub_compare unless sub_compare&.zero?
162
+
163
+ tagger_list = values.first
164
+ if tag_definition.preserve_tag_order
165
+ tagger_list <=> other
166
+ else
167
+ tagger_list.sort <=> other.sort
168
+ end
169
+ end
170
+ end
171
+ end