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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/spec.yml +15 -34
  3. data/Appraisals +13 -13
  4. data/CHANGELOG.md +17 -3
  5. data/README.md +6 -6
  6. data/acts-as-taggable-on.gemspec +2 -2
  7. data/db/migrate/1_acts_as_taggable_on_migration.rb +5 -8
  8. data/db/migrate/2_add_missing_unique_indices.rb +6 -8
  9. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +3 -6
  10. data/db/migrate/4_add_missing_taggable_index.rb +5 -7
  11. data/db/migrate/5_change_collation_for_tag_names.rb +4 -6
  12. data/db/migrate/6_add_missing_indexes_on_taggings.rb +15 -13
  13. data/db/migrate/7_add_tenant_to_taggings.rb +7 -10
  14. data/docker-compose.yml +15 -0
  15. data/gemfiles/activerecord_6.0.gemfile +5 -8
  16. data/gemfiles/activerecord_6.1.gemfile +3 -8
  17. data/gemfiles/{activerecord_5.0.gemfile → activerecord_7.0.gemfile} +6 -9
  18. data/lib/acts_as_taggable_on/default_parser.rb +8 -10
  19. data/lib/acts_as_taggable_on/engine.rb +2 -0
  20. data/lib/acts_as_taggable_on/generic_parser.rb +2 -0
  21. data/lib/acts_as_taggable_on/tag.rb +30 -30
  22. data/lib/acts_as_taggable_on/tag_list.rb +8 -11
  23. data/lib/acts_as_taggable_on/taggable/cache.rb +64 -62
  24. data/lib/acts_as_taggable_on/taggable/collection.rb +178 -142
  25. data/lib/acts_as_taggable_on/taggable/core.rb +248 -244
  26. data/lib/acts_as_taggable_on/taggable/ownership.rb +110 -98
  27. data/lib/acts_as_taggable_on/taggable/related.rb +60 -47
  28. data/lib/acts_as_taggable_on/taggable/tag_list_type.rb +6 -2
  29. data/lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb +110 -106
  30. data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +57 -53
  31. data/lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb +63 -60
  32. data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +54 -46
  33. data/lib/acts_as_taggable_on/taggable/tagged_with_query.rb +14 -8
  34. data/lib/acts_as_taggable_on/taggable.rb +14 -14
  35. data/lib/acts_as_taggable_on/tagger.rb +9 -5
  36. data/lib/acts_as_taggable_on/tagging.rb +6 -4
  37. data/lib/acts_as_taggable_on/tags_helper.rb +3 -1
  38. data/lib/acts_as_taggable_on/utils.rb +4 -2
  39. data/lib/acts_as_taggable_on/version.rb +3 -1
  40. data/spec/support/database.rb +36 -26
  41. metadata +13 -14
  42. data/gemfiles/activerecord_5.1.gemfile +0 -21
  43. data/gemfiles/activerecord_5.2.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
@@ -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
- # 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
- #
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
- 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'
@@ -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: [: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]
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
- else
33
- tag.destroy if tag.reload.taggings.none?
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.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 = '8.1.0'
4
+ VERSION = '9.0.1'
3
5
  end
@@ -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('../../internal/config/database.yml', __FILE__)
6
+ database_yml = File.expand_path('../internal/config/database.yml', __dir__)
5
7
 
6
- if File.exist?(database_yml)
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
- ActiveRecord::Migration.verbose = false
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
- ActiveRecord::Base.configurations = YAML.load_file(database_yml)
11
- ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), '../debug.log'))
12
- ActiveRecord::Base.logger.level = ENV['TRAVIS'] ? ::Logger::ERROR : ::Logger::DEBUG
13
- config = ActiveSupport::HashWithIndifferentAccess.new(ActiveRecord::Base.configurations[db_name])
14
-
15
- begin
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
- ActiveRecord::Base.establish_connection(config)
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
- require File.dirname(__FILE__) + '/../internal/db/schema.rb'
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 }