acts-as-taggable-on-mongoid 6.0.1.5 → 6.1.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +2 -1
  3. data/.rubocop.yml +8 -10
  4. data/.ruby-version +1 -1
  5. data/Gemfile.lock +76 -82
  6. data/README.md +102 -28
  7. data/acts-as-taggable-on-mongoid.gemspec +3 -4
  8. data/lib/acts-as-taggable-on-mongoid.rb +9 -3
  9. data/lib/acts_as_taggable_on_mongoid/models/concerns/tag_associations.rb +12 -0
  10. data/lib/acts_as_taggable_on_mongoid/models/concerns/tag_fields.rb +4 -1
  11. data/lib/acts_as_taggable_on_mongoid/models/concerns/tag_methods.rb +21 -12
  12. data/lib/acts_as_taggable_on_mongoid/models/concerns/tag_migration.rb +46 -0
  13. data/lib/acts_as_taggable_on_mongoid/models/concerns/tag_scopes.rb +3 -2
  14. data/lib/acts_as_taggable_on_mongoid/models/concerns/tag_validations.rb +1 -1
  15. data/lib/acts_as_taggable_on_mongoid/models/concerns/tagging_associations.rb +13 -1
  16. data/lib/acts_as_taggable_on_mongoid/models/concerns/tagging_fields.rb +4 -2
  17. data/lib/acts_as_taggable_on_mongoid/models/concerns/tagging_methods.rb +2 -2
  18. data/lib/acts_as_taggable_on_mongoid/models/concerns/tagging_migration.rb +46 -0
  19. data/lib/acts_as_taggable_on_mongoid/models/concerns/tagging_scopes.rb +7 -5
  20. data/lib/acts_as_taggable_on_mongoid/models/concerns/tagging_validations.rb +2 -6
  21. data/lib/acts_as_taggable_on_mongoid/tag_list.rb +91 -4
  22. data/lib/acts_as_taggable_on_mongoid/taggable.rb +13 -1
  23. data/lib/acts_as_taggable_on_mongoid/taggable/changeable.rb +5 -4
  24. data/lib/acts_as_taggable_on_mongoid/taggable/core.rb +156 -34
  25. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition.rb +29 -50
  26. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/attributes.rb +57 -6
  27. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/changeable.rb +52 -39
  28. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/list_methods.rb +77 -0
  29. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/names.rb +12 -0
  30. data/lib/acts_as_taggable_on_mongoid/taggable/tagged_with_query/all_tags_query.rb +1 -1
  31. data/lib/acts_as_taggable_on_mongoid/taggable/tagged_with_query/any_tags_query.rb +1 -1
  32. data/lib/acts_as_taggable_on_mongoid/taggable/tagged_with_query/exclude_tags_query.rb +1 -1
  33. data/lib/acts_as_taggable_on_mongoid/taggable/tagged_with_query/match_all_tags_query.rb +1 -1
  34. data/lib/acts_as_taggable_on_mongoid/taggable/tagger_relation.rb +53 -0
  35. data/lib/acts_as_taggable_on_mongoid/taggable/utils/tag_list_diff.rb +9 -7
  36. data/lib/acts_as_taggable_on_mongoid/tagger.rb +67 -0
  37. data/lib/acts_as_taggable_on_mongoid/tagger/tag_methods.rb +74 -0
  38. data/lib/acts_as_taggable_on_mongoid/tagger_tag_list.rb +171 -0
  39. data/lib/acts_as_taggable_on_mongoid/version.rb +1 -1
  40. metadata +18 -26
@@ -11,16 +11,36 @@ module ActsAsTaggableOnMongoid
11
11
  # * tag_list_will_change!
12
12
  # * tag_list_changed_from_default?
13
13
  # * tag_list_was
14
+ # * tagger_tag_list_was
15
+ # * tag_lists_was
14
16
  # * reset_tag_list!
15
17
  # * reset_tag_list_to_default!
18
+
19
+ # :reek:FeatureEnvy
20
+ # :reek:DuplicateMethodCall
16
21
  module Changeable
