make_taggable 0.7.4 → 1.0.0

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +79 -0
  3. data/CONTRIBUTING.md +65 -22
  4. data/LICENSE.md +18 -17
  5. data/README.md +73 -444
  6. data/UPGRADING.md +28 -2
  7. data/docs/caching.md +93 -0
  8. data/docs/configuration.md +108 -0
  9. data/docs/contexts.md +147 -0
  10. data/docs/database.md +106 -0
  11. data/docs/getting-started.md +114 -0
  12. data/docs/migrating-from-aato.md +67 -0
  13. data/docs/ownership.md +111 -0
  14. data/docs/parsers.md +109 -0
  15. data/docs/querying.md +163 -0
  16. data/docs/tag-clouds.md +74 -0
  17. data/lib/make_taggable/default_parser.rb +45 -32
  18. data/lib/make_taggable/engine.rb +6 -0
  19. data/lib/make_taggable/generic_parser.rb +30 -4
  20. data/lib/make_taggable/tag.rb +121 -19
  21. data/lib/make_taggable/tag_list.rb +77 -24
  22. data/lib/make_taggable/taggable/cache.rb +61 -11
  23. data/lib/make_taggable/taggable/collection.rb +102 -30
  24. data/lib/make_taggable/taggable/core.rb +154 -34
  25. data/lib/make_taggable/taggable/ownership.rb +82 -4
  26. data/lib/make_taggable/taggable/related.rb +66 -3
  27. data/lib/make_taggable/taggable/tag_list_type.rb +8 -0
  28. data/lib/make_taggable/taggable/tagged_with_query/all_tags_query.rb +10 -0
  29. data/lib/make_taggable/taggable/tagged_with_query/any_tags_query.rb +10 -0
  30. data/lib/make_taggable/taggable/tagged_with_query/exclude_tags_query.rb +10 -0
  31. data/lib/make_taggable/taggable/tagged_with_query/query_base.rb +15 -0
  32. data/lib/make_taggable/taggable/tagged_with_query.rb +17 -0
  33. data/lib/make_taggable/taggable.rb +34 -33
  34. data/lib/make_taggable/tagger.rb +73 -12
  35. data/lib/make_taggable/tagging.rb +30 -3
  36. data/lib/make_taggable/tags_helper.rb +22 -1
  37. data/lib/make_taggable/utils.rb +40 -5
  38. data/lib/make_taggable/version.rb +8 -1
  39. data/lib/make_taggable.rb +153 -8
  40. data/make_taggable.gemspec +13 -20
  41. metadata +50 -165
  42. data/.dummyrc +0 -17
  43. data/.github/workflows/ci.yml +0 -140
  44. data/.github/workflows/standard-ci.yml +0 -27
  45. data/.gitignore +0 -17
  46. data/.rspec +0 -3
  47. data/Appraisals +0 -15
  48. data/Gemfile +0 -16
  49. data/Rakefile +0 -13
  50. data/gemfiles/rails_5.gemfile +0 -9
  51. data/gemfiles/rails_6.gemfile +0 -9
  52. data/gemfiles/rails_6_1.gemfile +0 -9
  53. data/gemfiles/rails_master.gemfile +0 -9
  54. data/lib/tasks/setup_test_db.rake +0 -8
@@ -1,11 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MakeTaggable::Taggable
4
+ ##
5
+ # Finding records that share tags with this one.
6
+ #
7
+ # Results come back ordered by how many tags matched, most first, and carry that figure as a
8
+ # `count` attribute.
9
+ #
2
10
  module Related
11
+ ##
12
+ # @param base [Class] the model being made taggable
13
+ # @return [void]
14
+ #
15
+ # @api private
16
+ #
3
17
  def self.included(base)
4
18
  base.extend MakeTaggable::Taggable::Related::ClassMethods
5
19
  base.initialize_make_taggable_related
6
20
  end
7
21
 
22
+ ##
23
+ # Added to every taggable model.
24
+ #
8
25
  module ClassMethods
