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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/spec.yml +76 -0
  3. data/Appraisals +13 -13
  4. data/CHANGELOG.md +27 -2
  5. data/Gemfile +1 -0
  6. data/README.md +32 -7
  7. data/acts-as-taggable-on.gemspec +2 -2
  8. data/db/migrate/1_acts_as_taggable_on_migration.rb +5 -8
  9. data/db/migrate/2_add_missing_unique_indices.rb +6 -8
  10. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +3 -6
  11. data/db/migrate/4_add_missing_taggable_index.rb +5 -7
  12. data/db/migrate/5_change_collation_for_tag_names.rb +4 -6
  13. data/db/migrate/6_add_missing_indexes_on_taggings.rb +15 -13
  14. data/db/migrate/7_add_tenant_to_taggings.rb +13 -0
  15. data/docker-compose.yml +15 -0
  16. data/gemfiles/activerecord_6.0.gemfile +5 -8
  17. data/gemfiles/activerecord_6.1.gemfile +3 -8
  18. data/gemfiles/{activerecord_5.2.gemfile → activerecord_7.0.gemfile} +6 -9
  19. data/lib/acts_as_taggable_on/default_parser.rb +8 -10
  20. data/lib/acts_as_taggable_on/engine.rb +2 -0
  21. data/lib/acts_as_taggable_on/generic_parser.rb +2 -0
  22. data/lib/acts_as_taggable_on/tag.rb +33 -27
  23. data/lib/acts_as_taggable_on/tag_list.rb +8 -11
  24. data/lib/acts_as_taggable_on/taggable/cache.rb +64 -62
  25. data/lib/acts_as_taggable_on/taggable/collection.rb +178 -142
  26. data/lib/acts_as_taggable_on/taggable/core.rb +250 -236
  27. data/lib/acts_as_taggable_on/taggable/ownership.rb +110 -98
  28. data/lib/acts_as_taggable_on/taggable/related.rb +60 -47
  29. data/lib/acts_as_taggable_on/taggable/tag_list_type.rb +6 -2
  30. data/lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb +110 -106
  31. data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +57 -53
  32. data/lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb +63 -60
  33. data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +54 -46
  34. data/lib/acts_as_taggable_on/taggable/tagged_with_query.rb +14 -8
  35. data/lib/acts_as_taggable_on/taggable.rb +30 -12
  36. data/lib/acts_as_taggable_on/tagger.rb +9 -5
  37. data/lib/acts_as_taggable_on/tagging.rb +8 -4
  38. data/lib/acts_as_taggable_on/tags_helper.rb +3 -1
  39. data/lib/acts_as_taggable_on/utils.rb +4 -2
  40. data/lib/acts_as_taggable_on/version.rb +3 -1
  41. data/spec/acts_as_taggable_on/tag_spec.rb +16 -1
  42. data/spec/acts_as_taggable_on/taggable_spec.rb +6 -2
  43. data/spec/acts_as_taggable_on/tagging_spec.rb +26 -0
  44. data/spec/internal/app/models/taggable_model.rb +2 -0
  45. data/spec/internal/config/database.yml.sample +4 -8
  46. data/spec/internal/db/schema.rb +3 -0
  47. data/spec/support/database.rb +36 -26
  48. metadata +13 -22
  49. data/.travis.yml +0 -49
  50. data/UPGRADING.md +0 -8
  51. data/gemfiles/activerecord_5.0.gemfile +0 -21
  52. data/gemfiles/activerecord_5.1.gemfile +0 -21
@@ -1,70 +1,74 @@
1
- module ActsAsTaggableOn::Taggable::TaggedWithQuery
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
- private
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
- def all_fields
13
- taggable_arel_table[Arel.star]
14
- end
14
+ private
15
15
 
16
- def model_has_at_least_one_tag
17
- tagging_arel_table.project(Arel.star).where(at_least_one_tag).exists
18
- end
16
+ def all_fields
17
+ taggable_arel_table[Arel.star]
18
+ end
19
19
 
20
- def at_least_one_tag
21
- exists_contition = tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
22
- .and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
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
- if options[:start_at].present?
30
- exists_contition = exists_contition.and(tagging_arel_table[:created_at].gteq(options[:start_at]))
31
- end
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
- if options[:end_at].present?
34
- exists_contition = exists_contition.and(tagging_arel_table[:created_at].lteq(options[:end_at]))
35
- end
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
- if options[:on].present?
38
- exists_contition = exists_contition.and(tagging_arel_table[:context].eq(options[:on]))
39
- end
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
- if (owner = options[:owned_by]).present?
42
- exists_contition = exists_contition.and(tagging_arel_table[:tagger_id].eq(owner.id))
43
- .and(tagging_arel_table[:tagger_type].eq(owner.class.base_class.to_s))
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
- exists_contition
47
- end
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
- def order_conditions
50
- order_by = []
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
- order_by << options[:order] if options[:order].present?
56
- order_by.join(', ')
57
- end
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
- def alias_name(tag_list)
60
- alias_base_name = taggable_model.base_class.name.downcase
61
- taggings_context = options[:on] ? "_#{options[:on]}" : ''
59
+ order_by << options[:order] if options[:order].present?
60
+ order_by.join(', ')
61
+ end
62
62
 
