acts-as-taggable-on 3.3.0 → 3.4.4
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/.travis.yml +13 -16
- data/Appraisals +6 -6
- data/CHANGELOG.md +103 -4
- data/Gemfile +1 -0
- data/README.md +63 -2
- data/UPGRADING.md +1 -7
- data/acts-as-taggable-on.gemspec +2 -2
- data/gemfiles/activerecord_3.2.gemfile +2 -1
- data/gemfiles/activerecord_4.0.gemfile +2 -1
- data/gemfiles/activerecord_4.1.gemfile +2 -1
- data/gemfiles/{activerecord_edge.gemfile → activerecord_4.2.gemfile} +3 -2
- data/lib/acts-as-taggable-on.rb +15 -2
- data/lib/acts_as_taggable_on/default_parser.rb +79 -0
- data/lib/acts_as_taggable_on/generic_parser.rb +19 -0
- data/lib/acts_as_taggable_on/tag.rb +17 -11
- data/lib/acts_as_taggable_on/tag_list.rb +8 -4
- data/lib/acts_as_taggable_on/tag_list_parser.rb +7 -64
- data/lib/acts_as_taggable_on/taggable/cache.rb +4 -0
- data/lib/acts_as_taggable_on/taggable/collection.rb +2 -2
- data/lib/acts_as_taggable_on/taggable/core.rb +45 -32
- data/lib/acts_as_taggable_on/taggable/ownership.rb +2 -2
- data/lib/acts_as_taggable_on/taggable/related.rb +1 -1
- data/lib/acts_as_taggable_on/tagging.rb +6 -2
- data/lib/acts_as_taggable_on/utils.rb +1 -1
- data/lib/acts_as_taggable_on/version.rb +1 -1
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +16 -0
- data/spec/acts_as_taggable_on/caching_spec.rb +6 -0
- data/spec/acts_as_taggable_on/default_parser_spec.rb +47 -0
- data/spec/acts_as_taggable_on/generic_parser_spec.rb +14 -0
- data/spec/acts_as_taggable_on/related_spec.rb +9 -0
- data/spec/acts_as_taggable_on/tag_list_spec.rb +29 -0
- data/spec/acts_as_taggable_on/tag_spec.rb +35 -1
- data/spec/acts_as_taggable_on/taggable_spec.rb +2 -1
- data/spec/acts_as_taggable_on/tagger_spec.rb +13 -0
- data/spec/acts_as_taggable_on/utils_spec.rb +7 -0
- data/spec/spec_helper.rb +4 -0
- metadata +16 -17
|
@@ -7,72 +7,15 @@ module ActsAsTaggableOn
|
|
|
7
7
|
# tag_list # ["One", "Two", "Three"]
|
|
8
8
|
module TagListParser
|
|
9
9
|
class << self
|
|
10
|
+
## DEPRECATED
|
|
10
11
|
def parse(string)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
# Append the matched tag to the tag list
|
|
18
|
-
tag_list << Regexp.last_match[2]
|
|
19
|
-
# Return the matched delimiter ($3) to replace the matched items
|
|
20
|
-
''
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
string.gsub!(single_quote_pattern) {
|
|
24
|
-
# Append the matched tag ($2) to the tag list
|
|
25
|
-
tag_list << Regexp.last_match[2]
|
|
26
|
-
# Return an empty string to replace the matched items
|
|
27
|
-
''
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
# split the string by the delimiter
|
|
31
|
-
# and add to the tag_list
|
|
32
|
-
tag_list.add(string.split(Regexp.new delimiter))
|
|
33
|
-
end
|
|
12
|
+
ActiveRecord::Base.logger.warn <<WARNING
|
|
13
|
+
ActsAsTaggableOn::TagListParser.parse is deprecated \
|
|
14
|
+
and will be removed from v4.0+, use \
|
|
15
|
+
ActsAsTaggableOn::TagListParser.new instead
|
|
16
|
+
WARNING
|
|
17
|
+
DefaultParser.new(string).parse
|
|
34
18
|
end
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# private
|
|
38
|
-
def delimiter
|
|
39
|
-
# Parse the quoted tags
|
|
40
|
-
d = ActsAsTaggableOn.delimiter
|
|
41
|
-
# Separate multiple delimiters by bitwise operator
|
|
42
|
-
d = d.join('|') if d.kind_of?(Array)
|
|
43
|
-
d
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# ( # Tag start delimiter ($1)
|
|
47
|
-
# \A | # Either string start or
|
|
48
|
-
# #{delimiter} # a delimiter
|
|
49
|
-
# )
|
|
50
|
-
# \s*" # quote (") optionally preceded by whitespace
|
|
51
|
-
# (.*?) # Tag ($2)
|
|
52
|
-
# "\s* # quote (") optionally followed by whitespace
|
|
53
|
-
# (?= # Tag end delimiter (not consumed; is zero-length lookahead)
|
|
54
|
-
# #{delimiter}\s* | # Either a delimiter optionally followed by whitespace or
|
|
55
|
-
# \z # string end
|
|
56
|
-
# )
|
|
57
|
-
def double_quote_pattern
|
|
58
|
-
/(\A|#{delimiter})\s*"(.*?)"\s*(?=#{delimiter}\s*|\z)/
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# ( # Tag start delimiter ($1)
|
|
62
|
-
# \A | # Either string start or
|
|
63
|
-
# #{delimiter} # a delimiter
|
|
64
|
-
# )
|
|
65
|
-
# \s*' # quote (') optionally preceded by whitespace
|
|
66
|
-
# (.*?) # Tag ($2)
|
|
67
|
-
# '\s* # quote (') optionally followed by whitespace
|
|
68
|
-
# (?= # Tag end delimiter (not consumed; is zero-length lookahead)
|
|
69
|
-
# #{delimiter}\s* | d # Either a delimiter optionally followed by whitespace or
|
|
70
|
-
# \z # string end
|
|
71
|
-
# )
|
|
72
|
-
def single_quote_pattern
|
|
73
|
-
/(\A|#{delimiter})\s*'(.*?)'\s*(?=#{delimiter}\s*|\z)/
|
|
74
|
-
end
|
|
75
|
-
|
|
76
19
|
end
|
|
77
20
|
end
|
|
78
21
|
end
|
|
@@ -135,7 +135,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
135
135
|
table_name_pkey = "#{table_name}.#{primary_key}"
|
|
136
136
|
if ActsAsTaggableOn::Utils.using_mysql?
|
|
137
137
|
# See https://github.com/mbleigh/acts-as-taggable-on/pull/457 for details
|
|
138
|
-
scoped_ids =
|
|
138
|
+
scoped_ids = pluck(table_name_pkey)
|
|
139
139
|
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN (?)", scoped_ids)
|
|
140
140
|
else
|
|
141
141
|
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(select(table_name_pkey))})")
|
|
@@ -169,7 +169,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
module CalculationMethods
|
|
172
|
-
def count(column_name=:all)
|
|
172
|
+
def count(column_name=:all, options = {})
|
|
173
173
|
# https://github.com/rails/rails/commit/da9b5d4a8435b744fcf278fffd6d7f1e36d4a4f2
|
|
174
174
|
super
|
|
175
175
|
end
|
|
@@ -27,7 +27,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
27
27
|
dependent: :destroy,
|
|
28
28
|
class_name: 'ActsAsTaggableOn::Tagging',
|
|
29
29
|
order: taggings_order,
|
|
30
|
-
conditions:
|
|
30
|
+
conditions: {context: tags_type},
|
|
31
31
|
include: :tag
|
|
32
32
|
|
|
33
33
|
has_many_with_taggable_compatibility context_tags, through: context_taggings,
|
|
@@ -77,15 +77,15 @@ module ActsAsTaggableOn::Taggable
|
|
|
77
77
|
# * <tt>:end_at</tt> - Restrict the tags to those created before a certain time
|
|
78
78
|
#
|
|
79
79
|
# Example:
|
|
80
|
-
# User.tagged_with("awesome", "cool") # Users that are tagged with awesome and cool
|
|
81
|
-
# User.tagged_with("awesome", "cool", :exclude => true) # Users that are not tagged with awesome or cool
|
|
82
|
-
# User.tagged_with("awesome", "cool", :any => true) # Users that are tagged with awesome or cool
|
|
83
|
-
# User.tagged_with("awesome", "cool", :any => true, :order_by_matching_tag_count => true) # Sort by users who match the most tags, descending
|
|
84
|
-
# User.tagged_with("awesome", "cool", :match_all => true) # Users that are tagged with just awesome and cool
|
|
85
|
-
# User.tagged_with("awesome", "cool", :owned_by => foo ) # Users that are tagged with just awesome and cool by 'foo'
|
|
86
|
-
# 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
|
|
80
|
+
# User.tagged_with(["awesome", "cool"]) # Users that are tagged with awesome and cool
|
|
81
|
+
# User.tagged_with(["awesome", "cool"], :exclude => true) # Users that are not tagged with awesome or cool
|
|
82
|
+
# User.tagged_with(["awesome", "cool"], :any => true) # Users that are tagged with awesome or cool
|
|
83
|
+
# User.tagged_with(["awesome", "cool"], :any => true, :order_by_matching_tag_count => true) # Sort by users who match the most tags, descending
|
|
84
|
+
# User.tagged_with(["awesome", "cool"], :match_all => true) # Users that are tagged with just awesome and cool
|
|
85
|
+
# User.tagged_with(["awesome", "cool"], :owned_by => foo ) # Users that are tagged with just awesome and cool by 'foo'
|
|
86
|
+
# 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
|
|
87
87
|
def tagged_with(tags, options = {})
|
|
88
|
-
tag_list = ActsAsTaggableOn
|
|
88
|
+
tag_list = ActsAsTaggableOn.default_parser.new(tags).parse
|
|
89
89
|
options = options.dup
|
|
90
90
|
empty_result = where('1 = 0')
|
|
91
91
|
|
|
@@ -100,6 +100,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
100
100
|
context = options.delete(:on)
|
|
101
101
|
owned_by = options.delete(:owned_by)
|
|
102
102
|
alias_base_name = undecorated_table_name.gsub('.', '_')
|
|
103
|
+
# FIXME use ActiveRecord's connection quote_column_name
|
|
103
104
|
quote = ActsAsTaggableOn::Utils.using_postgresql? ? '"' : ''
|
|
104
105
|
|
|
105
106
|
if options.delete(:exclude)
|
|
@@ -117,7 +118,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
117
118
|
" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)}" +
|
|
118
119
|
" AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = #{quote_value(owned_by.id, nil)}" +
|
|
119
120
|
" AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = #{quote_value(owned_by.class.base_class.to_s, nil)}"
|
|
120
|
-
|
|
121
|
+
|
|
121
122
|
joins << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
|
122
123
|
joins << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
|
123
124
|
end
|
|
@@ -130,7 +131,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
130
131
|
ActsAsTaggableOn::Tag.named_any(tag_list)
|
|
131
132
|
end
|
|
132
133
|
|
|
133
|
-
return empty_result
|
|
134
|
+
return empty_result if tags.length == 0
|
|
134
135
|
|
|
135
136
|
# setup taggings alias so we can chain, ex: items_locations_taggings_awesome_cool_123
|
|
136
137
|
# avoid ambiguous column name
|
|
@@ -140,21 +141,22 @@ module ActsAsTaggableOn::Taggable
|
|
|
140
141
|
"#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{ActsAsTaggableOn::Utils.sha_prefix(tags.map(&:name).join('_'))}"
|
|
141
142
|
)
|
|
142
143
|
|
|
143
|
-
|
|
144
|
-
"
|
|
144
|
+
tagging_cond = "#{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
|
|
145
|
+
" WHERE #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
|
|
145
146
|
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
|
|
146
147
|
|
|
147
|
-
|
|
148
|
-
|
|
148
|
+
tagging_cond << " AND " + sanitize_sql(["#{taggings_alias}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
|
149
|
+
tagging_cond << " AND " + sanitize_sql(["#{taggings_alias}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
|
149
150
|
|
|
150
|
-
|
|
151
|
+
tagging_cond << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
|
|
151
152
|
|
|
152
153
|
# don't need to sanitize sql, map all ids and join with OR logic
|
|
153
|
-
|
|
154
|
+
tag_ids = tags.map { |t| quote_value(t.id, nil) }.join(', ')
|
|
155
|
+
tagging_cond << " AND #{taggings_alias}.tag_id in (#{tag_ids})"
|
|
154
156
|
select_clause << " #{table_name}.*" unless context and tag_types.one?
|
|
155
157
|
|
|
156
158
|
if owned_by
|
|
157
|
-
|
|
159
|
+
tagging_cond << ' AND ' +
|
|
158
160
|
sanitize_sql([
|
|
159
161
|
"#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
|
|
160
162
|
owned_by.id,
|
|
@@ -162,10 +164,9 @@ module ActsAsTaggableOn::Taggable
|
|
|
162
164
|
])
|
|
163
165
|
end
|
|
164
166
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
select_clause << group
|
|
167
|
+
conditions << "EXISTS (SELECT 1 FROM #{tagging_cond})"
|
|
168
|
+
if options.delete(:order_by_matching_tag_count)
|
|
169
|
+
order_by << "(SELECT count(*) FROM #{tagging_cond}) desc"
|
|
169
170
|
end
|
|
170
171
|
else
|
|
171
172
|
tags = ActsAsTaggableOn::Tag.named_any(tag_list)
|
|
@@ -224,11 +225,11 @@ module ActsAsTaggableOn::Taggable
|
|
|
224
225
|
query = self
|
|
225
226
|
query = self.select(select_clause.join(',')) unless select_clause.empty?
|
|
226
227
|
query.joins(joins.join(' '))
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
228
|
+
.where(conditions.join(' AND '))
|
|
229
|
+
.group(group)
|
|
230
|
+
.having(having)
|
|
231
|
+
.order(order_by.join(', '))
|
|
232
|
+
.readonly(false)
|
|
232
233
|
end
|
|
233
234
|
|
|
234
235
|
def is_taggable?
|
|
@@ -278,7 +279,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
278
279
|
if instance_variable_get(variable_name)
|
|
279
280
|
instance_variable_get(variable_name)
|
|
280
281
|
elsif cached_tag_list_on(context) && self.class.caching_tag_list_on?(context)
|
|
281
|
-
instance_variable_set(variable_name, ActsAsTaggableOn
|
|
282
|
+
instance_variable_set(variable_name, ActsAsTaggableOn.default_parser.new(cached_tag_list_on(context)).parse)
|
|
282
283
|
else
|
|
283
284
|
instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(tags_on(context).map(&:name)))
|
|
284
285
|
end
|
|
@@ -328,7 +329,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
328
329
|
variable_name = "@#{context.to_s.singularize}_list"
|
|
329
330
|
process_dirty_object(context, new_list) unless custom_contexts.include?(context.to_s)
|
|
330
331
|
|
|
331
|
-
instance_variable_set(variable_name, ActsAsTaggableOn
|
|
332
|
+
instance_variable_set(variable_name, ActsAsTaggableOn.default_parser.new(new_list).parse)
|
|
332
333
|
end
|
|
333
334
|
|
|
334
335
|
def tagging_contexts
|
|
@@ -342,13 +343,13 @@ module ActsAsTaggableOn::Taggable
|
|
|
342
343
|
if changed_attributes.include?(attrib)
|
|
343
344
|
# The attribute already has an unsaved change.
|
|
344
345
|
old = changed_attributes[attrib]
|
|
345
|
-
changed_attributes.delete(attrib) if old.to_s == value.to_s
|
|
346
|
+
@changed_attributes.delete(attrib) if old.to_s == value.to_s
|
|
346
347
|
else
|
|
347
348
|
old = tag_list_on(context)
|
|
348
349
|
if self.class.preserve_tag_order
|
|
349
|
-
changed_attributes[attrib] = old if old.to_s != value.to_s
|
|
350
|
+
@changed_attributes[attrib] = old if old.to_s != value.to_s
|
|
350
351
|
else
|
|
351
|
-
changed_attributes[attrib] = old.to_s if old.sort != ActsAsTaggableOn
|
|
352
|
+
@changed_attributes[attrib] = old.to_s if old.sort != ActsAsTaggableOn.default_parser.new(value).parse.sort
|
|
352
353
|
end
|
|
353
354
|
end
|
|
354
355
|
end
|
|
@@ -420,6 +421,18 @@ module ActsAsTaggableOn::Taggable
|
|
|
420
421
|
|
|
421
422
|
private
|
|
422
423
|
|
|
424
|
+
# Filters the tag lists from the attribute names.
|
|
425
|
+
def attributes_for_update(attribute_names)
|
|
426
|
+
tag_lists = tag_types.map {|tags_type| "#{tags_type.to_s.singularize}_list"}
|
|
427
|
+
super.delete_if {|attr| tag_lists.include? attr }
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Filters the tag lists from the attribute names.
|
|
431
|
+
def attributes_for_create(attribute_names)
|
|
432
|
+
tag_lists = tag_types.map {|tags_type| "#{tags_type.to_s.singularize}_list"}
|
|
433
|
+
super.delete_if {|attr| tag_lists.include? attr }
|
|
434
|
+
end
|
|
435
|
+
|
|
423
436
|
##
|
|
424
437
|
# Override this hook if you wish to subclass {ActsAsTaggableOn::Tag} --
|
|
425
438
|
# context is provided so that you may conditionally use a Tag subclass
|
|
@@ -63,7 +63,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
63
63
|
|
|
64
64
|
cache = cached_owned_tag_list_on(context)
|
|
65
65
|
|
|
66
|
-
cache[owner] = ActsAsTaggableOn
|
|
66
|
+
cache[owner] = ActsAsTaggableOn.default_parser.new(new_list).parse
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
def reload(*args)
|
|
@@ -82,7 +82,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
82
82
|
tags = find_or_create_tags_from_list_with_context(tag_list.uniq, context)
|
|
83
83
|
|
|
84
84
|
# Tag objects for owned tags
|
|
85
|
-
owned_tags = owner_tags_on(owner, context)
|
|
85
|
+
owned_tags = owner_tags_on(owner, context).to_a
|
|
86
86
|
|
|
87
87
|
# Tag maintenance based on whether preserving the created order of tags
|
|
88
88
|
if self.class.preserve_tag_order?
|
|
@@ -43,7 +43,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
43
43
|
def related_tags_for(context, klass, options = {})
|
|
44
44
|
tags_to_ignore = Array.wrap(options[:ignore]).map(&:to_s) || []
|
|
45
45
|
tags_to_find = tags_on(context).map { |t| t.name }.reject { |t| tags_to_ignore.include? t }
|
|
46
|
-
related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find])
|
|
46
|
+
related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, context])
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
private
|
|
@@ -11,7 +11,7 @@ module ActsAsTaggableOn
|
|
|
11
11
|
:tagger_type,
|
|
12
12
|
:tagger_id if defined?(ActiveModel::MassAssignmentSecurity)
|
|
13
13
|
|
|
14
|
-
belongs_to :tag, class_name: 'ActsAsTaggableOn::Tag'
|
|
14
|
+
belongs_to :tag, class_name: 'ActsAsTaggableOn::Tag', counter_cache: ActsAsTaggableOn.tags_counter
|
|
15
15
|
belongs_to :taggable, polymorphic: true
|
|
16
16
|
belongs_to :tagger, polymorphic: true
|
|
17
17
|
|
|
@@ -32,7 +32,11 @@ module ActsAsTaggableOn
|
|
|
32
32
|
|
|
33
33
|
def remove_unused_tags
|
|
34
34
|
if ActsAsTaggableOn.remove_unused_tags
|
|
35
|
-
|
|
35
|
+
if ActsAsTaggableOn.tags_counter
|
|
36
|
+
tag.destroy if tag.reload.taggings_count.zero?
|
|
37
|
+
else
|
|
38
|
+
tag.destroy if tag.reload.taggings.count.zero?
|
|
39
|
+
end
|
|
36
40
|
end
|
|
37
41
|
end
|
|
38
42
|
end
|
|
@@ -133,6 +133,22 @@ describe 'Acts As Taggable On' do
|
|
|
133
133
|
expect(taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to_not include(taggable1)
|
|
134
134
|
end
|
|
135
135
|
|
|
136
|
+
it 'should ensure joins to multiple taggings maintain their contexts when aliasing' do
|
|
137
|
+
taggable1 = TaggableModel.create!(name: 'Taggable 1')
|
|
138
|
+
|
|
139
|
+
taggable1.offering_list = 'one'
|
|
140
|
+
taggable1.need_list = 'two'
|
|
141
|
+
|
|
142
|
+
taggable1.save
|
|
143
|
+
|
|
144
|
+
column = TaggableModel.connection.quote_column_name("context")
|
|
145
|
+
offer_alias = TaggableModel.connection.quote_table_name("taggings")
|
|
146
|
+
need_alias = TaggableModel.connection.quote_table_name("need_taggings_taggable_models_join")
|
|
147
|
+
|
|
148
|
+
expect(TaggableModel.joins(:offerings, :needs).to_sql).to include "#{offer_alias}.#{column}"
|
|
149
|
+
expect(TaggableModel.joins(:offerings, :needs).to_sql).to include "#{need_alias}.#{column}"
|
|
150
|
+
end
|
|
151
|
+
|
|
136
152
|
end
|
|
137
153
|
|
|
138
154
|
describe 'Tagging Contexts' do
|
|
@@ -69,6 +69,12 @@ describe 'Acts As Taggable On' do
|
|
|
69
69
|
@taggable.save!
|
|
70
70
|
expect(@taggable.tag_list.sort).to eq(%w(awesome epic).sort)
|
|
71
71
|
end
|
|
72
|
+
|
|
73
|
+
it 'should clear the cache on reset_column_information' do
|
|
74
|
+
CachedModel.column_names
|
|
75
|
+
CachedModel.reset_column_information
|
|
76
|
+
expect(CachedModel.instance_variable_get(:@acts_as_taggable_on_cache_columns)).to eql(nil)
|
|
77
|
+
end
|
|
72
78
|
end
|
|
73
79
|
|
|
74
80
|
describe 'CachingWithArray' do
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe ActsAsTaggableOn::DefaultParser do
|
|
5
|
+
it '#parse should return empty array if empty array is passed' do
|
|
6
|
+
parser = ActsAsTaggableOn::DefaultParser.new([])
|
|
7
|
+
expect(parser.parse).to be_empty
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe 'Multiple Delimiter' do
|
|
11
|
+
before do
|
|
12
|
+
@old_delimiter = ActsAsTaggableOn.delimiter
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
after do
|
|
16
|
+
ActsAsTaggableOn.delimiter = @old_delimiter
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'should separate tags by delimiters' do
|
|
20
|
+
ActsAsTaggableOn.delimiter = [',', ' ', '\|']
|
|
21
|
+
parser = ActsAsTaggableOn::DefaultParser.new('cool, data|I have')
|
|
22
|
+
expect(parser.parse.to_s).to eq('cool, data, I, have')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'should escape quote' do
|
|
26
|
+
ActsAsTaggableOn.delimiter = [',', ' ', '\|']
|
|
27
|
+
parser = ActsAsTaggableOn::DefaultParser.new("'I have'|cool, data")
|
|
28
|
+
expect(parser.parse.to_s).to eq('"I have", cool, data')
|
|
29
|
+
|
|
30
|
+
parser = ActsAsTaggableOn::DefaultParser.new('"I, have"|cool, data')
|
|
31
|
+
expect(parser.parse.to_s).to eq('"I, have", cool, data')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'should work for utf8 delimiter and long delimiter' do
|
|
35
|
+
ActsAsTaggableOn.delimiter = [',', '的', '可能是']
|
|
36
|
+
parser = ActsAsTaggableOn::DefaultParser.new('我的东西可能是不见了,还好有备份')
|
|
37
|
+
expect(parser.parse.to_s).to eq('我, 东西, 不见了, 还好有备份')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'should work for multiple quoted tags' do
|
|
41
|
+
ActsAsTaggableOn.delimiter = [',']
|
|
42
|
+
parser = ActsAsTaggableOn::DefaultParser.new('"Ruby Monsters","eat Katzenzungen"')
|
|
43
|
+
expect(parser.parse.to_s).to eq('Ruby Monsters, eat Katzenzungen')
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe ActsAsTaggableOn::GenericParser do
|
|
5
|
+
it '#parse should return empty array if empty tag string is passed' do
|
|
6
|
+
tag_list = ActsAsTaggableOn::GenericParser.new('')
|
|
7
|
+
expect(tag_list.parse).to be_empty
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it '#parse should separate tags by comma' do
|
|
11
|
+
tag_list = ActsAsTaggableOn::GenericParser.new('cool,data,,I,have')
|
|
12
|
+
expect(tag_list.parse).to eq(%w(cool data I have))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -42,6 +42,15 @@ describe 'Acts As Taggable On' do
|
|
|
42
42
|
expect(taggable1.find_related_tags_for(OtherTaggableModel)).to_not include(taggable2)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
it 'should find other related objects based on tags only from particular context' do
|
|
46
|
+
taggable1 = TaggableModel.create!(name: 'Taggable 1',tag_list: 'one, two')
|
|
47
|
+
taggable2 = TaggableModel.create!(name: 'Taggable 2',tag_list: 'three, four', skill_list: 'one, two')
|
|
48
|
+
taggable3 = TaggableModel.create!(name: 'Taggable 3',tag_list: 'one, four')
|
|
49
|
+
|
|
50
|
+
expect(taggable1.find_related_tags).to include(taggable3)
|
|
51
|
+
expect(taggable1.find_related_tags).to_not include(taggable2)
|
|
52
|
+
end
|
|
53
|
+
|
|
45
54
|
|
|
46
55
|
shared_examples "a collection" do
|
|
47
56
|
it do
|
|
@@ -114,7 +114,36 @@ describe ActsAsTaggableOn::TagList do
|
|
|
114
114
|
|
|
115
115
|
ActsAsTaggableOn.force_lowercase = false
|
|
116
116
|
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
describe 'custom parser' do
|
|
120
|
+
let(:parser) { double(parse: %w(cool wicked)) }
|
|
121
|
+
let(:parser_class) { stub_const('MyParser', Class) }
|
|
122
|
+
|
|
123
|
+
it 'should use a the default parser if none is set as parameter' do
|
|
124
|
+
allow(ActsAsTaggableOn.default_parser).to receive(:new).and_return(parser)
|
|
125
|
+
ActsAsTaggableOn::TagList.new('cool, wicked', parse: true)
|
|
126
|
+
|
|
127
|
+
expect(parser).to have_received(:parse)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'should use the custom parser passed as parameter' do
|
|
131
|
+
allow(parser_class).to receive(:new).and_return(parser)
|
|
117
132
|
|
|
133
|
+
ActsAsTaggableOn::TagList.new('cool, wicked', parser: parser_class)
|
|
134
|
+
|
|
135
|
+
expect(parser).to have_received(:parse)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it 'should use the parser setted as attribute' do
|
|
139
|
+
allow(parser_class).to receive(:new).with('new, tag').and_return(parser)
|
|
140
|
+
|
|
141
|
+
tag_list = ActsAsTaggableOn::TagList.new('example')
|
|
142
|
+
tag_list.parser = parser_class
|
|
143
|
+
tag_list.add('new, tag', parse: true)
|
|
144
|
+
|
|
145
|
+
expect(parser).to have_received(:parse)
|
|
146
|
+
end
|
|
118
147
|
end
|
|
119
148
|
|
|
120
149
|
|
|
@@ -46,6 +46,14 @@ describe ActsAsTaggableOn::Tag do
|
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
describe 'named any' do
|
|
50
|
+
context 'with some special characters combinations', if: using_mysql? do
|
|
51
|
+
it 'should not raise an invalid encoding exception' do
|
|
52
|
+
expect{ActsAsTaggableOn::Tag.named_any(["holä", "hol'ä"])}.not_to raise_error
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
49
57
|
describe 'find or create by name' do
|
|
50
58
|
before(:each) do
|
|
51
59
|
@tag.name = 'awesome'
|
|
@@ -250,13 +258,19 @@ describe ActsAsTaggableOn::Tag do
|
|
|
250
258
|
end
|
|
251
259
|
end
|
|
252
260
|
|
|
253
|
-
it 'should not change
|
|
261
|
+
it 'should not change encoding' do
|
|
254
262
|
name = "\u3042"
|
|
255
263
|
original_encoding = name.encoding
|
|
256
264
|
record = ActsAsTaggableOn::Tag.find_or_create_with_like_by_name(name)
|
|
257
265
|
record.reload
|
|
258
266
|
expect(record.name.encoding).to eq(original_encoding)
|
|
259
267
|
end
|
|
268
|
+
|
|
269
|
+
context 'named any with some special characters combinations', if: using_mysql? do
|
|
270
|
+
it 'should not raise an invalid encoding exception' do
|
|
271
|
+
expect{ActsAsTaggableOn::Tag.named_any(["holä", "hol'ä"])}.not_to raise_error
|
|
272
|
+
end
|
|
273
|
+
end
|
|
260
274
|
end
|
|
261
275
|
|
|
262
276
|
describe 'name uniqeness validation' do
|
|
@@ -286,4 +300,24 @@ describe ActsAsTaggableOn::Tag do
|
|
|
286
300
|
end
|
|
287
301
|
end
|
|
288
302
|
end
|
|
303
|
+
|
|
304
|
+
describe 'popular tags' do
|
|
305
|
+
before do
|
|
306
|
+
%w(sports rails linux tennis golden_syrup).each_with_index do |t, i|
|
|
307
|
+
tag = ActsAsTaggableOn::Tag.new(name: t)
|
|
308
|
+
tag.taggings_count = i
|
|
309
|
+
tag.save!
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it 'should find the most popular tags' do
|
|
314
|
+
expect(ActsAsTaggableOn::Tag.most_used(3).first.name).to eq("golden_syrup")
|
|
315
|
+
expect(ActsAsTaggableOn::Tag.most_used(3).length).to eq(3)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
it 'should find the least popular tags' do
|
|
319
|
+
expect(ActsAsTaggableOn::Tag.least_used(3).first.name).to eq("sports")
|
|
320
|
+
expect(ActsAsTaggableOn::Tag.least_used(3).length).to eq(3)
|
|
321
|
+
end
|
|
322
|
+
end
|
|
289
323
|
end
|
|
@@ -261,7 +261,7 @@ describe 'Taggable' do
|
|
|
261
261
|
@taggable.tag_list = 'bob, charlie'
|
|
262
262
|
@taggable.save
|
|
263
263
|
|
|
264
|
-
expect(TaggableModel.
|
|
264
|
+
expect(TaggableModel.tagged_with(['bob', 'css'], :any => true).to_a).to eq([@taggable])
|
|
265
265
|
|
|
266
266
|
bob = TaggableModel.create(:name => 'Bob', :tag_list => 'ruby, rails, css')
|
|
267
267
|
frank = TaggableModel.create(:name => 'Frank', :tag_list => 'ruby, rails')
|
|
@@ -459,6 +459,7 @@ describe 'Taggable' do
|
|
|
459
459
|
|
|
460
460
|
expect(TaggableModel.tagged_with(%w(bob tricia), wild: true, any: true).to_a.sort_by { |o| o.id }).to eq([bob, frank, steve])
|
|
461
461
|
expect(TaggableModel.tagged_with(%w(bob tricia), wild: true, exclude: true).to_a).to eq([jim])
|
|
462
|
+
expect(TaggableModel.tagged_with('ji', wild: true, any: true).to_a).to eq([frank, jim])
|
|
462
463
|
end
|
|
463
464
|
end
|
|
464
465
|
|
|
@@ -137,4 +137,17 @@ describe 'Tagger' do
|
|
|
137
137
|
}).to_not change(ActsAsTaggableOn::Tagging, :count)
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
+
it 'should change tags order in ordered taggable' do
|
|
141
|
+
@ordered_taggable = OrderedTaggableModel.create name: 'hey!'
|
|
142
|
+
|
|
143
|
+
@user.tag @ordered_taggable, with: 'tag, tag1', on: :tags
|
|
144
|
+
expect(@ordered_taggable.reload.tags_from(@user)).to eq(['tag', 'tag1'])
|
|
145
|
+
|
|
146
|
+
@user.tag @ordered_taggable, with: 'tag2, tag1', on: :tags
|
|
147
|
+
expect(@ordered_taggable.reload.tags_from(@user)).to eq(['tag2', 'tag1'])
|
|
148
|
+
|
|
149
|
+
@user.tag @ordered_taggable, with: 'tag1, tag2', on: :tags
|
|
150
|
+
expect(@ordered_taggable.reload.tags_from(@user)).to eq(['tag1', 'tag2'])
|
|
151
|
+
end
|
|
152
|
+
|
|
140
153
|
end
|
|
@@ -13,4 +13,11 @@ describe ActsAsTaggableOn::Utils do
|
|
|
13
13
|
expect(ActsAsTaggableOn::Utils.like_operator).to eq('LIKE')
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
describe '#sha_prefix' do
|
|
18
|
+
it 'should return a consistent prefix for a given word' do
|
|
19
|
+
expect(ActsAsTaggableOn::Utils.sha_prefix('kittens')).to eq(ActsAsTaggableOn::Utils.sha_prefix('kittens'))
|
|
20
|
+
expect(ActsAsTaggableOn::Utils.sha_prefix('puppies')).not_to eq(ActsAsTaggableOn::Utils.sha_prefix('kittens'))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
16
23
|
end
|