26
+ ##
27
+ # Defines `find_related_<context>` and `find_related_<context>_for` for each context.
28
+ #
29
+ # @return [void]
30
+ #
9
31
  def initialize_make_taggable_related
10
32
  tag_types.map(&:to_s).each do |tag_type|
11
33
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -21,29 +43,70 @@ module MakeTaggable::Taggable
21
43
  end
22
44
  end
23
45
 
46
+ ##
47
+ # Adds contexts and refreshes the related-record finders.
48
+ #
49
+ # @param args [Array<Symbol, String>] the contexts to add
50
+ # @return [void]
51
+ #
24
52
  def make_taggable(*args)
25
- super(*args)
53
+ super
26
54
  initialize_make_taggable_related
27
55
  end
28
56
  end
29
57
 
58
+ ##
59
+ # Records of this model tagged, in one context, with the tags this record carries in another.
60
+ #
61
+ # @param search_context [Symbol, String] the context to take this record's tags from
62
+ # @param result_context [Symbol, String] the context to match them against
63
+ # @param options [Hash] reserved for future use
64
+ # @return [ActiveRecord::Relation] ordered by number of matching tags, descending
65
+ #
30
66
  def find_matching_contexts(search_context, result_context, options = {})
31
67
  matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options)
32
68
  end
33
69
 
70
+ ##
71
+ # As {#find_matching_contexts}, against another model.
72
+ #
73
+ # @param klass [Class] the model to search
74
+ # @param search_context [Symbol, String] the context to take this record's tags from
75
+ # @param result_context [Symbol, String] the context to match them against
76
+ # @param options [Hash] reserved for future use
77
+ # @return [ActiveRecord::Relation] ordered by number of matching tags, descending
78
+ #
34
79
  def find_matching_contexts_for(klass, search_context, result_context, options = {})
35
80
  matching_contexts_for(search_context.to_s, result_context.to_s, klass, options)
36
81
  end
37
82
 
83
+ ##
84
+ # Builds the relation behind {#find_matching_contexts}.
85
+ #
86
+ # @param search_context [Symbol, String] the context to take this record's tags from
87
+ # @param result_context [Symbol, String] the context to match them against
88
+ # @param klass [Class] the model to search
89
+ # @param options [Hash] reserved for future use
90
+ # @return [ActiveRecord::Relation]
91
+ #
38
92
  def matching_contexts_for(search_context, result_context, klass, options = {})
39
93
  tags_to_find = tags_on(search_context).map { |t| t.name }
40
- related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{MakeTaggable::Tagging.table_name}.taggable_id AND #{MakeTaggable::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{MakeTaggable::Tagging.table_name}.tag_id = #{MakeTaggable::Tag.table_name}.#{MakeTaggable::Tag.primary_key} AND #{MakeTaggable::Tag.table_name}.name IN (?) AND #{MakeTaggable::Tagging.table_name}.context = ?", tags_to_find, result_context])
94
+ related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{MakeTaggable::Tagging.table_name}.taggable_id AND #{MakeTaggable::Tagging.table_name}.taggable_type = ? AND #{MakeTaggable::Tagging.table_name}.tag_id = #{MakeTaggable::Tag.table_name}.#{MakeTaggable::Tag.primary_key} AND #{MakeTaggable::Tag.table_name}.name IN (?) AND #{MakeTaggable::Tagging.table_name}.context = ?", klass.base_class.to_s, tags_to_find, result_context])
41
95
  end
42
96
 
97
+ ##
98
+ # Builds the relation behind each generated `find_related_<context>` method.
99
+ #
100
+ # @param context [Symbol, String] the context to match on
101
+ # @param klass [Class] the model to search
102
+ # @param options [Hash] the search options
103
+ # @option options [String, Array<String>] :ignore tags to leave out of the match
104
+ # @return [ActiveRecord::Relation] ordered by number of matching tags, descending
105
+ #
43
106
  def related_tags_for(context, klass, options = {})