63
- taggings_alias = adjust_taggings_alias(
64
- "#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{ActsAsTaggableOn::Utils.sha_prefix(tag_list.join('_'))}"
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
- taggings_alias
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
- module ActsAsTaggableOn::Taggable::TaggedWithQuery
2
- class ExcludeTagsQuery < QueryBase
3
- def build
4
- taggable_model.joins(owning_to_tagger)
5
- .where(tags_not_in_list)
6
- .having(tags_that_matches_count)
7
- .readonly(false)
8
- end
9
-
10
- private
11
-
12
- def tags_not_in_list
13
- return taggable_arel_table[:id].not_in(
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
- tagging_arel_table[:tag_id].eq(tag_arel_table[:id])
19
- .and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
20
- .and(tags_match_type)
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
- # FIXME: missing time scope, this is also missing in the original implementation
25
- end
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
- def owning_to_tagger
29
- return [] unless options[:owned_by].present?
34
+ owner = options[:owned_by]
30
35
 
31
- owner = options[:owned_by]
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
- arel_join = taggable_arel_table
34
- .join(tagging_arel_table)
35
- .on(
36
- tagging_arel_table[:tagger_id].eq(owner.id)
37
- .and(tagging_arel_table[:tagger_type].eq(owner.class.base_class.to_s))
38
- .and(tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key]))
39
- .and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
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
- if options[:match_all].present?
43
- arel_join = arel_join
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
- return arel_join.join_sources
51
- end
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
- def match_all_on_conditions
54
- on_condition = tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
55
- .and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
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
- if options[:start_at].present?
58
- on_condition = on_condition.and(tagging_arel_table[:created_at].gteq(options[:start_at]))
59
- end
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
- if options[:end_at].present?
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
- if options[:on].present?
66
- on_condition = on_condition.and(tagging_arel_table[:context].eq(options[:on]))
67
- end
70
+ on_condition
71
+ end
68
72
 
69
- on_condition
70
- end
73
+ def tags_that_matches_count
74
+ return [] if options[:match_all].blank?
71
75
 
72
- def tags_that_matches_count
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
- taggable_model.find_by_sql(tag_arel_table.project(Arel.star.count).where(tags_match_type).to_sql)
76
-
77
- tagging_arel_table[:taggable_id].count.eq(
78
- tag_arel_table.project(Arel.star.count).where(tags_match_type)
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
- module ActsAsTaggableOn::Taggable::TaggedWithQuery
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
- private
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
- attr_reader :taggable_model, :tag_model, :tagging_model, :tag_list, :options
15
+ private
14
16
 
15
- def taggable_arel_table
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
- def tag_arel_table
20
- @tag_arel_table ||= tag_model.arel_table
21
- end
19
+ def taggable_arel_table
20
+ @taggable_arel_table ||= taggable_model.arel_table
21
+ end
22
22
 
23
- def tagging_arel_table
24
- @tagging_arel_table ||=tagging_model.arel_table
25
- end
23
+ def tag_arel_table
24
+ @tag_arel_table ||= tag_model.arel_table
25
+ end
26
26
 
27
- def tag_match_type(tag)
28
- matches_attribute = tag_arel_table[:name]
29
- matches_attribute = matches_attribute.lower unless ActsAsTaggableOn.strict_case_match
27
+ def tagging_arel_table
28
+ @tagging_arel_table ||= tagging_model.arel_table
29
+ end
30
30
 
31
- if options[:wild].present?
32
- matches_attribute.matches("%#{escaped_tag(tag)}%", "!", ActsAsTaggableOn.strict_case_match)
33
- else
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
- def tags_match_type
39
- matches_attribute = tag_arel_table[:name]
40
- matches_attribute = matches_attribute.lower unless ActsAsTaggableOn.strict_case_match
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
- if options[:wild].present?
43
- matches_attribute.matches_any(tag_list.map{|tag| "%#{escaped_tag(tag)}%"}, "!", ActsAsTaggableOn.strict_case_match)
44
- else
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
- def escaped_tag(tag)
50
- tag = tag.downcase unless ActsAsTaggableOn.strict_case_match
51
- ActsAsTaggableOn::Utils.escape_like(tag)
52
- end
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
- def adjust_taggings_alias(taggings_alias)
55
- if taggings_alias.size > 75
56
- taggings_alias = 'taggings_alias_' + Digest::SHA1.hexdigest(taggings_alias)
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::Taggable::TaggedWithQuery
7
- def self.build(taggable_model, tag_model, tagging_model, tag_list, options)
8
- if options[:exclude].present?
9
- ExcludeTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
10
- elsif options[:any].present?
11
- AnyTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
12
- else
13
- AllTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
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
- # Make a model taggable on specified contexts
60
- # and optionally preserves the order in which tags are created
61
- #
62
- # Separate methods used above for backwards compatibility
63
- # so that the original acts_as_taggable_on method is unaffected
64
- # as it's not possible to add another argument to the method
65
- # without the tag_types being enclosed in square brackets
66
- #
67
- # NB: method overridden in core module in order to create tag type
68
- # associations and methods after this logic has executed
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
- fail 'You need to specify a tag context using :on' unless opts.key?(:on)
63
- fail 'You need to specify some tags using :with' unless opts.key?(:with)
64
- fail "No context :#{opts[:on]} defined in #{taggable.class}" unless opts[:force] || taggable.tag_types.include?(opts[:on])
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 #:nodoc:
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: [:taggable_type, :taggable_id, :context, :tagger_id, :tagger_type]
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
- else
31
- tag.destroy if tag.reload.taggings.count.zero?
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.sort_by(&:taggings_count).last.taggings_count.to_f
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 == 'PostgreSQL'
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| '!' + x }
35
+ str.gsub(/[!%_]/) { |x| "!#{x}" }
34
36
  end
35
37
  end
36
38
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsTaggableOn
2
- VERSION = '7.0.0'
4
+ VERSION = '9.0.1'
3
5
  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