acts-as-taggable-on 3.1.1 → 3.2.2

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +9 -7
  4. data/Appraisals +13 -8
  5. data/CHANGELOG.md +27 -7
  6. data/Gemfile +1 -2
  7. data/README.md +36 -19
  8. data/Rakefile +5 -17
  9. data/UPGRADING.md +6 -0
  10. data/acts-as-taggable-on.gemspec +15 -15
  11. data/db/migrate/1_acts_as_taggable_on_migration.rb +3 -3
  12. data/db/migrate/2_add_missing_unique_indices.rb +3 -5
  13. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +1 -1
  14. data/gemfiles/activerecord_3.2.gemfile +15 -0
  15. data/gemfiles/activerecord_4.0.gemfile +15 -0
  16. data/gemfiles/activerecord_4.1.gemfile +15 -0
  17. data/gemfiles/activerecord_edge.gemfile +16 -0
  18. data/lib/acts-as-taggable-on.rb +23 -21
  19. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +2 -4
  20. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +29 -20
  21. data/lib/acts_as_taggable_on/acts_as_taggable_on/compatibility.rb +11 -10
  22. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +103 -85
  23. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +5 -12
  24. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +11 -11
  25. data/lib/acts_as_taggable_on/engine.rb +0 -1
  26. data/lib/acts_as_taggable_on/tag.rb +24 -19
  27. data/lib/acts_as_taggable_on/tag_list.rb +97 -22
  28. data/lib/acts_as_taggable_on/taggable.rb +26 -30
  29. data/lib/acts_as_taggable_on/tagger.rb +30 -18
  30. data/lib/acts_as_taggable_on/tagging.rb +7 -8
  31. data/lib/acts_as_taggable_on/tags_helper.rb +1 -1
  32. data/lib/acts_as_taggable_on/utils.rb +57 -35
  33. data/lib/acts_as_taggable_on/version.rb +1 -1
  34. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +133 -138
  35. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +55 -58
  36. data/spec/acts_as_taggable_on/caching_spec.rb +34 -35
  37. data/spec/acts_as_taggable_on/related_spec.rb +59 -113
  38. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +118 -95
  39. data/spec/acts_as_taggable_on/tag_list_spec.rb +89 -57
  40. data/spec/acts_as_taggable_on/tag_spec.rb +129 -116
  41. data/spec/acts_as_taggable_on/taggable_spec.rb +539 -352
  42. data/spec/acts_as_taggable_on/tagger_spec.rb +81 -78
  43. data/spec/acts_as_taggable_on/tagging_spec.rb +13 -14
  44. data/spec/acts_as_taggable_on/tags_helper_spec.rb +25 -25
  45. data/spec/acts_as_taggable_on/utils_spec.rb +7 -13
  46. data/spec/internal/app/models/altered_inheriting_taggable_model.rb +3 -0
  47. data/spec/internal/app/models/cached_model.rb +3 -0
  48. data/spec/internal/app/models/cached_model_with_array.rb +5 -0
  49. data/spec/internal/app/models/company.rb +15 -0
  50. data/spec/internal/app/models/inheriting_taggable_model.rb +2 -0
  51. data/spec/internal/app/models/market.rb +2 -0
  52. data/spec/{models.rb → internal/app/models/models.rb} +34 -2
  53. data/spec/internal/app/models/non_standard_id_taggable_model.rb +8 -0
  54. data/spec/internal/app/models/ordered_taggable_model.rb +4 -0
  55. data/spec/internal/app/models/other_cached_model.rb +3 -0
  56. data/spec/internal/app/models/other_taggable_model.rb +4 -0
  57. data/spec/internal/app/models/student.rb +2 -0
  58. data/spec/internal/app/models/taggable_model.rb +13 -0
  59. data/spec/internal/app/models/untaggable_model.rb +3 -0
  60. data/spec/internal/app/models/user.rb +3 -0
  61. data/spec/{database.yml.sample → internal/config/database.yml.sample} +2 -2
  62. data/spec/internal/db/schema.rb +97 -0
  63. data/spec/schema.rb +11 -0
  64. data/spec/spec_helper.rb +9 -62
  65. data/spec/support/array.rb +9 -0
  66. data/spec/support/database.rb +42 -0
  67. data/spec/support/database_cleaner.rb +21 -0
  68. metadata +105 -43
  69. data/gemfiles/rails_3.2.gemfile +0 -7
  70. data/gemfiles/rails_4.0.gemfile +0 -7
  71. data/gemfiles/rails_4.1.gemfile +0 -7
  72. data/gemfiles/rails_edge.gemfile +0 -7
  73. data/spec/bm.rb +0 -52