44
107
  tags_to_ignore = Array.wrap(options[:ignore]).map(&:to_s) || []
45
108
  tags_to_find = tags_on(context).map { |t| t.name }.reject { |t| tags_to_ignore.include? t }
46
- related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{MakeTaggable::Tagging.table_name}.taggable_id AND #{MakeTaggable::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{MakeTaggable::Tagging.table_name}.tag_id = #{MakeTaggable::Tag.table_name}.#{MakeTaggable::Tag.primary_key} AND #{MakeTaggable::Tag.table_name}.name IN (?) AND #{MakeTaggable::Tagging.table_name}.context = ?", tags_to_find, context])
109
+ related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{MakeTaggable::Tagging.table_name}.taggable_id AND #{MakeTaggable::Tagging.table_name}.taggable_type = ? AND #{MakeTaggable::Tagging.table_name}.tag_id = #{MakeTaggable::Tag.table_name}.#{MakeTaggable::Tag.primary_key} AND #{MakeTaggable::Tag.table_name}.name IN (?) AND #{MakeTaggable::Tagging.table_name}.context = ?", klass.base_class.to_s, tags_to_find, context])
47
110
  end
48
111
 
49
112
  private
@@ -1,4 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MakeTaggable::Taggable
4
+ ##
5
+ # The Active Model type backing each generated `*_list` attribute, so tag lists take part in
6
+ # dirty tracking alongside ordinary columns.
7
+ #
8
+ # @api private
9
+ #
2
10
  class TagListType < ActiveModel::Type::Value
3
11
  end
4
12
  end
@@ -1,5 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MakeTaggable::Taggable::TaggedWithQuery
4
+ ##
5
+ # Records carrying every tag in the list.
6
+ #
7
+ # @api private
8
+ #
2
9
  class AllTagsQuery < QueryBase
10
+ ##
11
+ # @return [ActiveRecord::Relation]
12
+ #
3
13
  def build
4
14
  taggable_model.joins(each_tag_in_list)
5
15
  .group(by_taggable)
@@ -1,5 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MakeTaggable::Taggable::TaggedWithQuery
4
+ ##
5
+ # Records carrying at least one tag in the list.
6
+ #
7
+ # @api private
8
+ #
2
9
  class AnyTagsQuery < QueryBase
10
+ ##
11
+ # @return [ActiveRecord::Relation]
12
+ #
3
13
  def build
4
14
  taggable_model.select(all_fields)
5
15
  .where(model_has_at_least_one_tag)
@@ -1,5 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MakeTaggable::Taggable::TaggedWithQuery
4
+ ##
5
+ # Records carrying none of the tags in the list.
6
+ #
7
+ # @api private
8
+ #
2
9
  class ExcludeTagsQuery < QueryBase
10
+ ##
11
+ # @return [ActiveRecord::Relation]
12
+ #
3
13
  def build
4
14
  taggable_model.joins(owning_to_tagger)
5
15
  .where(tags_not_in_list)
@@ -1,5 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MakeTaggable::Taggable::TaggedWithQuery
4
+ ##
5
+ # Shared Arel plumbing for the three query strategies.
6
+ #
7
+ # @api private
8
+ #
2
9
  class QueryBase
10
+ ##
11
+ # @param taggable_model [Class] the model being queried
12
+ # @param tag_model [Class] the tag model
13
+ # @param tagging_model [Class] the tagging model
14
+ # @param tag_list [MakeTaggable::TagList] the tags to match
15
+ # @param options [Hash] the options given to `tagged_with`
16
+ # @return [MakeTaggable::Taggable::TaggedWithQuery::QueryBase]
17
+ #
3
18
  def initialize(taggable_model, tag_model, tagging_model, tag_list, options)
4
19
  @taggable_model = taggable_model
5
20
  @tag_model = tag_model
@@ -1,9 +1,26 @@
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
 
