acts-as-taggable-on 8.1.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 +15 -34
- data/Appraisals +13 -13
- data/CHANGELOG.md +17 -3
- data/README.md +6 -6
- 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 +7 -10
- 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.0.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 +30 -30
- 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 +248 -244
- 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 +14 -14
- data/lib/acts_as_taggable_on/tagger.rb +9 -5
- data/lib/acts_as_taggable_on/tagging.rb +6 -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/support/database.rb +36 -26
- metadata +13 -14
- data/gemfiles/activerecord_5.1.gemfile +0 -21
- data/gemfiles/activerecord_5.2.gemfile +0 -21
@@ -1,70 +1,74 @@
|
|
1
|
-
|
2
|
-
class AnyTagsQuery < QueryBase
|
3
|
-
def build
|
4
|
-
taggable_model.select(all_fields)
|
5
|
-
.where(model_has_at_least_one_tag)
|
6
|
-
.order(Arel.sql(order_conditions))
|
7
|
-
.readonly(false)
|
8
|
-
end
|
1
|
+
# frozen_string_literal: true
|
9
2
|
|
10
|
-
|
3
|
+
module ActsAsTaggableOn
|
4
|
+
module Taggable
|
5
|
+
module TaggedWithQuery
|
6
|
+
class AnyTagsQuery < QueryBase
|
7
|
+
def build
|
8
|
+
taggable_model.select(all_fields)
|
9
|
+
.where(model_has_at_least_one_tag)
|
10
|
+
.order(Arel.sql(order_conditions))
|
11
|
+
.readonly(false)
|
12
|
+
end
|
11
13
|
|
12
|
-
|
13
|
-
taggable_arel_table[Arel.star]
|
14
|
-
end
|
14
|
+
private
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
def all_fields
|
17
|
+
taggable_arel_table[Arel.star]
|
18
|
+
end
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
.and(
|
24
|
-
tagging_arel_table[:tag_id].in(
|
25
|
-
tag_arel_table.project(tag_arel_table[:id]).where(tags_match_type)
|
26
|
-
)
|
27
|
-
)
|
20
|
+
def model_has_at_least_one_tag
|
21
|
+
tagging_arel_table.project(Arel.star).where(at_least_one_tag).exists
|
22
|
+
end
|
28
23
|
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
def at_least_one_tag
|
25
|
+
exists_contition = tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
|
26
|
+
.and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
|
27
|
+
.and(
|
28
|
+
tagging_arel_table[:tag_id].in(
|
29
|
+
tag_arel_table.project(tag_arel_table[:id]).where(tags_match_type)
|
30
|
+
)
|
31
|
+
)
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
if options[:start_at].present?
|
34
|
+
exists_contition = exists_contition.and(tagging_arel_table[:created_at].gteq(options[:start_at]))
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
if options[:end_at].present?
|
38
|
+
exists_contition = exists_contition.and(tagging_arel_table[:created_at].lteq(options[:end_at]))
|
39
|
+
end
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
41
|
+
if options[:on].present?
|
42
|
+
exists_contition = exists_contition.and(tagging_arel_table[:context].eq(options[:on]))
|
43
|
+
end
|
45
44
|
|
46
|
-
|
47
|
-
|
45
|
+
if (owner = options[:owned_by]).present?
|
46
|
+
exists_contition = exists_contition.and(tagging_arel_table[:tagger_id].eq(owner.id))
|
47
|
+
.and(tagging_arel_table[:tagger_type].eq(owner.class.base_class.to_s))
|
48
|
+
end
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
if options[:order_by_matching_tag_count].present?
|
52
|
-
order_by << "(SELECT count(*) FROM #{tagging_model.table_name} WHERE #{at_least_one_tag.to_sql}) desc"
|
53
|
-
end
|
50
|
+
exists_contition
|
51
|
+
end
|
54
52
|
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
def order_conditions
|
54
|
+
order_by = []
|
55
|
+
if options[:order_by_matching_tag_count].present?
|
56
|
+
order_by << "(SELECT count(*) FROM #{tagging_model.table_name} WHERE #{at_least_one_tag.to_sql}) desc"
|
57
|
+
end
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
order_by << options[:order] if options[:order].present?
|
60
|
+
order_by.join(', ')
|
61
|
+
end
|
62
62
|
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
def alias_name(tag_list)
|
64
|
+
alias_base_name = taggable_model.base_class.name.downcase
|
65
|
+
taggings_context = options[:on] ? "_#{options[:on]}" : ''
|
66
66
|
|
67
|
-
|
67
|
+
adjust_taggings_alias(
|
68
|
+
"#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{ActsAsTaggableOn::Utils.sha_prefix(tag_list.join('_'))}"
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
68
72
|
end
|
69
73
|
end
|
70
74
|
end
|
@@ -1,82 +1,85 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActsAsTaggableOn
|
4
|
+
module Taggable
|
5
|
+
module TaggedWithQuery
|
6
|
+
class ExcludeTagsQuery < QueryBase
|
7
|
+
def build
|
8
|
+
taggable_model.joins(owning_to_tagger)
|
9
|
+
.where(tags_not_in_list)
|
10
|
+
.having(tags_that_matches_count)
|
11
|
+
.readonly(false)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def tags_not_in_list
|
17
|
+
taggable_arel_table[:id].not_in(
|
14
18
|
tagging_arel_table
|
15
19
|
.project(tagging_arel_table[:taggable_id])
|
16
20
|
.join(tag_arel_table)
|
17
21
|
.on(
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
tagging_arel_table[:tag_id].eq(tag_arel_table[:id])
|
23
|
+
.and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
|
24
|
+
.and(tags_match_type)
|
25
|
+
)
|
22
26
|
)
|
23
27
|
|
24
|
-
|
25
|
-
|
28
|
+
# FIXME: missing time scope, this is also missing in the original implementation
|
29
|
+
end
|
26
30
|
|
31
|
+
def owning_to_tagger
|
32
|
+
return [] if options[:owned_by].blank?
|
27
33
|
|
28
|
-
|
29
|
-
return [] unless options[:owned_by].present?
|
34
|
+
owner = options[:owned_by]
|
30
35
|
|
31
|
-
|
36
|
+
arel_join = taggable_arel_table
|
37
|
+
.join(tagging_arel_table)
|
38
|
+
.on(
|
39
|
+
tagging_arel_table[:tagger_id].eq(owner.id)
|
40
|
+
.and(tagging_arel_table[:tagger_type].eq(owner.class.base_class.to_s))
|
41
|
+
.and(tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key]))
|
42
|
+
.and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
|
43
|
+
)
|
32
44
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
)
|
45
|
+
if options[:match_all].present?
|
46
|
+
arel_join = arel_join
|
47
|
+
.join(tagging_arel_table, Arel::Nodes::OuterJoin)
|
48
|
+
.on(
|
49
|
+
match_all_on_conditions
|
50
|
+
)
|
51
|
+
end
|
41
52
|
|
42
|
-
|
43
|
-
|
44
|
-
.join(tagging_arel_table, Arel::Nodes::OuterJoin)
|
45
|
-
.on(
|
46
|
-
match_all_on_conditions
|
47
|
-
)
|
48
|
-
end
|
53
|
+
arel_join.join_sources
|
54
|
+
end
|
49
55
|
|
50
|
-
|
51
|
-
|
56
|
+
def match_all_on_conditions
|
57
|
+
on_condition = tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
|
58
|
+
.and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
|
52
59
|
|
53
|
-
|
54
|
-
|
55
|
-
|
60
|
+
if options[:start_at].present?
|
61
|
+
on_condition = on_condition.and(tagging_arel_table[:created_at].gteq(options[:start_at]))
|
62
|
+
end
|
56
63
|
|
57
|
-
|
58
|
-
|
59
|
-
|
64
|
+
if options[:end_at].present?
|
65
|
+
on_condition = on_condition.and(tagging_arel_table[:created_at].lteq(options[:end_at]))
|
66
|
+
end
|
60
67
|
|
61
|
-
|
62
|
-
on_condition = on_condition.and(tagging_arel_table[:created_at].lteq(options[:end_at]))
|
63
|
-
end
|
68
|
+
on_condition = on_condition.and(tagging_arel_table[:context].eq(options[:on])) if options[:on].present?
|
64
69
|
|
65
|
-
|
66
|
-
|
67
|
-
end
|
70
|
+
on_condition
|
71
|
+
end
|
68
72
|
|
69
|
-
|
70
|
-
|
73
|
+
def tags_that_matches_count
|
74
|
+
return [] if options[:match_all].blank?
|
71
75
|
|
72
|
-
|
73
|
-
return [] unless options[:match_all].present?
|
76
|
+
taggable_model.find_by_sql(tag_arel_table.project(Arel.star.count).where(tags_match_type).to_sql)
|
74
77
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
78
|
+
tagging_arel_table[:taggable_id].count.eq(
|
79
|
+
tag_arel_table.project(Arel.star.count).where(tags_match_type)
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
80
83
|
end
|
81
84
|
end
|
82
85
|
end
|
@@ -1,61 +1,69 @@
|
|
1
|
-
|
2
|
-
class QueryBase
|
3
|
-
def initialize(taggable_model, tag_model, tagging_model, tag_list, options)
|
4
|
-
@taggable_model = taggable_model
|
5
|
-
@tag_model = tag_model
|
6
|
-
@tagging_model = tagging_model
|
7
|
-
@tag_list = tag_list
|
8
|
-
@options = options
|
9
|
-
end
|
1
|
+
# frozen_string_literal: true
|
10
2
|
|
11
|
-
|
3
|
+
module ActsAsTaggableOn
|
4
|
+
module Taggable
|
5
|
+
module TaggedWithQuery
|
6
|
+
class QueryBase
|
7
|
+
def initialize(taggable_model, tag_model, tagging_model, tag_list, options)
|
8
|
+
@taggable_model = taggable_model
|
9
|
+
@tag_model = tag_model
|
10
|
+
@tagging_model = tagging_model
|
11
|
+
@tag_list = tag_list
|
12
|
+
@options = options
|
13
|
+
end
|
12
14
|
|
13
|
-
|
15
|
+
private
|
14
16
|
|
15
|
-
|
16
|
-
@taggable_arel_table ||= taggable_model.arel_table
|
17
|
-
end
|
17
|
+
attr_reader :taggable_model, :tag_model, :tagging_model, :tag_list, :options
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
def taggable_arel_table
|
20
|
+
@taggable_arel_table ||= taggable_model.arel_table
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
def tag_arel_table
|
24
|
+
@tag_arel_table ||= tag_model.arel_table
|
25
|
+
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
def tagging_arel_table
|
28
|
+
@tagging_arel_table ||= tagging_model.arel_table
|
29
|
+
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
matches_attribute.matches(escaped_tag(tag), "!", ActsAsTaggableOn.strict_case_match)
|
35
|
-
end
|
36
|
-
end
|
31
|
+
def tag_match_type(tag)
|
32
|
+
matches_attribute = tag_arel_table[:name]
|
33
|
+
matches_attribute = matches_attribute.lower unless ActsAsTaggableOn.strict_case_match
|
37
34
|
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
if options[:wild].present?
|
36
|
+
matches_attribute.matches("%#{escaped_tag(tag)}%", '!', ActsAsTaggableOn.strict_case_match)
|
37
|
+
else
|
38
|
+
matches_attribute.matches(escaped_tag(tag), '!', ActsAsTaggableOn.strict_case_match)
|
39
|
+
end
|
40
|
+
end
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
matches_attribute.matches_any(tag_list.map{|tag| "#{escaped_tag(tag)}"}, "!", ActsAsTaggableOn.strict_case_match)
|
46
|
-
end
|
47
|
-
end
|
42
|
+
def tags_match_type
|
43
|
+
matches_attribute = tag_arel_table[:name]
|
44
|
+
matches_attribute = matches_attribute.lower unless ActsAsTaggableOn.strict_case_match
|
48
45
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
46
|
+
if options[:wild].present?
|
47
|
+
matches_attribute.matches_any(tag_list.map do |tag|
|
48
|
+
"%#{escaped_tag(tag)}%"
|
49
|
+
end, '!', ActsAsTaggableOn.strict_case_match)
|
50
|
+
else
|
51
|
+
matches_attribute.matches_any(tag_list.map do |tag|
|
52
|
+
escaped_tag(tag).to_s
|
53
|
+
end, '!', ActsAsTaggableOn.strict_case_match)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def escaped_tag(tag)
|
58
|
+
tag = tag.downcase unless ActsAsTaggableOn.strict_case_match
|
59
|
+
ActsAsTaggableOn::Utils.escape_like(tag)
|
60
|
+
end
|
53
61
|
|
54
|
-
|
55
|
-
|
56
|
-
|
62
|
+
def adjust_taggings_alias(taggings_alias)
|
63
|
+
taggings_alias = "taggings_alias_#{Digest::SHA1.hexdigest(taggings_alias)}" if taggings_alias.size > 75
|
64
|
+
taggings_alias
|
65
|
+
end
|
57
66
|
end
|
58
|
-
taggings_alias
|
59
67
|
end
|
60
68
|
end
|
61
69
|
end
|
@@ -1,16 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'tagged_with_query/query_base'
|
2
4
|
require_relative 'tagged_with_query/exclude_tags_query'
|
3
5
|
require_relative 'tagged_with_query/any_tags_query'
|
4
6
|
require_relative 'tagged_with_query/all_tags_query'
|
5
7
|
|
6
|
-
module ActsAsTaggableOn
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
module ActsAsTaggableOn
|
9
|
+
module Taggable
|
10
|
+
module TaggedWithQuery
|
11
|
+
def self.build(taggable_model, tag_model, tagging_model, tag_list, options)
|
12
|
+
if options[:exclude].present?
|
13
|
+
ExcludeTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
|
14
|
+
elsif options[:any].present?
|
15
|
+
AnyTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
|
16
|
+
else
|
17
|
+
AllTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
|
18
|
+
end
|
19
|
+
end
|
14
20
|
end
|
15
21
|
end
|
16
22
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsTaggableOn
|
2
4
|
module Taggable
|
3
|
-
|
4
5
|
def taggable?
|
5
6
|
false
|
6
7
|
end
|
@@ -56,11 +57,10 @@ module ActsAsTaggableOn
|
|
56
57
|
|
57
58
|
def acts_as_taggable_tenant(tenant)
|
58
59
|
if taggable?
|
59
|
-
self.tenant_column = tenant
|
60
60
|
else
|
61
61
|
class_attribute :tenant_column
|
62
|
-
self.tenant_column = tenant
|
63
62
|
end
|
63
|
+
self.tenant_column = tenant
|
64
64
|
|
65
65
|
# each of these add context-specific methods and must be
|
66
66
|
# called on each call of taggable_on
|
@@ -73,17 +73,17 @@ module ActsAsTaggableOn
|
|
73
73
|
|
74
74
|
private
|
75
75
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
76
|
+
# Make a model taggable on specified contexts
|
77
|
+
# and optionally preserves the order in which tags are created
|
78
|
+
#
|
79
|
+
# Separate methods used above for backwards compatibility
|
80
|
+
# so that the original acts_as_taggable_on method is unaffected
|
81
|
+
# as it's not possible to add another argument to the method
|
82
|
+
# without the tag_types being enclosed in square brackets
|
83
|
+
#
|
84
|
+
# NB: method overridden in core module in order to create tag type
|
85
|
+
# associations and methods after this logic has executed
|
86
|
+
#
|
87
87
|
def taggable_on(preserve_tag_order, *tag_types)
|
88
88
|
tag_types = tag_types.to_a.flatten.compact.map(&:to_sym)
|
89
89
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsTaggableOn
|
2
4
|
module Tagger
|
3
5
|
def self.included(base)
|
@@ -13,7 +15,7 @@ module ActsAsTaggableOn
|
|
13
15
|
# class User < ActiveRecord::Base
|
14
16
|
# acts_as_tagger
|
15
17
|
# end
|
16
|
-
def acts_as_tagger(opts={})
|
18
|
+
def acts_as_tagger(opts = {})
|
17
19
|
class_eval do
|
18
20
|
owned_taggings_scope = opts.delete(:scope)
|
19
21
|
|
@@ -54,14 +56,16 @@ module ActsAsTaggableOn
|
|
54
56
|
#
|
55
57
|
# Example:
|
56
58
|
# @user.tag(@photo, :with => "paris, normandy", :on => :locations)
|
57
|
-
def tag(taggable, opts={})
|
59
|
+
def tag(taggable, opts = {})
|
58
60
|
opts.reverse_merge!(force: true)
|
59
61
|
skip_save = opts.delete(:skip_save)
|
60
62
|
return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
|
61
63
|
|
62
|
-
|
63
|
-
|
64
|
-
|
64
|
+
raise 'You need to specify a tag context using :on' unless opts.key?(:on)
|
65
|
+
raise 'You need to specify some tags using :with' unless opts.key?(:with)
|
66
|
+
unless opts[:force] || taggable.tag_types.include?(opts[:on])
|
67
|
+
raise "No context :#{opts[:on]} defined in #{taggable.class}"
|
68
|
+
end
|
65
69
|
|
66
70
|
taggable.set_owner_tag_list_on(self, opts[:on].to_s, opts[:with])
|
67
71
|
taggable.save unless skip_save
|
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsTaggableOn
|
2
|
-
class Tagging < ::ActiveRecord::Base
|
4
|
+
class Tagging < ::ActiveRecord::Base # :nodoc:
|
3
5
|
self.table_name = ActsAsTaggableOn.taggings_table
|
4
6
|
|
5
7
|
DEFAULT_CONTEXT = 'tags'
|
@@ -19,7 +21,7 @@ module ActsAsTaggableOn
|
|
19
21
|
validates_presence_of :context
|
20
22
|
validates_presence_of :tag_id
|
21
23
|
|
22
|
-
validates_uniqueness_of :tag_id, scope: [
|
24
|
+
validates_uniqueness_of :tag_id, scope: %i[taggable_type taggable_id context tagger_id tagger_type]
|
23
25
|
|
24
26
|
after_destroy :remove_unused_tags
|
25
27
|
|
@@ -29,8 +31,8 @@ module ActsAsTaggableOn
|
|
29
31
|
if ActsAsTaggableOn.remove_unused_tags
|
30
32
|
if ActsAsTaggableOn.tags_counter
|
31
33
|
tag.destroy if tag.reload.taggings_count.zero?
|
32
|
-
|
33
|
-
tag.destroy
|
34
|
+
elsif tag.reload.taggings.none?
|
35
|
+
tag.destroy
|
34
36
|
end
|
35
37
|
end
|
36
38
|
end
|
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsTaggableOn
|
2
4
|
module TagsHelper
|
3
5
|
# See the wiki for an example using tag_cloud.
|
4
6
|
def tag_cloud(tags, classes)
|
5
7
|
return [] if tags.empty?
|
6
8
|
|
7
|
-
max_count = tags.
|
9
|
+
max_count = tags.max_by(&:taggings_count).taggings_count.to_f
|
8
10
|
|
9
11
|
tags.each do |tag|
|
10
12
|
index = ((tag.taggings_count / max_count) * (classes.size - 1))
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This module is deprecated and will be removed in the incoming versions
|
2
4
|
|
3
5
|
module ActsAsTaggableOn
|
@@ -9,7 +11,7 @@ module ActsAsTaggableOn
|
|
9
11
|
end
|
10
12
|
|
11
13
|
def using_postgresql?
|
12
|
-
connection && connection.adapter_name
|
14
|
+
connection && %w[PostgreSQL PostGIS].include?(connection.adapter_name)
|
13
15
|
end
|
14
16
|
|
15
17
|
def using_mysql?
|
@@ -30,7 +32,7 @@ module ActsAsTaggableOn
|
|
30
32
|
|
31
33
|
# escape _ and % characters in strings, since these are wildcards in SQL.
|
32
34
|
def escape_like(str)
|
33
|
-
str.gsub(/[!%_]/) { |x|
|
35
|
+
str.gsub(/[!%_]/) { |x| "!#{x}" }
|
34
36
|
end
|
35
37
|
end
|
36
38
|
end
|
data/spec/support/database.rb
CHANGED
@@ -1,36 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# set adapter to use, default is sqlite3
|
2
4
|
# to use an alternative adapter run => rake spec DB='postgresql'
|
3
5
|
db_name = ENV['DB'] || 'sqlite3'
|
4
|
-
database_yml = File.expand_path('
|
6
|
+
database_yml = File.expand_path('../internal/config/database.yml', __dir__)
|
5
7
|
|
6
|
-
|
8
|
+
unless File.exist?(database_yml)
|
9
|
+
raise "Please create #{database_yml} first to configure your database. Take a look at: #{database_yml}.sample"
|
10
|
+
end
|
7
11
|
|
8
|
-
|
12
|
+
ActiveRecord::Base.configurations = YAML.load_file(database_yml)
|
13
|
+
ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), '../debug.log'))
|
14
|
+
ActiveRecord::Base.logger.level = ENV['CI'] ? ::Logger::ERROR : ::Logger::DEBUG
|
15
|
+
ActiveRecord::Migration.verbose = false
|
16
|
+
if ActiveRecord.version >= Gem::Version.new('7.0.0.alpha2')
|
17
|
+
ActiveRecord.default_timezone = :utc
|
18
|
+
else
|
9
19
|
ActiveRecord::Base.default_timezone = :utc
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
ActiveRecord::Base.establish_connection(db_name.to_sym)
|
17
|
-
ActiveRecord::Base.connection
|
18
|
-
rescue
|
19
|
-
case db_name
|
20
|
-
when /mysql/
|
21
|
-
ActiveRecord::Base.establish_connection(config.merge('database' => nil))
|
22
|
-
ActiveRecord::Base.connection.create_database(config['database'], {charset: 'utf8', collation: 'utf8_unicode_ci'})
|
23
|
-
when 'postgresql'
|
24
|
-
ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
|
25
|
-
ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => 'utf8'))
|
26
|
-
end
|
20
|
+
end
|
21
|
+
config = if ActiveRecord.version >= Gem::Version.new('6.1.0')
|
22
|
+
ActiveRecord::Base.configurations.configs_for(env_name: db_name)
|
23
|
+
else
|
24
|
+
ActiveSupport::HashWithIndifferentAccess.new(ActiveRecord::Base.configurations[db_name])
|
25
|
+
end
|
27
26
|
|
28
|
-
|
27
|
+
begin
|
28
|
+
ActiveRecord::Base.establish_connection(db_name.to_sym)
|
29
|
+
ActiveRecord::Base.connection
|
30
|
+
rescue StandardError
|
31
|
+
case db_name
|
32
|
+
when /mysql/
|
33
|
+
ActiveRecord::Base.establish_connection(config.merge('database' => nil))
|
34
|
+
ActiveRecord::Base.connection.create_database(config['database'],
|
35
|
+
{ charset: 'utf8', collation: 'utf8_unicode_ci' })
|
36
|
+
when 'postgresql'
|
37
|
+
ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres',
|
38
|
+
'schema_search_path' => 'public'))
|
39
|
+
ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => 'utf8'))
|
29
40
|
end
|
30
41
|
|
31
|
-
|
32
|
-
Dir[File.dirname(__dir__) + '/internal/app/models/*.rb'].each { |f| require f }
|
33
|
-
|
34
|
-
else
|
35
|
-
fail "Please create #{database_yml} first to configure your database. Take a look at: #{database_yml}.sample"
|
42
|
+
ActiveRecord::Base.establish_connection(config)
|
36
43
|
end
|
44
|
+
|
45
|
+
require "#{File.dirname(__FILE__)}/../internal/db/schema.rb"
|
46
|
+
Dir["#{File.dirname(__dir__)}/internal/app/models/*.rb"].each { |f| require f }
|