@@ -1,19 +1,18 @@
1
1
  # coding: utf-8
2
2
  module ActsAsTaggableOn
3
3
  class Tag < ::ActiveRecord::Base
4
- extend ActsAsTaggableOn::Utils
5
4
 
6
5
  attr_accessible :name if defined?(ActiveModel::MassAssignmentSecurity)
7
6
 
8
7
  ### ASSOCIATIONS:
9
8
 
10
- has_many :taggings, :dependent => :destroy, :class_name => 'ActsAsTaggableOn::Tagging'
9
+ has_many :taggings, dependent: :destroy, class_name: 'ActsAsTaggableOn::Tagging'
11
10
 
12
11
  ### VALIDATIONS:
13
12
 
14
13
  validates_presence_of :name
15
- validates_uniqueness_of :name, :if => :validates_name_uniqueness?
16
- validates_length_of :name, :maximum => 255
14
+ validates_uniqueness_of :name, if: :validates_name_uniqueness?
15
+ validates_length_of :name, maximum: 255
17
16
 
18
17
  # monkey patch this method if don't need name uniqueness validation
19
18
  def validates_name_uniqueness?
@@ -26,7 +25,7 @@ module ActsAsTaggableOn
26
25
  if ActsAsTaggableOn.strict_case_match
27
26
  where(["name = #{binary}?", as_8bit_ascii(name)])
28
27
  else
29
- where(["LOWER(name) = LOWER(?)", as_8bit_ascii(unicode_downcase(name))])
28
+ where(['LOWER(name) = LOWER(?)', as_8bit_ascii(unicode_downcase(name))])
30
29
  end
31
30
  end
32
31
 
@@ -34,35 +33,35 @@ module ActsAsTaggableOn
34
33
  if ActsAsTaggableOn.strict_case_match
35
34
  clause = list.map { |tag|
36
35
  sanitize_sql(["name = #{binary}?", as_8bit_ascii(tag)])
37
- }.join(" OR ")
36
+ }.join(' OR ')
38
37
  where(clause)
39
38
  else
40
39
  clause = list.map { |tag|
41
- sanitize_sql(["LOWER(name) = LOWER(?)", as_8bit_ascii(unicode_downcase(tag))])
42
- }.join(" OR ")
40
+ sanitize_sql(['LOWER(name) = LOWER(?)', as_8bit_ascii(unicode_downcase(tag))])
41
+ }.join(' OR ')
43
42
  where(clause)
44
43
  end
45
44
  end
46
45
 
47
46
  def self.named_like(name)
48
- clause = ["name #{like_operator} ? ESCAPE '!'", "%#{escape_like(name)}%"]
47
+ clause = ["name #{ActsAsTaggableOn::Utils.like_operator} ? ESCAPE '!'", "%#{ActsAsTaggableOn::Utils.escape_like(name)}%"]
49
48
  where(clause)
50
49
  end
51
50
 
52
51
  def self.named_like_any(list)
53
52
  clause = list.map { |tag|
54
- sanitize_sql(["name #{like_operator} ? ESCAPE '!'", "%#{escape_like(tag.to_s)}%"])
55
- }.join(" OR ")
53
+ sanitize_sql(["name #{ActsAsTaggableOn::Utils.like_operator} ? ESCAPE '!'", "%#{ActsAsTaggableOn::Utils.escape_like(tag.to_s)}%"])
54
+ }.join(' OR ')
56
55
  where(clause)
57
56
  end
58
57
 
59
58
  ### CLASS METHODS:
60
59
 
61
60
  def self.find_or_create_with_like_by_name(name)
62
- if (ActsAsTaggableOn.strict_case_match)
61
+ if ActsAsTaggableOn.strict_case_match
63
62
  self.find_or_create_all_with_like_by_name([name]).first
64
63
  else