8
+ ##
9
+ # Builds the relation behind {MakeTaggable::Taggable::Core::ClassMethods#tagged_with}.
10
+ #
11
+ # @api private
12
+ #
6
13
  module MakeTaggable::Taggable::TaggedWithQuery
14
+ ##
15
+ # Picks the query strategy the options call for and builds the relation.
16
+ #
17
+ # @param taggable_model [Class] the model being queried
18
+ # @param tag_model [Class] the tag model
19
+ # @param tagging_model [Class] the tagging model
20
+ # @param tag_list [MakeTaggable::TagList] the tags to match
21
+ # @param options [Hash] the options given to `tagged_with`
22
+ # @return [ActiveRecord::Relation]
23
+ #
7
24
  def self.build(taggable_model, tag_model, tagging_model, tag_list, options)
8
25
  if options[:exclude].present?
9
26
  ExcludeTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
@@ -1,55 +1,61 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MakeTaggable
4
+ ##
5
+ # Declaring a model taggable. Mixed into Active Record automatically, which is what puts
6
+ # {#make_taggable} on every model.
7
+ #
2
8
  module Taggable
9
+ ##
10
+ # Whether this model has been made taggable.
11
+ #
12
+ # @return [TrueClass, FalseClass] `false` until {#make_taggable} is called
13
+ #
3
14
  def taggable?
4
15
  false
5
16
  end
6
17
 
7
18
  ##
8
- # This is an alias for calling <tt>make_taggable :tags</tt>.
19
+ # Make a model taggable on the given contexts.
9
20
  #
10
- # Example:
11
- # class Book < ActiveRecord::Base
12
- # acts_as_taggable
13
- # end
14
- def acts_as_taggable
15
- make_taggable :tags
16
- end
17
-
18
- ##
19
- # This is an alias for calling <tt>acts_as_ordered_taggable_on :tags</tt>.
21
+ # Called without arguments it makes the model taggable on `:tags`, which is the context every
22
+ # other part of the library treats as the default.
20
23
  #
21
- # Example:
24
+ # @param tag_types [Array<Symbol, String>] the contexts to tag on
25
+ # @return [void]
26
+ #
27
+ # @example A single, default context
22
28
  # class Book < ActiveRecord::Base
23
- # acts_as_ordered_taggable
29
+ # make_taggable
24
30
  # end
25
- def acts_as_ordered_taggable
26
- acts_as_ordered_taggable_on :tags
27
- end
28
-
29
- ##
30
- # Make a model taggable on specified contexts.
31
- #
32
- # @param [Array] tag_types An array of taggable contexts
33
31
  #
34
- # Example:
32
+ # @example Several named contexts
35
33
  # class User < ActiveRecord::Base
36
34
  # make_taggable :languages, :skills
37
35
  # end
36
+ #
38
37
  def make_taggable(*tag_types)
38
+ tag_types = [:tags] if tag_types.flatten.compact.empty?
39
+
39
40
  taggable_on(false, tag_types)
40
41
  end
41
42
 
42
43
  ##
43
- # Make a model taggable on specified contexts
44
- # and preserves the order in which tags are created
44
+ # Make a model taggable on the given contexts, preserving the order in which tags were added.
45
+ #
46
+ # Called without arguments it makes the model taggable on `:tags`.
45
47
  #
46
- # @param [Array] tag_types An array of taggable contexts
48
+ # @param tag_types [Array<Symbol, String>] the contexts to tag on
49
+ # @return [void]
47
50
  #
48
- # Example:
51
+ # @example
49
52
  # class User < ActiveRecord::Base
50
- # acts_as_ordered_taggable_on :languages, :skills
53
+ # make_ordered_taggable :languages, :skills
51
54
  # end
52
- def acts_as_ordered_taggable_on(*tag_types)
55
+ #
56
+ def make_ordered_taggable(*tag_types)
57
+ tag_types = [:tags] if tag_types.flatten.compact.empty?
58
+
53
59
  taggable_on(true, tag_types)
