acts-as-taggable-on 7.0.0 → 9.0.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/spec.yml +76 -0
- data/Appraisals +13 -13
- data/CHANGELOG.md +27 -2
- data/Gemfile +1 -0
- data/README.md +32 -7
- data/acts-as-taggable-on.gemspec +2 -2
- data/db/migrate/1_acts_as_taggable_on_migration.rb +5 -8
- data/db/migrate/2_add_missing_unique_indices.rb +6 -8
- data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +3 -6
- data/db/migrate/4_add_missing_taggable_index.rb +5 -7
- data/db/migrate/5_change_collation_for_tag_names.rb +4 -6
- data/db/migrate/6_add_missing_indexes_on_taggings.rb +15 -13
- data/db/migrate/7_add_tenant_to_taggings.rb +13 -0
- data/docker-compose.yml +15 -0
- data/gemfiles/activerecord_6.0.gemfile +5 -8
- data/gemfiles/activerecord_6.1.gemfile +3 -8
- data/gemfiles/{activerecord_5.2.gemfile → activerecord_7.0.gemfile} +6 -9
- data/lib/acts_as_taggable_on/default_parser.rb +8 -10
- data/lib/acts_as_taggable_on/engine.rb +2 -0
- data/lib/acts_as_taggable_on/generic_parser.rb +2 -0
- data/lib/acts_as_taggable_on/tag.rb +33 -27
- data/lib/acts_as_taggable_on/tag_list.rb +8 -11
- data/lib/acts_as_taggable_on/taggable/cache.rb +64 -62
- data/lib/acts_as_taggable_on/taggable/collection.rb +178 -142
- data/lib/acts_as_taggable_on/taggable/core.rb +250 -236
- data/lib/acts_as_taggable_on/taggable/ownership.rb +110 -98
- data/lib/acts_as_taggable_on/taggable/related.rb +60 -47
- data/lib/acts_as_taggable_on/taggable/tag_list_type.rb +6 -2
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb +110 -106
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +57 -53
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb +63 -60
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +54 -46
- data/lib/acts_as_taggable_on/taggable/tagged_with_query.rb +14 -8
- data/lib/acts_as_taggable_on/taggable.rb +30 -12
- data/lib/acts_as_taggable_on/tagger.rb +9 -5
- data/lib/acts_as_taggable_on/tagging.rb +8 -4
- data/lib/acts_as_taggable_on/tags_helper.rb +3 -1
- data/lib/acts_as_taggable_on/utils.rb +4 -2
- data/lib/acts_as_taggable_on/version.rb +3 -1
- data/spec/acts_as_taggable_on/tag_spec.rb +16 -1
- data/spec/acts_as_taggable_on/taggable_spec.rb +6 -2
- data/spec/acts_as_taggable_on/tagging_spec.rb +26 -0
- data/spec/internal/app/models/taggable_model.rb +2 -0
- data/spec/internal/config/database.yml.sample +4 -8
- data/spec/internal/db/schema.rb +3 -0
- data/spec/support/database.rb +36 -26
- metadata +13 -22
- data/.travis.yml +0 -49
- data/UPGRADING.md +0 -8
- data/gemfiles/activerecord_5.0.gemfile +0 -21
- data/gemfiles/activerecord_5.1.gemfile +0 -21
@@ -1,48 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'tagged_with_query'
|
2
4
|
require_relative 'tag_list_type'
|
3
5
|
|
4
|
-
module ActsAsTaggableOn
|
5
|
-
module
|
6
|
+
module ActsAsTaggableOn
|
7
|
+
module Taggable
|
8
|
+
module Core
|
9
|
+
def self.included(base)
|
10
|
+
base.extend ActsAsTaggableOn::Taggable::Core::ClassMethods
|
6
11
|
|
7
|
-
|
8
|
-
|
12
|
+
base.class_eval do
|
13
|
+
attr_writer :custom_contexts
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
after_save :save_tags
|
13
|
-
end
|
15
|
+
after_save :save_tags
|
16
|
+
end
|
14
17
|
|
15
|
-
|
16
|
-
|
18
|
+
base.initialize_acts_as_taggable_on_core
|
19
|
+
end
|
17
20
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
21
|
+
module ClassMethods
|
22
|
+
def initialize_acts_as_taggable_on_core
|
23
|
+
include taggable_mixin
|
24
|
+
tag_types.map(&:to_s).each do |tags_type|
|
25
|
+
tag_type = tags_type.to_s.singularize
|
26
|
+
context_taggings = "#{tag_type}_taggings".to_sym
|
27
|
+
context_tags = tags_type.to_sym
|
28
|
+
taggings_order = (preserve_tag_order? ? "#{ActsAsTaggableOn::Tagging.table_name}.id" : [])
|
29
|
+
|
30
|
+
class_eval do
|
31
|
+
# when preserving tag order, include order option so that for a 'tags' context
|
32
|
+
# the associations tag_taggings & tags are always returned in created order
|
33
|
+
has_many context_taggings, -> { includes(:tag).order(taggings_order).where(context: tags_type) },
|
34
|
+
as: :taggable,
|
35
|
+
class_name: 'ActsAsTaggableOn::Tagging',
|
36
|
+
dependent: :destroy,
|
37
|
+
after_add: :dirtify_tag_list,
|
38
|
+
after_remove: :dirtify_tag_list
|
39
|
+
|
40
|
+
has_many context_tags, -> { order(taggings_order) },
|
41
|
+
class_name: 'ActsAsTaggableOn::Tag',
|
42
|
+
through: context_taggings,
|
43
|
+
source: :tag
|
44
|
+
|
45
|
+
attribute "#{tags_type.singularize}_list".to_sym, ActsAsTaggableOn::Taggable::TagListType.new
|
46
|
+
end
|
44
47
|
|
45
|
-
|
48
|
+
taggable_mixin.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
46
49
|
def #{tag_type}_list
|
47
50
|
tag_list_on('#{tags_type}')
|
48
51
|
end
|
@@ -51,12 +54,8 @@ module ActsAsTaggableOn::Taggable
|
|
51
54
|
parsed_new_list = ActsAsTaggableOn.default_parser.new(new_tags).parse
|
52
55
|
|
53
56
|
if self.class.preserve_tag_order? || (parsed_new_list.sort != #{tag_type}_list.sort)
|
54
|
-
|
55
|
-
|
56
|
-
else
|
57
|
-
unless #{tag_type}_list_changed?
|
58
|
-
@attributes["#{tag_type}_list"] = ActiveModel::Attribute.from_user("#{tag_type}_list", #{tag_type}_list, ActsAsTaggableOn::Taggable::TagListType.new)
|
59
|
-
end
|
57
|
+
unless #{tag_type}_list_changed?
|
58
|
+
@attributes["#{tag_type}_list"] = ActiveModel::Attribute.from_user("#{tag_type}_list", #{tag_type}_list, ActsAsTaggableOn::Taggable::TagListType.new)
|
60
59
|
end
|
61
60
|
write_attribute("#{tag_type}_list", parsed_new_list)
|
62
61
|
end
|
@@ -72,253 +71,268 @@ module ActsAsTaggableOn::Taggable
|
|
72
71
|
def dirtify_tag_list(tagging)
|
73
72
|
attribute_will_change! tagging.context.singularize+"_list"
|
74
73
|
end
|
75
|
-
|
74
|
+
RUBY
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def taggable_on(preserve_tag_order, *tag_types)
|
79
|
+
super(preserve_tag_order, *tag_types)
|
80
|
+
initialize_acts_as_taggable_on_core
|
76
81
|
end
|
77
|
-
end
|
78
82
|
|
79
|
-
|
80
|
-
|
81
|
-
|
83
|
+
# all column names are necessary for PostgreSQL group clause
|
84
|
+
def grouped_column_names_for(object)
|
85
|
+
object.column_names.map { |column| "#{object.table_name}.#{column}" }.join(', ')
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Return a scope of objects that are tagged with the specified tags.
|
90
|
+
#
|
91
|
+
# @param tags The tags that we want to query for
|
92
|
+
# @param [Hash] options A hash of options to alter you query:
|
93
|
+
# * <tt>:exclude</tt> - if set to true, return objects that are *NOT* tagged with the specified tags
|
94
|
+
# * <tt>:any</tt> - if set to true, return objects that are tagged with *ANY* of the specified tags
|
95
|
+
# * <tt>:order_by_matching_tag_count</tt> - if set to true and used with :any, sort by objects matching the most tags, descending
|
96
|
+
# * <tt>:match_all</tt> - if set to true, return objects that are *ONLY* tagged with the specified tags
|
97
|
+
# * <tt>:owned_by</tt> - return objects that are *ONLY* owned by the owner
|
98
|
+
# * <tt>:start_at</tt> - Restrict the tags to those created after a certain time
|
99
|
+
# * <tt>:end_at</tt> - Restrict the tags to those created before a certain time
|
100
|
+
#
|
101
|
+
# Example:
|
102
|
+
# User.tagged_with(["awesome", "cool"]) # Users that are tagged with awesome and cool
|
103
|
+
# User.tagged_with(["awesome", "cool"], :exclude => true) # Users that are not tagged with awesome or cool
|
104
|
+
# User.tagged_with(["awesome", "cool"], :any => true) # Users that are tagged with awesome or cool
|
105
|
+
# User.tagged_with(["awesome", "cool"], :any => true, :order_by_matching_tag_count => true) # Sort by users who match the most tags, descending
|
106
|
+
# User.tagged_with(["awesome", "cool"], :match_all => true) # Users that are tagged with just awesome and cool
|
107
|
+
# User.tagged_with(["awesome", "cool"], :owned_by => foo ) # Users that are tagged with just awesome and cool by 'foo'
|
108
|
+
# User.tagged_with(["awesome", "cool"], :owned_by => foo, :start_at => Date.today ) # Users that are tagged with just awesome, cool by 'foo' and starting today
|
109
|
+
def tagged_with(tags, options = {})
|
110
|
+
tag_list = ActsAsTaggableOn.default_parser.new(tags).parse
|
111
|
+
options = options.dup
|
112
|
+
|
113
|
+
return none if tag_list.empty?
|
114
|
+
|
115
|
+
::ActsAsTaggableOn::Taggable::TaggedWithQuery.build(self, ActsAsTaggableOn::Tag, ActsAsTaggableOn::Tagging,
|
116
|
+
tag_list, options)
|
117
|
+
end
|
118
|
+
|
119
|
+
def is_taggable?
|
120
|
+
true
|
121
|
+
end
|
122
|
+
|
123
|
+
def taggable_mixin
|
124
|
+
@taggable_mixin ||= Module.new
|
125
|
+
end
|
82
126
|
end
|
83
127
|
|
84
128
|
# all column names are necessary for PostgreSQL group clause
|
85
129
|
def grouped_column_names_for(object)
|
86
|
-
|
130
|
+
self.class.grouped_column_names_for(object)
|
87
131
|
end
|
88
132
|
|
89
|
-
|
90
|
-
|
91
|
-
#
|
92
|
-
# @param tags The tags that we want to query for
|
93
|
-
# @param [Hash] options A hash of options to alter you query:
|
94
|
-
# * <tt>:exclude</tt> - if set to true, return objects that are *NOT* tagged with the specified tags
|
95
|
-
# * <tt>:any</tt> - if set to true, return objects that are tagged with *ANY* of the specified tags
|
96
|
-
# * <tt>:order_by_matching_tag_count</tt> - if set to true and used with :any, sort by objects matching the most tags, descending
|
97
|
-
# * <tt>:match_all</tt> - if set to true, return objects that are *ONLY* tagged with the specified tags
|
98
|
-
# * <tt>:owned_by</tt> - return objects that are *ONLY* owned by the owner
|
99
|
-
# * <tt>:start_at</tt> - Restrict the tags to those created after a certain time
|
100
|
-
# * <tt>:end_at</tt> - Restrict the tags to those created before a certain time
|
101
|
-
#
|
102
|
-
# Example:
|
103
|
-
# User.tagged_with(["awesome", "cool"]) # Users that are tagged with awesome and cool
|
104
|
-
# User.tagged_with(["awesome", "cool"], :exclude => true) # Users that are not tagged with awesome or cool
|
105
|
-
# User.tagged_with(["awesome", "cool"], :any => true) # Users that are tagged with awesome or cool
|
106
|
-
# User.tagged_with(["awesome", "cool"], :any => true, :order_by_matching_tag_count => true) # Sort by users who match the most tags, descending
|
107
|
-
# User.tagged_with(["awesome", "cool"], :match_all => true) # Users that are tagged with just awesome and cool
|
108
|
-
# User.tagged_with(["awesome", "cool"], :owned_by => foo ) # Users that are tagged with just awesome and cool by 'foo'
|
109
|
-
# User.tagged_with(["awesome", "cool"], :owned_by => foo, :start_at => Date.today ) # Users that are tagged with just awesome, cool by 'foo' and starting today
|
110
|
-
def tagged_with(tags, options = {})
|
111
|
-
tag_list = ActsAsTaggableOn.default_parser.new(tags).parse
|
112
|
-
options = options.dup
|
113
|
-
|
114
|
-
return none if tag_list.empty?
|
115
|
-
|
116
|
-
::ActsAsTaggableOn::Taggable::TaggedWithQuery.build(self, ActsAsTaggableOn::Tag, ActsAsTaggableOn::Tagging, tag_list, options)
|
133
|
+
def custom_contexts
|
134
|
+
@custom_contexts ||= taggings.map(&:context).uniq
|
117
135
|
end
|
118
136
|
|
119
137
|
def is_taggable?
|
120
|
-
|
138
|
+
self.class.is_taggable?
|
121
139
|
end
|
122
140
|
|
123
|
-
def
|
124
|
-
|
141
|
+
def add_custom_context(value)
|
142
|
+
unless custom_contexts.include?(value.to_s) || self.class.tag_types.map(&:to_s).include?(value.to_s)
|
143
|
+
custom_contexts << value.to_s
|
144
|
+
end
|
125
145
|
end
|
126
|
-
end
|
127
|
-
|
128
|
-
# all column names are necessary for PostgreSQL group clause
|
129
|
-
def grouped_column_names_for(object)
|
130
|
-
self.class.grouped_column_names_for(object)
|
131
|
-
end
|
132
146
|
|
133
|
-
|
134
|
-
|
135
|
-
|
147
|
+
def cached_tag_list_on(context)
|
148
|
+
self["cached_#{context.to_s.singularize}_list"]
|
149
|
+
end
|
136
150
|
|
137
|
-
|
138
|
-
|
139
|
-
|
151
|
+
def tag_list_cache_set_on(context)
|
152
|
+
variable_name = "@#{context.to_s.singularize}_list"
|
153
|
+
instance_variable_defined?(variable_name) && instance_variable_get(variable_name)
|
154
|
+
end
|
140
155
|
|
141
|
-
|
142
|
-
|
143
|
-
|
156
|
+
def tag_list_cache_on(context)
|
157
|
+
variable_name = "@#{context.to_s.singularize}_list"
|
158
|
+
if instance_variable_get(variable_name)
|
159
|
+
instance_variable_get(variable_name)
|
160
|
+
elsif cached_tag_list_on(context) && ensure_included_cache_methods! && self.class.caching_tag_list_on?(context)
|
161
|
+
instance_variable_set(variable_name, ActsAsTaggableOn.default_parser.new(cached_tag_list_on(context)).parse)
|
162
|
+
else
|
163
|
+
instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(tags_on(context).map(&:name)))
|
164
|
+
end
|
165
|
+
end
|
144
166
|
|
145
|
-
|
146
|
-
|
147
|
-
|
167
|
+
def tag_list_on(context)
|
168
|
+
add_custom_context(context)
|
169
|
+
tag_list_cache_on(context)
|
170
|
+
end
|
148
171
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
172
|
+
def all_tags_list_on(context)
|
173
|
+
variable_name = "@all_#{context.to_s.singularize}_list"
|
174
|
+
if instance_variable_defined?(variable_name) && instance_variable_get(variable_name)
|
175
|
+
return instance_variable_get(variable_name)
|
176
|
+
end
|
153
177
|
|
154
|
-
|
155
|
-
variable_name = "@#{context.to_s.singularize}_list"
|
156
|
-
if instance_variable_get(variable_name)
|
157
|
-
instance_variable_get(variable_name)
|
158
|
-
elsif cached_tag_list_on(context) && ensure_included_cache_methods! && self.class.caching_tag_list_on?(context)
|
159
|
-
instance_variable_set(variable_name, ActsAsTaggableOn.default_parser.new(cached_tag_list_on(context)).parse)
|
160
|
-
else
|
161
|
-
instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(tags_on(context).map(&:name)))
|
178
|
+
instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(all_tags_on(context).map(&:name)).freeze)
|
162
179
|
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def tag_list_on(context)
|
166
|
-
add_custom_context(context)
|
167
|
-
tag_list_cache_on(context)
|
168
|
-
end
|
169
180
|
|
170
|
-
|
171
|
-
|
172
|
-
|
181
|
+
##
|
182
|
+
# Returns all tags of a given context
|
183
|
+
def all_tags_on(context)
|
184
|
+
tagging_table_name = ActsAsTaggableOn::Tagging.table_name
|
173
185
|
|
174
|
-
|
175
|
-
|
186
|
+
opts = ["#{tagging_table_name}.context = ?", context.to_s]
|
187
|
+
scope = base_tags.where(opts)
|
176
188
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
189
|
+
if ActsAsTaggableOn::Utils.using_postgresql?
|
190
|
+
group_columns = grouped_column_names_for(ActsAsTaggableOn::Tag)
|
191
|
+
scope.order(Arel.sql("max(#{tagging_table_name}.created_at)")).group(group_columns)
|
192
|
+
else
|
193
|
+
scope.group("#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}")
|
194
|
+
end.to_a
|
195
|
+
end
|
181
196
|
|
182
|
-
|
183
|
-
|
197
|
+
##
|
198
|
+
# Returns all tags that are not owned of a given context
|
199
|
+
def tags_on(context)
|
200
|
+
scope = base_tags.where([
|
201
|
+
"#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id IS NULL", context.to_s
|
202
|
+
])
|
203
|
+
# when preserving tag order, return tags in created order
|
204
|
+
# if we added the order to the association this would always apply
|
205
|
+
scope = scope.order("#{ActsAsTaggableOn::Tagging.table_name}.id") if self.class.preserve_tag_order?
|
206
|
+
scope
|
207
|
+
end
|
184
208
|
|
185
|
-
|
186
|
-
|
187
|
-
scope.order(Arel.sql("max(#{tagging_table_name}.created_at)")).group(group_columns)
|
188
|
-
else
|
189
|
-
scope.group("#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}")
|
190
|
-
end.to_a
|
191
|
-
end
|
209
|
+
def set_tag_list_on(context, new_list)
|
210
|
+
add_custom_context(context)
|
192
211
|
|
193
|
-
|
194
|
-
# Returns all tags that are not owned of a given context
|
195
|
-
def tags_on(context)
|
196
|
-
scope = base_tags.where(["#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id IS NULL", context.to_s])
|
197
|
-
# when preserving tag order, return tags in created order
|
198
|
-
# if we added the order to the association this would always apply
|
199
|
-
scope = scope.order("#{ActsAsTaggableOn::Tagging.table_name}.id") if self.class.preserve_tag_order?
|
200
|
-
scope
|
201
|
-
end
|
212
|
+
variable_name = "@#{context.to_s.singularize}_list"
|
202
213
|
|
203
|
-
|
204
|
-
add_custom_context(context)
|
214
|
+
parsed_new_list = ActsAsTaggableOn.default_parser.new(new_list).parse
|
205
215
|
|
206
|
-
|
216
|
+
instance_variable_set(variable_name, parsed_new_list)
|
217
|
+
end
|
207
218
|
|
208
|
-
|
219
|
+
def tagging_contexts
|
220
|
+
self.class.tag_types.map(&:to_s) + custom_contexts
|
221
|
+
end
|
209
222
|
|
210
|
-
|
211
|
-
|
223
|
+
def taggable_tenant
|
224
|
+
public_send(self.class.tenant_column) if self.class.tenant_column
|
225
|
+
end
|
212
226
|
|
213
|
-
|
214
|
-
|
215
|
-
|
227
|
+
def reload(*args)
|
228
|
+
self.class.tag_types.each do |context|
|
229
|
+
instance_variable_set("@#{context.to_s.singularize}_list", nil)
|
230
|
+
instance_variable_set("@all_#{context.to_s.singularize}_list", nil)
|
231
|
+
end
|
216
232
|
|
217
|
-
|
218
|
-
self.class.tag_types.each do |context|
|
219
|
-
instance_variable_set("@#{context.to_s.singularize}_list", nil)
|
220
|
-
instance_variable_set("@all_#{context.to_s.singularize}_list", nil)
|
233
|
+
super(*args)
|
221
234
|
end
|
222
235
|
|
223
|
-
|
224
|
-
|
236
|
+
##
|
237
|
+
# Find existing tags or create non-existing tags
|
238
|
+
def load_tags(tag_list)
|
239
|
+
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list)
|
240
|
+
end
|
225
241
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list)
|
230
|
-
end
|
242
|
+
def save_tags
|
243
|
+
tagging_contexts.each do |context|
|
244
|
+
next unless tag_list_cache_set_on(context)
|
231
245
|
|
232
|
-
|
233
|
-
|
234
|
-
next unless tag_list_cache_set_on(context)
|
235
|
-
# List of currently assigned tag names
|
236
|
-
tag_list = tag_list_cache_on(context).uniq
|
246
|
+
# List of currently assigned tag names
|
247
|
+
tag_list = tag_list_cache_on(context).uniq
|
237
248
|
|
238
|
-
|
239
|
-
|
249
|
+
# Find existing tags or create non-existing tags:
|
250
|
+
tags = find_or_create_tags_from_list_with_context(tag_list, context)
|
240
251
|
|
241
|
-
|
242
|
-
|
252
|
+
# Tag objects for currently assigned tags
|
253
|
+
current_tags = tags_on(context)
|
243
254
|
|
244
|
-
|
245
|
-
|
246
|
-
|
255
|
+
# Tag maintenance based on whether preserving the created order of tags
|
256
|
+
old_tags = current_tags - tags
|
257
|
+
new_tags = tags - current_tags
|
258
|
+
if self.class.preserve_tag_order?
|
247
259
|
|
248
|
-
|
260
|
+
shared_tags = current_tags & tags
|
249
261
|
|
250
|
-
|
251
|
-
|
262
|
+
if shared_tags.any? && tags[0...shared_tags.size] != shared_tags
|
263
|
+
index = shared_tags.each_with_index do |_, i|
|
264
|
+
break i unless shared_tags[i] == tags[i]
|
265
|
+
end
|
252
266
|
|
253
|
-
|
254
|
-
|
255
|
-
|
267
|
+
# Update arrays of tag objects
|
268
|
+
old_tags |= current_tags[index...current_tags.size]
|
269
|
+
new_tags |= current_tags[index...current_tags.size] & shared_tags
|
256
270
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
271
|
+
# Order the array of tag objects to match the tag list
|
272
|
+
new_tags = tags.map do |t|
|
273
|
+
new_tags.find { |n| n.name.downcase == t.name.downcase }
|
274
|
+
end.compact
|
275
|
+
end
|
276
|
+
else
|
277
|
+
# Delete discarded tags and create new tags
|
261
278
|
end
|
262
|
-
else
|
263
|
-
# Delete discarded tags and create new tags
|
264
|
-
old_tags = current_tags - tags
|
265
|
-
new_tags = tags - current_tags
|
266
|
-
end
|
267
279
|
|
268
|
-
|
269
|
-
|
270
|
-
taggings.not_owned.by_context(context).where(tag_id: old_tags).destroy_all
|
271
|
-
end
|
280
|
+
# Destroy old taggings:
|
281
|
+
taggings.not_owned.by_context(context).where(tag_id: old_tags).destroy_all if old_tags.present?
|
272
282
|
|
273
|
-
|
274
|
-
|
275
|
-
|
283
|
+
# Create new taggings:
|
284
|
+
new_tags.each do |tag|
|
285
|
+
if taggable_tenant
|
286
|
+
taggings.create!(tag_id: tag.id, context: context.to_s, taggable: self, tenant: taggable_tenant)
|
287
|
+
else
|
288
|
+
taggings.create!(tag_id: tag.id, context: context.to_s, taggable: self)
|
289
|
+
end
|
290
|
+
end
|
276
291
|
end
|
277
|
-
end
|
278
292
|
|
279
|
-
|
280
|
-
|
293
|
+
true
|
294
|
+
end
|
281
295
|
|
282
|
-
|
296
|
+
private
|
283
297
|
|
284
|
-
|
285
|
-
|
286
|
-
|
298
|
+
def ensure_included_cache_methods!
|
299
|
+
self.class.columns
|
300
|
+
end
|
287
301
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
302
|
+
# Filters the tag lists from the attribute names.
|
303
|
+
def attributes_for_update(attribute_names)
|
304
|
+
tag_lists = tag_types.map { |tags_type| "#{tags_type.to_s.singularize}_list" }
|
305
|
+
super.delete_if { |attr| tag_lists.include? attr }
|
306
|
+
end
|
293
307
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
308
|
+
# Filters the tag lists from the attribute names.
|
309
|
+
def attributes_for_create(attribute_names)
|
310
|
+
tag_lists = tag_types.map { |tags_type| "#{tags_type.to_s.singularize}_list" }
|
311
|
+
super.delete_if { |attr| tag_lists.include? attr }
|
312
|
+
end
|
299
313
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
314
|
+
##
|
315
|
+
# Override this hook if you wish to subclass {ActsAsTaggableOn::Tag} --
|
316
|
+
# context is provided so that you may conditionally use a Tag subclass
|
317
|
+
# only for some contexts.
|
318
|
+
#
|
319
|
+
# @example Custom Tag class for one context
|
320
|
+
# class Company < ActiveRecord::Base
|
321
|
+
# acts_as_taggable_on :markets, :locations
|
322
|
+
#
|
323
|
+
# def find_or_create_tags_from_list_with_context(tag_list, context)
|
324
|
+
# if context.to_sym == :markets
|
325
|
+
# MarketTag.find_or_create_all_with_like_by_name(tag_list)
|
326
|
+
# else
|
327
|
+
# super
|
328
|
+
# end
|
329
|
+
# end
|
330
|
+
#
|
331
|
+
# @param [Array<String>] tag_list Tags to find or create
|
332
|
+
# @param [Symbol] context The tag context for the tag_list
|
333
|
+
def find_or_create_tags_from_list_with_context(tag_list, _context)
|
334
|
+
load_tags(tag_list)
|
335
|
+
end
|
321
336
|
end
|
322
337
|
end
|
323
338
|
end
|
324
|
-
|