65
- named_like(name).first || create(:name => name)
64
+ named_like(name).first || create(name: name)
66
65
  end
67
66
  end
68
67
 
@@ -71,13 +70,19 @@ module ActsAsTaggableOn
71
70
 
72
71
  return [] if list.empty?
73
72
 
74
- existing_tags = Tag.named_any(list)
73
+ existing_tags = named_any(list)
75
74
 
76
75
  list.map do |tag_name|
77
76
  comparable_tag_name = comparable_name(tag_name)
78
- existing_tag = existing_tags.detect { |tag| comparable_name(tag.name) == comparable_tag_name }
79
-
80
- existing_tag || Tag.create(:name => tag_name)
77
+ existing_tag = existing_tags.find { |tag| comparable_name(tag.name) == comparable_tag_name }
78
+ begin
79
+ existing_tag || create(name: tag_name)
80
+ rescue ActiveRecord::RecordNotUnique
81
+ # Postgres aborts the current transaction with
82
+ # PG::InFailedSqlTransaction: ERROR: current transaction is aborted, commands ignored until end of transaction block
83
+ # so we have to rollback this transaction
84
+ raise DuplicateTagError.new("'#{tag_name}' has already been taken")
85
+ end
81
86
  end
82
87
  end
83
88
 
@@ -107,14 +112,14 @@ module ActsAsTaggableOn
107
112
  end
108
113
 
109
114
  def binary
110
- using_mysql? ? "BINARY " : nil
115
+ ActsAsTaggableOn::Utils.using_mysql? ? 'BINARY ' : nil
111
116
  end
112
117
 
113
118
  def unicode_downcase(string)
114
119
  if ActiveSupport::Multibyte::Unicode.respond_to?(:downcase)
115
120
  ActiveSupport::Multibyte::Unicode.downcase(string)
116
121
  else
117
- ActiveSupport::Multibyte::Chars.new(string).downcase.to_s
122
+ ActiveSupport::Multibyte::Chars.new(string).downcase.to_s
118
123
  end
119
124
  end
120
125
 
@@ -8,28 +8,82 @@ module ActsAsTaggableOn
8
8
  add(*args)
9
9
  end
10
10
 
11
- ##
12
- # Returns a new TagList using the given tag string.
13
- #
14
- # Example:
15
- # tag_list = TagList.from("One , Two, Three")
16
- # tag_list # ["One", "Two", "Three"]
17
- def self.from(string)
18
- string = string.join(ActsAsTaggableOn.glue) if string.respond_to?(:join)
19
-
20
- new.tap do |tag_list|
21
- string = string.to_s.dup
11
+ class << self
12
+ ##
13
+ # Returns a new TagList using the given tag string.
14
+ #
15
+ # Example:
16
+ # tag_list = ActsAsTaggableOn::TagList.from("One , Two, Three")
17
+ # tag_list # ["One", "Two", "Three"]
18
+ def from(string)
19
+ string = string.join(ActsAsTaggableOn.glue) if string.respond_to?(:join)
20
+
21
+ new.tap do |tag_list|
22
+ string = string.to_s.dup
23
+
24
+
25
+ string.gsub!(double_quote_pattern) {
26
+ # Append the matched tag to the tag list
27
+ tag_list << Regexp.last_match[2]
28
+ # Return the matched delimiter ($3) to replace the matched items
29
+ ''
30
+ }
31
+
32
+ string.gsub!(single_quote_pattern) {
33
+ # Append the matched tag ($2) to the tag list
34
+ tag_list << Regexp.last_match[2]
35
+ # Return an empty string to replace the matched items
36
+ ''
37
+ }
38
+
39
+ # split the string by the delimiter
40
+ # and add to the tag_list
41
+ tag_list.add(string.split(Regexp.new delimiter))
42
+ end
43
+ end
22
44
 
45
+ def delimiter
23
46
  # Parse the quoted tags
24
47
  d = ActsAsTaggableOn.delimiter