54
60
  end
55
61
 
@@ -58,11 +64,6 @@ module MakeTaggable
58
64
  # Make a model taggable on specified contexts
59
65
  # and optionally preserves the order in which tags are created
60
66
  #
61
- # Separate methods used above for backwards compatibility
62
- # so that the original make_taggable method is unaffected
63
- # as it's not possible to add another argument to the method
64
- # without the tag_types being enclosed in square brackets
65
- #
66
67
  # NB: method overridden in core module in order to create tag type
67
68
  # associations and methods after this logic has executed
68
69
  #
@@ -1,19 +1,40 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MakeTaggable
4
+ ##
5
+ # Ownership of tags. Mixed into Active Record automatically, which is what puts {ClassMethods#make_tagger}
6
+ # on every model.
7
+ #
2
8
  module Tagger
9
+ ##
10
+ # @param base [Class] the class being extended
11
+ # @return [void]
12
+ #
13
+ # @api private
14
+ #
3
15
  def self.included(base)
4
16
  base.extend ClassMethods
5
17
  end
6
18
 
19
+ ##
20
+ # Added to every Active Record model.
21
+ #
7
22
  module ClassMethods
8
23
  ##
9
- # Make a model a tagger. This allows an instance of a model to claim ownership
10
- # of tags.
24
+ # Make a model a tagger, allowing its instances to claim ownership of the tags they apply.
25
+ #
26
+ # Adds an `owned_taggings` association and an `owned_tags` association to the model.
27
+ #
28
+ # @param opts [Hash] options forwarded to the generated `owned_taggings` association
29
+ # @option opts [Proc] :scope a scope applied to the `owned_taggings` association
30
+ # @return [void]
11
31
  #
12
- # Example:
32
+ # @example
13
33
  # class User < ActiveRecord::Base
14
- # acts_as_tagger
34
+ # make_tagger
15
35
  # end
16
- def acts_as_tagger(opts = {})
36
+ #
37
+ def make_tagger(opts = {})
17
38
  class_eval do
18
39
  owned_taggings_scope = opts.delete(:scope)
19
40
 
@@ -34,26 +55,47 @@ module MakeTaggable
34
55
  extend MakeTaggable::Tagger::SingletonMethods
35
56
  end
36
57
 
58
+ ##
59
+ # Whether this model claims ownership of the tags it applies.
60
+ #
61
+ # @return [TrueClass, FalseClass] `false` until {#make_tagger} is called
62
+ #
37
63
  def tagger?
38
64
  false
39
65
  end
40
66
 
67
+ ##
68
+ # @return [TrueClass, FalseClass]
69
+ # @see #tagger?
70
+ #
41
71
  def is_tagger?
42
72
  tagger?
43
73
  end
44
74
  end
45
75
 
76
+ ##
77
+ # Added to a model once it is a tagger.
78
+ #
46
79
  module InstanceMethods
47
80
  ##
48
- # Tag a taggable model with tags that are owned by the tagger.
81
+ # Tags a record, with this tagger as the owner of the tags.
82
+ #
83
+ # The taggable is saved unless `:skip_save` is given, so the tags are persisted immediately.
49
84
  #
50
- # @param taggable The object that will be tagged
51
- # @param [Hash] options An hash with options. Available options are:
52
- # * <tt>:with</tt> - The tags that you want to
53
- # * <tt>:on</tt> - The context on which you want to tag
85
+ # @param taggable [ActiveRecord::Base] the record to tag
86
+ # @param opts [Hash] the tagging options
87
+ # @option opts [String, Array<String>] :with the tags to apply
88
+ # @option opts [Symbol, String] :on the context to apply them in
89
+ # @option opts [TrueClass, FalseClass] :skip_save whether to leave the taggable unsaved
90
+ # @option opts [TrueClass, FalseClass] :force whether to allow a context the model does not
91
+ # declare, on by default
92
+ # @return [TrueClass, FalseClass] whether the taggable saved, or `false` when it is not taggable
93
+ # @raise [RuntimeError] when `:on` or `:with` is missing, or the context is undeclared and
94
+ # `:force` is off
95
+ #
96
+ # @example
97
+ # @user.tag(@photo, with: "paris, normandy", on: :locations)
54
98
  #
