acts-as-taggable-on 3.1.1 → 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) 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 +8 -0
  6. data/Gemfile +1 -2
  7. data/README.md +23 -13
  8. data/Rakefile +5 -17
  9. data/UPGRADING.md +6 -0
  10. data/acts-as-taggable-on.gemspec +13 -13
  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 +1 -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 +98 -80
  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 +7 -7
  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 +95 -21
  28. data/lib/acts_as_taggable_on/taggable.rb +28 -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 +25 -3
  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 +125 -114
  41. data/spec/acts_as_taggable_on/taggable_spec.rb +538 -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 +9 -9
  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 +17 -0
  68. metadata +101 -37
  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
@@ -79,7 +79,7 @@ module ActsAsTaggableOn::Taggable
79
79
  cached_owned_tag_list_on(context).each do |owner, tag_list|
80
80
 
81
81
  # Find existing tags or create non-existing tags:
82
- tags = ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list.uniq)
82
+ tags = find_or_create_tags_from_list_with_context(tag_list.uniq, context)
83
83
 
84
84
  # Tag objects for owned tags
85
85
  owned_tags = owner_tags_on(owner, context)
@@ -108,20 +108,13 @@ module ActsAsTaggableOn::Taggable
108
108
 
109
109
  # Find all taggings that belong to the taggable (self), are owned by the owner,
110
110
  # have the correct context, and are removed from the list.
111
- if old_tags.present?
112
- old_taggings = ActsAsTaggableOn::Tagging.where(:taggable_id => id, :taggable_type => self.class.base_class.to_s,
113
- :tagger_type => owner.class.base_class.to_s, :tagger_id => owner.id,
114
- :tag_id => old_tags, :context => context)
115
- end
116
-
117
- # Destroy old taggings:
118
- if old_taggings.present?
119
- ActsAsTaggableOn::Tagging.destroy_all(:id => old_taggings.map(&:id))
120
- end
111
+ ActsAsTaggableOn::Tagging.destroy_all(taggable_id: id, taggable_type: self.class.base_class.to_s,
112
+ tagger_type: owner.class.base_class.to_s, tagger_id: owner.id,
113
+ tag_id: old_tags, context: context) if old_tags.present?
121
114
 
122
115
  # Create new taggings:
123
116
  new_tags.each do |tag|
124
- taggings.create!(:tag_id => tag.id, :context => context.to_s, :tagger => owner, :taggable => self)
117
+ taggings.create!(tag_id: tag.id, context: context.to_s, tagger: owner, taggable: self)
125
118
  end
126
119
  end
127
120
  end
@@ -36,24 +36,24 @@ module ActsAsTaggableOn::Taggable
36
36
  end
37
37
 
38
38
  def matching_contexts_for(search_context, result_context, klass, options = {})
39
- tags_to_find = tags_on(search_context).collect { |t| t.name }
40
- related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context])
39
+ 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} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context])
41
41
  end
42
42
 
43
43
  def related_tags_for(context, klass, options = {})
44
44
  tags_to_ignore = Array.wrap(options.delete(:ignore)).map(&:to_s) || []
45
- tags_to_find = tags_on(context).collect { |t| t.name }.reject { |t| tags_to_ignore.include? t }
46
- related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find])
45
+ 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} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find])
47
47
  end
48
48
 
49
49
  private
50
50
 
51
51
  def exclude_self(klass, id)
52
- "#{klass.table_name}.#{klass.primary_key} != #{id} AND" if [self.class.base_class, self.class].include? klass
52
+ "#{klass.table_name}.#{klass.primary_key} != #{id} AND" if [self.class.base_class, self.class].include? klass
53
53
  end
54
54
 
55
55
  def group_columns(klass)
56
- if ActsAsTaggableOn::Tag.using_postgresql?
56
+ if ActsAsTaggableOn::Utils.using_postgresql?
57
57
  grouped_column_names_for(klass)
58
58
  else
59
59
  "#{klass.table_name}.#{klass.primary_key}"
@@ -64,7 +64,7 @@ module ActsAsTaggableOn::Taggable
64
64
  klass.select("#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count").
65
65
  from("#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}").
66
66
  group(group_columns(klass)).
67
- order("count DESC").
67
+ order('count DESC').
68
68
  where(conditions)
69
69
  end
70
70
  end
@@ -1,6 +1,5 @@
1
1
  require 'rails/engine'
2
2
  module ActsAsTaggableOn
3
3
  class Engine < Rails::Engine
4
-
5
4
  end
6
5
  end
@@ -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,7 +142,7 @@ 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
@@ -81,21 +153,23 @@ module ActsAsTaggableOn
81
153
  def clean!
82
154
  reject!(&:blank?)
83
155
  map!(&:strip)
84
- map!{ |tag| tag.mb_chars.downcase.to_s } if ActsAsTaggableOn.force_lowercase
156
+ map! { |tag| tag.mb_chars.downcase.to_s } if ActsAsTaggableOn.force_lowercase
85
157
  map!(&:parameterize) if ActsAsTaggableOn.force_parameterize
86
158
 
87
159
  uniq!
88
160
  end
89
161
 
162
+
90
163
  def extract_and_apply_options!(args)
91
164
  options = args.last.is_a?(Hash) ? args.pop : {}
92
165
  options.assert_valid_keys :parse
93
166
 
94
- if options[:parse]
95
- args.map! { |a| self.class.from(a) }
96
- end
167
+ args.map! { |a| self.class.from(a) } if options[:parse]
97
168
 
98
169
  args.flatten!
99
170
  end
171
+
172
+
100
173
  end
101
174
  end
175
+
@@ -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,46 @@ 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
- end
93
88
 
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
89
+ extend ActsAsTaggableOn::Utils
90
+ end
102
91
  end
103
92
 
93
+ # each of these add context-specific methods and must be
94
+ # called on each call of taggable_on
95
+ include ActsAsTaggableOn::Taggable::Core
96
+ include ActsAsTaggableOn::Taggable::Collection
97
+ include ActsAsTaggableOn::Taggable::Cache
98
+ include ActsAsTaggableOn::Taggable::Ownership
99
+ include ActsAsTaggableOn::Taggable::Related
100
+ include ActsAsTaggableOn::Taggable::Dirty
101
+ end
104
102
  end
105
103
  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