22
+ def default_tagger_tag_list(taggable)
23
+ list = ActsAsTaggableOnMongoid::TaggerTagList.new(self, nil)
24
+
25
+ list_default = default.dup
26
+ list_default.taggable = taggable
27
+ list_default.tagger = list_default.tagger
28
+ list[list_default.tagger] = list_default
29
+
30
+ list.taggable = taggable
31
+
32
+ list
33
+ end
34
+
35
+ private
36
+
17
37
  def add_list_exists
18
38
  tag_definition = self
19
39
  tag_list_name = tag_definition.tag_list_name
20
40
 
21
41
  owner.taggable_mixin.module_eval do
22
42
  define_method("#{tag_list_name}?") do
23
- public_send(tag_list_name).present?
43
+ tag_list_cache_on(tag_definition).values.any?(&:present?)
24
44
  end
25
45
  end
26
46
  end
@@ -31,48 +51,29 @@ module ActsAsTaggableOnMongoid
31
51
 
32
52
  owner.taggable_mixin.module_eval do
33
53
  define_method("#{tag_list_name}_change") do
34
- return nil unless public_send("#{tag_list_name}_changed?")
35
-
36
- changed_value = public_send("#{tag_list_name}_was")
37
- current_value = public_send(tag_list_name)
38
-
39
- [changed_value, current_value] unless current_value == changed_value
54
+ get_tag_list_change(tag_definition)
40
55
  end
41
56
  end
42
57
  end
43
58
 
44
- # rubocop:disable Metrics/AbcSize
45
-
46
59
  def add_list_changed
47
60
  tag_definition = self
48
61
  tag_list_name = tag_definition.tag_list_name
49
62
 
50
63
  owner.taggable_mixin.module_eval do
51
64
  define_method("#{tag_list_name}_changed?") do
52
- return false unless changed_attributes.key?(tag_list_name)
53
-
54
- changed_value = new_record? ? tag_definition.default : changed_attributes[tag_list_name]
55
- current_value = public_send(tag_list_name)
56
-
57
- unless tag_definition.preserve_tag_order?
58
- changed_value.sort!
59
- current_value.sort!
60
- end
61
-
62
- current_value != changed_value
65
+ get_tag_list_changed(tag_definition)
63
66
  end
64
67
  end
65
68
  end
66
69
 
67
- # rubocop:enable Metrics/AbcSize
68
-
69
70
  def add_will_change
70
71
  tag_definition = self
71
72
  tag_list_name = tag_definition.tag_list_name
72
73
 
73
74
  owner.taggable_mixin.module_eval do
74
75
  define_method("#{tag_list_name}_will_change!") do
75
- attribute_wil_change! tag_list_name
76
+ attribute_will_change! tag_list_name
76
77
  end
77
78
  end
78
79
  end
@@ -83,15 +84,10 @@ module ActsAsTaggableOnMongoid
83
84
 
84
85
  owner.taggable_mixin.module_eval do
85
86
  define_method("#{tag_list_name}_changed_from_default?") do
86
- changed_value = tag_definition.default
87
- current_value = public_send(tag_list_name)
88
-
89
- unless tag_definition.preserve_tag_order?
90
- changed_value.sort!
91
- current_value.sort!
92
- end
87
+ changed_value = tag_definition.default_tagger_tag_list(self)
88
+ current_value = tag_list_cache_on(tag_definition)
93
89
 
94
- current_value != changed_value
90
+ !(changed_value <=> current_value)&.zero?
95
91
  end
96
92
  end
97
93
  end
@@ -102,13 +98,28 @@ module ActsAsTaggableOnMongoid
102
98
 
103
99
  owner.taggable_mixin.module_eval do
104
100
  define_method("#{tag_list_name}_was") do
105
- return tag_definition.default if new_record?
101
+ get_tag_list_was tag_definition
102
+ end
103
+ end
104
+ end
105
+
106
+ def add_get_lists_was
107
+ tag_definition = self
106
108
 