55
- # Example:
56
- # @user.tag(@photo, :with => "paris, normandy", :on => :locations)
57
99
  def tag(taggable, opts = {})
58
100
  opts.reverse_merge!(force: true)
59
101
  skip_save = opts.delete(:skip_save)
@@ -67,20 +109,39 @@ module MakeTaggable
67
109
  taggable.save unless skip_save
68
110
  end
69
111
 
112
+ ##
113
+ # Whether this record claims ownership of the tags it applies.
114
+ #
115
+ # @return [TrueClass, FalseClass]
116
+ #
70
117
  def tagger?
71
118
  self.class.is_tagger?
72
119
  end
73
120
 
121
+ ##
122
+ # @return [TrueClass, FalseClass]
123
+ # @see #tagger?
124
+ #
74
125
  def is_tagger?
75
126
  tagger?
76
127
  end
77
128
  end
78
129
 
130
+ ##
131
+ # Replaces {ClassMethods#tagger?} once a model is a tagger.
132
+ #
79
133
  module SingletonMethods
134
+ ##
135
+ # @return [TrueClass, FalseClass] always `true`
136
+ #
80
137
  def tagger?
81
138
  true
82
139
  end
83
140
 
141
+ ##
142
+ # @return [TrueClass, FalseClass]
143
+ # @see #tagger?
144
+ #
84
145
  def is_tagger?
85
146
  tagger?
86
147
  end
@@ -1,8 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MakeTaggable
2
- class Tagging < ::ActiveRecord::Base #:nodoc:
4
+ ##
5
+ # The join between a tag and the record it was applied to.
6
+ #
7
+ # A tagging records the context the tag was applied in, and optionally the tagger who applied it.
8
+ # Records own their taggings, so destroying a taggable destroys its taggings with it.
9
+ #
10
+ # @!attribute [rw] context
11
+ # The context the tag was applied in, such as `"skills"`.
12
+ # @return [String]
13
+ # @!attribute [rw] tag
14
+ # The tag being applied.
15
+ # @return [MakeTaggable::Tag]
16
+ # @!attribute [rw] taggable
17
+ # The record being tagged.
18
+ # @return [ActiveRecord::Base]
19
+ # @!attribute [rw] tagger
20
+ # The record that applied the tag, when it was applied by a tagger.
21
+ # @return [ActiveRecord::Base, NilClass]
22
+ #
23
+ class Tagging < ::ActiveRecord::Base
24
+ ##
25
+ # The context used when none is given.
26
+ #
27
+ # @return [String]
28
+ #
29
+ DEFAULT_CONTEXT = "tags"
30
+
3
31
  self.table_name = MakeTaggable.taggings_table
4
32
 
5
- DEFAULT_CONTEXT = "tags"
6
33
  belongs_to :tag, class_name: "::MakeTaggable::Tag", counter_cache: MakeTaggable.tags_counter
7
34
  belongs_to :taggable, polymorphic: true
8
35
 
@@ -11,7 +38,7 @@ module MakeTaggable
11
38
  scope :owned_by, ->(owner) { where(tagger: owner) }
12
39
  scope :not_owned, -> { where(tagger_id: nil, tagger_type: nil) }
13
40
 
14
- scope :by_contexts, ->(contexts) { where(context: (contexts || DEFAULT_CONTEXT)) }
41
+ scope :by_contexts, ->(contexts) { where(context: contexts || DEFAULT_CONTEXT) }
15
42
  scope :by_context, ->(context = DEFAULT_CONTEXT) { by_contexts(context.to_s) }
16
43
 
17
44
  validates_presence_of :context
