acts-as-taggable-on 8.1.0 → 9.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 +13 -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 -7
  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.0'
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 }