107
- if public_send "#{tag_list_name}_changed?"
108
- changed_attributes[tag_list_name]
109
- else
110
- public_send tag_list_name
111
- end
109
+ owner.taggable_mixin.module_eval do
110
+ define_method("#{tag_definition.tagger_tag_lists_name}_was") do
111
+ get_tag_lists_was(tag_definition)
112
+ end
113
+ end
114
+ end
115
+
116
+ def add_tagger_get_was
117
+ tag_definition = self
118
+ tag_list_name = tag_definition.tag_list_name
119
+
120
+ owner.taggable_mixin.module_eval do
121
+ define_method("tagger_#{tag_list_name}_was") do |tagger|
122
+ get_tagger_list_was(tag_definition, tagger)
112
123
  end
113
124
  end
114
125
  end
@@ -119,7 +130,9 @@ module ActsAsTaggableOnMongoid
119
130
 
120
131
  owner.taggable_mixin.module_eval do
121
132
  define_method("reset_#{tag_list_name}!") do
122
- public_send "#{tag_list_name}=", public_send("#{tag_list_name}_was")
133
+ return unless public_send("#{tag_list_name}_changed?")
134
+
135
+ tagger_tag_list_set(changed_attributes[tag_list_name].dup)
123
136
  end
124
137
  end
125
138
  end
@@ -130,7 +143,7 @@ module ActsAsTaggableOnMongoid
130
143
 
131
144
  owner.taggable_mixin.module_eval do
132
145
  define_method("reset_#{tag_list_name}_to_default!") do
133
- public_send "#{tag_list_name}=", tag_definition.default
146
+ tagger_tag_list_set(tag_definition.default_tagger_tag_list(self))
134
147
  end
135
148
  end
136
149
  end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOnMongoid
4
+ module Taggable
5
+ class TagTypeDefinition
6
+ # This module extracts out the methods used to add list methods to the taggable object
7
+ module ListMethods
8
+ def add_list_getter
9
+ tag_definition = self
10
+ tag_list_name = tag_definition.tag_list_name
11
+
12
+ owner.taggable_mixin.module_eval do
13
+ define_method(tag_list_name) do
14
+ tag_list_on tag_definition
15
+ end
16
+
17
+ alias_method "#{tag_list_name}_before_type_cast".to_sym, tag_list_name.to_sym
18
+ end
19
+ end
20
+
21
+ def add_list_setter
22
+ tag_definition = self
23
+
24
+ owner.taggable_mixin.module_eval do
25
+ define_method("#{tag_definition.tag_list_name}=") do |new_tags|
26
+ set_tag_list(tag_definition, new_tags)
27
+ end
28
+ end
29
+ end
30
+
31
+ def add_all_list_getter
32
+ tag_definition = self
33
+
34
+ owner.taggable_mixin.module_eval do
35
+ define_method(tag_definition.all_tag_list_name) do
36
+ all_tags_list_on tag_definition
37
+ end
38
+ end
39
+ end
40
+
41
+ def add_tagger_tag_list
42
+ tag_definition = self
43
+ tag_list_name = tag_definition.tagger_tag_list_name
44
+
45
+ owner.taggable_mixin.module_eval do
46
+ define_method(tag_list_name) do |owner|
47
+ return nil unless tag_definition.tagger?
48
+
49
+ tag_list_cache_on(tag_definition)[owner]
50
+ end
51
+ end
52
+ end
53
+
54
+ def add_tag_list_from
55
+ tag_definition = self
56
+
57
+ owner.taggable_mixin.module_eval do
58
+ define_method(tag_definition.from_list_name) do |owner|
59
+ public_send(tag_definition.tagger_tag_list_name, owner)
60
+ end
61
+ end
62
+ end
63
+
64
+ def add_tagger_tag_lists
65
+ tag_definition = self
66
+ tag_list_name = tag_definition.tagger_tag_lists_name
67
+
68
+ owner.taggable_mixin.module_eval do
69
+ define_method(tag_list_name) do
70
+ tag_list_cache_on(tag_definition)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -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.detect { |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