25
- d = d.join("|") if d.kind_of?(Array)
26
- string.gsub!(/(\A|#{d})\s*"(.*?)"\s*(#{d}\s*|\z)/) { tag_list << $2; $3 }
27
- string.gsub!(/(\A|#{d})\s*'(.*?)'\s*(#{d}\s*|\z)/) { tag_list << $2; $3 }
48
+ # Separate multiple delimiters by bitwise operator
49
+ d = d.join('|') if d.kind_of?(Array)
28
50
 
29
- tag_list.add(string.split(Regexp.new d))
51
+ d
30
52
  end
31
- end
32
53
 
54
+ def single_quote_pattern
55
+ %r{
56
+ ( # Tag start delimiter ($1)
57
+ \A | # Either string start or
58
+ #{delimiter} # a delimiter
59
+ )
60
+ \s*' # quote (') optionally preceded by whitespace
61
+ (.*?) # Tag ($2)
62
+ '\s* # quote (') optionally followed by whitespace
63
+ (?= # Tag end delimiter (not consumed; is zero-length lookahead)
64
+ #{delimiter}\s* | # Either a delimiter optionally followed by whitespace or
65
+ \z # string end
66
+ )
67
+ }x
68
+ end
69
+
70
+ def double_quote_pattern
71
+ %r{
72
+ ( # Tag start delimiter ($1)
73
+ \A | # Either string start or
74
+ #{delimiter} # a delimiter
75
+ )
76
+ \s*" # quote (") optionally preceded by whitespace
77
+ (.*?) # Tag ($2)
78
+ "\s* # quote (") optionally followed by whitespace
79
+ (?= # Tag end delimiter (not consumed; is zero-length lookahead)
80
+ #{delimiter}\s* | # Either a delimiter optionally followed by whitespace or
81
+ \z # string end
82
+ )
83
+ }x
84
+ end
85
+
86
+ end
33
87
  ##
34
88
  # Add tags to the tag_list. Duplicate or blank tags will be ignored.
35
89
  # Use the <tt>:parse</tt> option to add an unparsed tag string.
@@ -44,6 +98,24 @@ module ActsAsTaggableOn
44
98
  self
45
99
  end
46
100
 
101
+ # Append---Add the tag to the tag_list. This
102
+ # expression returns the tag_list itself, so several appends
103
+ # may be chained together.
104
+ def <<(obj)
105
+ add(obj)
106
+ end
107
+
108
+ # Concatenation --- Returns a new tag list built by concatenating the
109
+ # two tag lists together to produce a third tag list.
110
+ def +(other_tag_list)
111
+ TagList.new.add(self).add(other_tag_list)
112
+ end
113
+
114
+ # Appends the elements of +other_tag_list+ to +self+.
115
+ def concat(other_tag_list)
116
+ super(other_tag_list).send(:clean!)
117
+ end
118
+
47
119
  ##
48
120
  # Remove specific tags from the tag_list.
49
121
  # Use the <tt>:parse</tt> option to add an unparsed tag string.
@@ -70,32 +142,35 @@ module ActsAsTaggableOn
70
142
 
71
143
  tags.map do |name|
72
144
  d = ActsAsTaggableOn.delimiter
73
- d = Regexp.new d.join("|") if d.kind_of? Array
145
+ d = Regexp.new d.join('|') if d.kind_of? Array
74
146
  name.index(d) ? "\"#{name}\"" : name
75
147
  end.join(ActsAsTaggableOn.glue)
76
148
  end
77
149
 
78
150
  private
79
151
 
80
- # Remove whitespace, duplicates, and blanks.
152
+ # Convert everything to string, remove whitespace, duplicates, and blanks.
81
153
  def clean!
82
154
  reject!(&:blank?)
155
+ map!(&:to_s)
83
156
  map!(&:strip)
84
- map!{ |tag| tag.mb_chars.downcase.to_s } if ActsAsTaggableOn.force_lowercase
157
+ map! { |tag| tag.mb_chars.downcase.to_s } if ActsAsTaggableOn.force_lowercase
85
158
  map!(&:parameterize) if ActsAsTaggableOn.force_parameterize
86
159
 
87
160
  uniq!
88
161
  end
89
162
 
163
+
90
164
  def extract_and_apply_options!(args)
91
165
  options = args.last.is_a?(Hash) ? args.pop : {}
92
166
  options.assert_valid_keys :parse
93
167
 
94
- if options[:parse]
95
- args.map! { |a| self.class.from(a) }
96
- end
168
+ args.map! { |a| self.class.from(a) } if options[:parse]
97
169
 