@@ -1,6 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MakeTaggable
4
+ ##
5
+ # View helpers for rendering tags. Mixed into Action View automatically.
6
+ #
2
7
  module TagsHelper
3
- # See the wiki for an example using tag_cloud.
8
+ ##
9
+ # Yields each tag with the CSS class matching how often it is used, for building a tag cloud.
10
+ #
11
+ # Classes are handed out in the order given, from least to most used, so the first class is the
12
+ # smallest and the last is the largest.
13
+ #
14
+ # @param tags [Array<MakeTaggable::Tag>] tags carrying a `taggings_count`
15
+ # @param classes [Array<String>] the CSS classes, smallest first
16
+ # @yieldparam tag [MakeTaggable::Tag] the tag
17
+ # @yieldparam css_class [String] the class for that tag's frequency
18
+ # @return [Array] the tags, or an empty array when none were given
19
+ #
20
+ # @example
21
+ # <% tag_cloud(@tags, %w[css1 css2 css3 css4]) do |tag, css_class| %>
22
+ # <%= link_to tag.name, tag_path(tag.name), class: css_class %>
23
+ # <% end %>
24
+ #
4
25
  def tag_cloud(tags, classes)
5
26
  return [] if tags.empty?
6
27
 
@@ -1,31 +1,66 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MakeTaggable
4
+ ##
5
+ # Database differences the rest of the library needs to work around.
6
+ #
2
7
  module Utils
3
8
  class << self
9
+ ##
10
+ # The connection tags are read and written through.
11
+ #
12
+ # @return [ActiveRecord::ConnectionAdapters::AbstractAdapter]
13
+ #
4
14
  def connection
5
15
  MakeTaggable::Tag.connection
6
16
  end
7
17
 
18
+ ##
19
+ # Whether tags are stored in PostgreSQL.
20
+ #
21
+ # @return [TrueClass, FalseClass]
22
+ #
8
23
  def using_postgresql?
9
24
  connection && connection.adapter_name == "PostgreSQL"
10
25
  end
11
26
 
27
+ ##
28
+ # Whether tags are stored in MySQL.
29
+ #
30
+ # @return [TrueClass, FalseClass]
31
+ #
12
32
  def using_mysql?
13
33
  connection && connection.adapter_name == "Mysql2"
14
34
  end
15
35
 
36
+ ##
37
+ # A short digest of a string, used to keep generated SQL aliases unique and within the
38
+ # identifier length databases allow.
39
+ #
40
+ # @param string [String] the value to digest
41
+ # @return [String] the first seven characters of the SHA1 hex digest
42
+ #
16
43
  def sha_prefix(string)
17
44
  Digest::SHA1.hexdigest(string)[0..6]
18
45
  end
19
46
 
47
+ ##
48
+ # The case-insensitive pattern operator for the current adapter.
49
+ #
50
+ # @return [String] `"ILIKE"` on PostgreSQL, otherwise `"LIKE"`
51
+ #
20
52
  def like_operator
21
53
  using_postgresql? ? "ILIKE" : "LIKE"
22
54
  end
23
55
 
24
- def legacy_activerecord?
25
- ActiveRecord.version <= Gem::Version.new("5.3.0")
26
- end
27
-
28
- # escape _ and % characters in strings, since these are wildcards in SQL.
56
+ ##
57
+ # Escapes the SQL pattern wildcards in a string so it matches literally.
58
+ #
59
+ # Pair it with an `ESCAPE '!'` clause.
60
+ #
61
+ # @param str [String] the value to escape
62
+ # @return [String] with `!`, `%` and `_` escaped
63
+ #
29
64
  def escape_like(str)
30
65
  str.gsub(/[!%_]/) { |x| "!" + x }
31
66
  end
@@ -1,3 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MakeTaggable
2
- VERSION = "0.7.4"
4
+ ##
5
+ # The released version of the gem.
6
+ #
7
+ # @return [String]
8
+ #
9
+ VERSION = "1.0.0"
3
10
  end