acts-as-taggable-on 3.1.1 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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