98
170
  args.flatten!
99
171
  end
172
+
173
+
100
174
  end
101
175
  end
176
+
@@ -39,7 +39,6 @@ module ActsAsTaggableOn
39
39
  taggable_on(false, tag_types)
40
40
  end
41
41
 
42
-
43
42
  ##
44
43
  # Make a model taggable on specified contexts
45
44
  # and preserves the order in which tags are created
@@ -59,47 +58,44 @@ module ActsAsTaggableOn
59
58
  # Make a model taggable on specified contexts
60
59
  # and optionally preserves the order in which tags are created
61
60
  #
62
- # Seperate methods used above for backwards compatibility
61
+ # Separate methods used above for backwards compatibility
63
62
  # so that the original acts_as_taggable_on method is unaffected
64
- # as it's not possible to add another arguement to the method
63
+ # as it's not possible to add another argument to the method
65
64
  # without the tag_types being enclosed in square brackets
66
65
  #
67
66
  # NB: method overridden in core module in order to create tag type
68
67
  # associations and methods after this logic has executed
69
68
  #
70
- def taggable_on(preserve_tag_order, *tag_types)
71
- tag_types = tag_types.to_a.flatten.compact.map(&:to_sym)
72
-
73
- if taggable?
74
- self.tag_types = (self.tag_types + tag_types).uniq
75
- self.preserve_tag_order = preserve_tag_order
76
- else
77
- class_attribute :tag_types
78
- self.tag_types = tag_types
79
- class_attribute :preserve_tag_order
80
- self.preserve_tag_order = preserve_tag_order
69
+ def taggable_on(preserve_tag_order, *tag_types)
70
+ tag_types = tag_types.to_a.flatten.compact.map(&:to_sym)
81
71
 
82
- class_eval do
83
- has_many :taggings, :as => :taggable, :dependent => :destroy, :class_name => "ActsAsTaggableOn::Tagging"
84
- has_many :base_tags, :through => :taggings, :source => :tag, :class_name => "ActsAsTaggableOn::Tag"
72
+ if taggable?
73
+ self.tag_types = (self.tag_types + tag_types).uniq
74
+ self.preserve_tag_order = preserve_tag_order
75
+ else
76
+ class_attribute :tag_types
77
+ self.tag_types = tag_types
78
+ class_attribute :preserve_tag_order
79
+ self.preserve_tag_order = preserve_tag_order
85
80
 
86
- def self.taggable?
87
- true
88
- end
81
+ class_eval do
82
+ has_many :taggings, as: :taggable, dependent: :destroy, class_name: 'ActsAsTaggableOn::Tagging'
83
+ has_many :base_tags, through: :taggings, source: :tag, class_name: 'ActsAsTaggableOn::Tag'
89
84
 
90
- extend ActsAsTaggableOn::Utils
85
+ def self.taggable?
86
+ true
91
87
  end
92
88
  end
93
-
94
- # each of these add context-specific methods and must be
95
- # called on each call of taggable_on
96
- include ActsAsTaggableOn::Taggable::Core
97
- include ActsAsTaggableOn::Taggable::Collection
98
- include ActsAsTaggableOn::Taggable::Cache
99
- include ActsAsTaggableOn::Taggable::Ownership
100
- include ActsAsTaggableOn::Taggable::Related
101
- include ActsAsTaggableOn::Taggable::Dirty
102
89
  end
103
90
 
91
+ # each of these add context-specific methods and must be
92
+ # called on each call of taggable_on
93
+ include ActsAsTaggableOn::Taggable::Core
94
+ include ActsAsTaggableOn::Taggable::Collection
95
+ include ActsAsTaggableOn::Taggable::Cache
96
+ include ActsAsTaggableOn::Taggable::Ownership
97
+ include ActsAsTaggableOn::Taggable::Related
98
+ include ActsAsTaggableOn::Taggable::Dirty
99
+ end
104
100
  end
105
101
  end
@@ -15,27 +15,31 @@ module ActsAsTaggableOn
15
15
  # end
16
16
  def acts_as_tagger(opts={})
17
17
  class_eval do
