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,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
|
@@ -54,19 +55,35 @@ module ActsAsTaggableOn
|
|
54
55
|
taggable_on(true, tag_types)
|
55
56
|
end
|
56
57
|
|
58
|
+
def acts_as_taggable_tenant(tenant)
|
59
|
+
if taggable?
|
60
|
+
else
|
61
|
+
class_attribute :tenant_column
|
62
|
+
end
|
63
|
+
self.tenant_column = tenant
|
64
|
+
|
65
|
+
# each of these add context-specific methods and must be
|
66
|
+
# called on each call of taggable_on
|
67
|
+
include Core
|
68
|
+
include Collection
|
69
|
+
include Cache
|
70
|
+
include Ownership
|
71
|
+
include Related
|
72
|
+
end
|
73
|
+
|
57
74
|
private
|
58
75
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
+
#
|
70
87
|
def taggable_on(preserve_tag_order, *tag_types)
|
71
88
|
tag_types = tag_types.to_a.flatten.compact.map(&:to_sym)
|
72
89
|
|
@@ -78,6 +95,7 @@ module ActsAsTaggableOn
|
|
78
95
|
self.tag_types = tag_types
|
79
96
|
class_attribute :preserve_tag_order
|
80
97
|
self.preserve_tag_order = preserve_tag_order
|
98
|
+
class_attribute :tenant_column
|
81
99
|
|
82
100
|
class_eval do
|
83
101
|
has_many :taggings, as: :taggable, dependent: :destroy, class_name: '::ActsAsTaggableOn::Tagging'
|
@@ -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'
|
@@ -14,10 +16,12 @@ module ActsAsTaggableOn
|
|
14
16
|
scope :by_contexts, ->(contexts) { where(context: (contexts || DEFAULT_CONTEXT)) }
|
15
17
|
scope :by_context, ->(context = DEFAULT_CONTEXT) { by_contexts(context.to_s) }
|
16
18
|
|
19
|
+
scope :by_tenant, ->(tenant) { where(tenant: tenant) }
|
20
|
+
|
17
21
|
validates_presence_of :context
|
18
22
|
validates_presence_of :tag_id
|
19
23
|
|
20
|
-
validates_uniqueness_of :tag_id, scope: [
|
24
|
+
validates_uniqueness_of :tag_id, scope: %i[taggable_type taggable_id context tagger_id tagger_type]
|
21
25
|
|
22
26
|
after_destroy :remove_unused_tags
|
23
27
|
|
@@ -27,8 +31,8 @@ module ActsAsTaggableOn
|
|
27
31
|
if ActsAsTaggableOn.remove_unused_tags
|
28
32
|
if ActsAsTaggableOn.tags_counter
|
29
33
|
tag.destroy if tag.reload.taggings_count.zero?
|
30
|
-
|
31
|
-
tag.destroy
|
34
|
+
elsif tag.reload.taggings.none?
|
35
|
+
tag.destroy
|
32
36
|
end
|
33
37
|
end
|
34
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
|
@@ -14,7 +14,7 @@ end
|
|
14
14
|
describe ActsAsTaggableOn::Tag do
|
15
15
|
before(:each) do
|
16
16
|
@tag = ActsAsTaggableOn::Tag.new
|
17
|
-
@user = TaggableModel.create(name: 'Pablo')
|
17
|
+
@user = TaggableModel.create(name: 'Pablo', tenant_id: 100)
|
18
18
|
end
|
19
19
|
|
20
20
|
|
@@ -70,6 +70,21 @@ describe ActsAsTaggableOn::Tag do
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
+
describe 'for tenant' do
|
74
|
+
before(:each) do
|
75
|
+
@user.skill_list.add('ruby')
|
76
|
+
@user.save
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should return tags for the tenant' do
|
80
|
+
expect(ActsAsTaggableOn::Tag.for_tenant('100').pluck(:name)).to include('ruby')
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should not return tags for other tenants' do
|
84
|
+
expect(ActsAsTaggableOn::Tag.for_tenant('200').pluck(:name)).to_not include('ruby')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
73
88
|
describe 'find or create by name' do
|
74
89
|
before(:each) do
|
75
90
|
@tag.name = 'awesome'
|
@@ -109,6 +109,10 @@ describe 'Taggable' do
|
|
109
109
|
expect(@taggable.tag_types).to eq(TaggableModel.tag_types)
|
110
110
|
end
|
111
111
|
|
112
|
+
it 'should have tenant column' do
|
113
|
+
expect(TaggableModel.tenant_column).to eq(:tenant_id)
|
114
|
+
end
|
115
|
+
|
112
116
|
it 'should have tag_counts_on' do
|
113
117
|
expect(TaggableModel.tag_counts_on(:tags)).to be_empty
|
114
118
|
|
@@ -676,11 +680,11 @@ describe 'Taggable' do
|
|
676
680
|
end
|
677
681
|
|
678
682
|
it 'should return all column names joined for TaggableModel GROUP clause' do
|
679
|
-
expect(@taggable.grouped_column_names_for(TaggableModel)).to eq('taggable_models.id, taggable_models.name, taggable_models.type')
|
683
|
+
expect(@taggable.grouped_column_names_for(TaggableModel)).to eq('taggable_models.id, taggable_models.name, taggable_models.type, taggable_models.tenant_id')
|
680
684
|
end
|
681
685
|
|
682
686
|
it 'should return all column names joined for NonStandardIdTaggableModel GROUP clause' do
|
683
|
-
expect(@taggable.grouped_column_names_for(TaggableModel)).to eq("taggable_models.#{TaggableModel.primary_key}, taggable_models.name, taggable_models.type")
|
687
|
+
expect(@taggable.grouped_column_names_for(TaggableModel)).to eq("taggable_models.#{TaggableModel.primary_key}, taggable_models.name, taggable_models.type, taggable_models.tenant_id")
|
684
688
|
end
|
685
689
|
end
|
686
690
|
|