18
- has_many_with_compatibility :owned_taggings,
19
- opts.merge(
20
- :as => :tagger,
21
- :dependent => :destroy,
22
- :class_name => "ActsAsTaggableOn::Tagging"
23
- )
18
+ has_many_with_taggable_compatibility :owned_taggings,
19
+ opts.merge(
20
+ as: :tagger,
21
+ dependent: :destroy,
22
+ class_name: 'ActsAsTaggableOn::Tagging'
23
+ )
24
24
 
25
- has_many_with_compatibility :owned_tags,
26
- :through => :owned_taggings,
27
- :source => :tag,
28
- :class_name => "ActsAsTaggableOn::Tag",
29
- :uniq => true
25
+ has_many_with_taggable_compatibility :owned_tags,
26
+ through: :owned_taggings,
27
+ source: :tag,
28
+ class_name: 'ActsAsTaggableOn::Tag',
29
+ uniq: true
30
30
  end
31
31
 
32
32
  include ActsAsTaggableOn::Tagger::InstanceMethods
33
33
  extend ActsAsTaggableOn::Tagger::SingletonMethods
34
34
  end
35
35
 
36
- def is_tagger?
36
+ def tagger?
37
37
  false
38
38
  end
39
+
40
+ def is_tagger?
41
+ tagger?
42
+ end
39
43
  end
40
44
 
41
45
  module InstanceMethods
@@ -50,27 +54,35 @@ module ActsAsTaggableOn
50
54
  # Example:
51
55
  # @user.tag(@photo, :with => "paris, normandy", :on => :locations)
52
56
  def tag(taggable, opts={})
53
- opts.reverse_merge!(:force => true)
57
+ opts.reverse_merge!(force: true)
54
58
  skip_save = opts.delete(:skip_save)
55
59
  return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
56
60
 
57
- raise "You need to specify a tag context using :on" unless opts.has_key?(:on)
58
- raise "You need to specify some tags using :with" unless opts.has_key?(:with)
59
- raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless (opts[:force] || taggable.tag_types.include?(opts[:on]))
61
+ fail 'You need to specify a tag context using :on' unless opts.key?(:on)
62
+ fail 'You need to specify some tags using :with' unless opts.key?(:with)
63
+ fail "No context :#{opts[:on]} defined in #{taggable.class}" unless opts[:force] || taggable.tag_types.include?(opts[:on])
60
64
 
61
65
  taggable.set_owner_tag_list_on(self, opts[:on].to_s, opts[:with])
62
66
  taggable.save unless skip_save
63
67
  end
64
68
 
65
- def is_tagger?
69
+ def tagger?
66
70
  self.class.is_tagger?
67
71
  end
72
+
73
+ def is_tagger?
74
+ tagger?
75
+ end
68
76
  end
69
77
 
70
78
  module SingletonMethods
71
- def is_tagger?
79
+ def tagger?
72
80
  true
73
81
  end
82
+
83
+ def is_tagger?
84
+ tagger?
85
+ end
74
86
  end
75
87
  end
76
88
  end
@@ -1,5 +1,6 @@
1
1
  module ActsAsTaggableOn
2
2
  class Tagging < ::ActiveRecord::Base #:nodoc:
3
+ #TODO, remove from 4.0.0
3
4
  attr_accessible :tag,
4
5
  :tag_id,
5
6
  :context,
@@ -9,14 +10,15 @@ module ActsAsTaggableOn
9
10
  :tagger,
10
11
  :tagger_type,
11
12
  :tagger_id if defined?(ActiveModel::MassAssignmentSecurity)
12
- belongs_to :tag, :class_name => 'ActsAsTaggableOn::Tag' , :counter_cache => true
13
- belongs_to :taggable, :polymorphic => true
14
- belongs_to :tagger, :polymorphic => true
13
+
14
+ belongs_to :tag, class_name: 'ActsAsTaggableOn::Tag' , counter_cache: true
15
+ belongs_to :taggable, polymorphic: true
16
+ belongs_to :tagger, polymorphic: true
15
17
 
16
18
  validates_presence_of :context
17
19
  validates_presence_of :tag_id
18
20
 
19
- validates_uniqueness_of :tag_id, :scope => [:taggable_type, :taggable_id, :context, :tagger_id, :tagger_type]
21
+ validates_uniqueness_of :tag_id, scope: [:taggable_type, :taggable_id, :context, :tagger_id, :tagger_type]
20
22
 
21
23
  after_destroy :remove_unused_tags
22
24
 
@@ -24,10 +26,7 @@ module ActsAsTaggableOn
24
26
 
25
27
  def remove_unused_tags
26
28
  if ActsAsTaggableOn.remove_unused_tags
27
- # TODO: use taggings_count in release 4.0.0
28
- if tag.taggings.count.zero?
29
- tag.destroy
30
- end
29
+ tag.destroy if tag.taggings_count.zero?
31
30
  end
32
31
  end
33
32
  end
@@ -1,6 +1,6 @@
1
1
  module ActsAsTaggableOn
2
2
  module TagsHelper
3
- # See the README for an example using tag_cloud.
3
+ # See the wiki for an example using tag_cloud.
4
4
  def tag_cloud(tags, classes)
5
5
  return [] if tags.empty?
6
6
 
@@ -1,40 +1,62 @@
1
1
  module ActsAsTaggableOn
2
2
  module Utils
3
-
4
- def connection
5
- ::ActiveRecord::Base.connection
6
- end
7
-
8
- def using_postgresql?
9
- connection && connection.adapter_name == 'PostgreSQL'
10
- end
11
-
12
- def using_sqlite?
13
- connection && connection.adapter_name == 'SQLite'
14
- end
15
-
16
- def using_mysql?
17
- #We should probably use regex for mysql to support prehistoric adapters
18
- connection && connection.adapter_name == 'Mysql2'
19
- end
20
-
21
- def using_case_insensitive_collation?
22
- using_mysql? && ::ActiveRecord::Base.connection.collation =~ /_ci\Z/
23
- end
24
-
25
- def sha_prefix(string)
26
- Digest::SHA1.hexdigest("#{string}#{rand}")[0..6]
27
- end
28
-
29
- private
30
-
31
- def like_operator
32
- using_postgresql? ? 'ILIKE' : 'LIKE'
33
- end
34
-
35
- # escape _ and % characters in strings, since these are wildcards in SQL.
36
- def escape_like(str)
37
- str.gsub(/[!%_]/) { |x| '!' + x }
3
+ class << self
4
+ # Use ActsAsTaggableOn::Tag connection
5
+ def connection
6
+ ActsAsTaggableOn::Tag.connection
7
+ end
8
+
9
+ def using_postgresql?
10
+ connection && connection.adapter_name == 'PostgreSQL'
11
+ end
12
+
13
+ def postgresql_version
14
+ if using_postgresql?
15
+ connection.execute('SHOW SERVER_VERSION').first['server_version'].to_f
16
+ end
17
+ end
18
+
19
+ def postgresql_support_json?
20
+ postgresql_version >= 9.2
21
+ end
22
+
23
+ def using_sqlite?
24
+ connection && connection.adapter_name == 'SQLite'
25
+ end
26
+
27
+ def using_mysql?
28
+ #We should probably use regex for mysql to support prehistoric adapters
29
+ connection && connection.adapter_name == 'Mysql2'
30
+ end
31
+
32
+ def using_case_insensitive_collation?
33
+ using_mysql? && connection.collation =~ /_ci\Z/
34
+ end
35
+
36
+ def supports_concurrency?
37
+ !using_sqlite?
38
+ end
39
+
40
+ def sha_prefix(string)
41
+ Digest::SHA1.hexdigest("#{string}#{rand}")[0..6]
42
+ end
43
+
44
+ def active_record4?
45
+ ::ActiveRecord::VERSION::MAJOR == 4
46
+ end
47
+
48
+ def active_record42?
49
+ active_record4? && ::ActiveRecord::VERSION::MINOR >= 2
50
+ end
51
+
52
+ def like_operator
53
+ using_postgresql? ? 'ILIKE' : 'LIKE'
54
+ end
55
+
56
+ # escape _ and % characters in strings, since these are wildcards in SQL.
57
+ def escape_like(str)
58
+ str.gsub(/[!%_]/) { |x| '!' + x }
59
+ end
38
60
  end
39
61
  end
40
62
  end
@@ -1,4 +1,4 @@
1
1
  module ActsAsTaggableOn
2
- VERSION = '3.1.1'
2
+ VERSION = '3.2.2'
3
3
  end
4
4