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.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.travis.yml +9 -7
- data/Appraisals +13 -8
- data/CHANGELOG.md +8 -0
- data/Gemfile +1 -2
- data/README.md +23 -13
- data/Rakefile +5 -17
- data/UPGRADING.md +6 -0
- data/acts-as-taggable-on.gemspec +13 -13
- data/db/migrate/1_acts_as_taggable_on_migration.rb +3 -3
- data/db/migrate/2_add_missing_unique_indices.rb +3 -5
- data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +1 -1
- data/gemfiles/activerecord_3.2.gemfile +15 -0
- data/gemfiles/activerecord_4.0.gemfile +15 -0
- data/gemfiles/activerecord_4.1.gemfile +15 -0
- data/gemfiles/activerecord_edge.gemfile +16 -0
- data/lib/acts-as-taggable-on.rb +23 -21
- data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +1 -4
- data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +29 -20
- data/lib/acts_as_taggable_on/acts_as_taggable_on/compatibility.rb +11 -10
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +98 -80
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +5 -12
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +7 -7
- data/lib/acts_as_taggable_on/engine.rb +0 -1
- data/lib/acts_as_taggable_on/tag.rb +24 -19
- data/lib/acts_as_taggable_on/tag_list.rb +95 -21
- data/lib/acts_as_taggable_on/taggable.rb +28 -30
- data/lib/acts_as_taggable_on/tagger.rb +30 -18
- data/lib/acts_as_taggable_on/tagging.rb +7 -8
- data/lib/acts_as_taggable_on/tags_helper.rb +1 -1
- data/lib/acts_as_taggable_on/utils.rb +25 -3
- data/lib/acts_as_taggable_on/version.rb +1 -1
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +133 -138
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +55 -58
- data/spec/acts_as_taggable_on/caching_spec.rb +34 -35
- data/spec/acts_as_taggable_on/related_spec.rb +59 -113
- data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +118 -95
- data/spec/acts_as_taggable_on/tag_list_spec.rb +89 -57
- data/spec/acts_as_taggable_on/tag_spec.rb +125 -114
- data/spec/acts_as_taggable_on/taggable_spec.rb +538 -352
- data/spec/acts_as_taggable_on/tagger_spec.rb +81 -78
- data/spec/acts_as_taggable_on/tagging_spec.rb +13 -14
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +25 -25
- data/spec/acts_as_taggable_on/utils_spec.rb +9 -9
- data/spec/internal/app/models/altered_inheriting_taggable_model.rb +3 -0
- data/spec/internal/app/models/cached_model.rb +3 -0
- data/spec/internal/app/models/cached_model_with_array.rb +5 -0
- data/spec/internal/app/models/company.rb +15 -0
- data/spec/internal/app/models/inheriting_taggable_model.rb +2 -0
- data/spec/internal/app/models/market.rb +2 -0
- data/spec/{models.rb → internal/app/models/models.rb} +34 -2
- data/spec/internal/app/models/non_standard_id_taggable_model.rb +8 -0
- data/spec/internal/app/models/ordered_taggable_model.rb +4 -0
- data/spec/internal/app/models/other_cached_model.rb +3 -0
- data/spec/internal/app/models/other_taggable_model.rb +4 -0
- data/spec/internal/app/models/student.rb +2 -0
- data/spec/internal/app/models/taggable_model.rb +13 -0
- data/spec/internal/app/models/untaggable_model.rb +3 -0
- data/spec/internal/app/models/user.rb +3 -0
- data/spec/{database.yml.sample → internal/config/database.yml.sample} +2 -2
- data/spec/internal/db/schema.rb +97 -0
- data/spec/schema.rb +11 -0
- data/spec/spec_helper.rb +9 -62
- data/spec/support/array.rb +9 -0
- data/spec/support/database.rb +42 -0
- data/spec/support/database_cleaner.rb +17 -0
- metadata +101 -37
- data/gemfiles/rails_3.2.gemfile +0 -7
- data/gemfiles/rails_4.0.gemfile +0 -7
- data/gemfiles/rails_4.1.gemfile +0 -7
- 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 =
|
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
|
-
|
112
|
-
|
113
|
-
:
|
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!(:
|
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).
|
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
|
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).
|
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
|
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
|
-
|
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::
|
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(
|
67
|
+
order('count DESC').
|
68
68
|
where(conditions)
|
69
69
|
end
|
70
70
|
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, :
|
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, :
|
16
|
-
validates_length_of :name, :
|
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([
|
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(
|
36
|
+
}.join(' OR ')
|
38
37
|
where(clause)
|
39
38
|
else
|
40
39
|
clause = list.map { |tag|
|
41
|
-
sanitize_sql([
|
42
|
-
}.join(
|
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(
|
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
|
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(:
|
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 =
|
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.
|
79
|
-
|
80
|
-
|
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? ?
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
26
|
-
|
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
|
-
|
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(
|
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
|
-
#
|
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
|
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
|
-
|
71
|
-
|
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
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
85
|
+
def self.taggable?
|
86
|
+
true
|
91
87
|
end
|
92
|
-
end
|
93
88
|
|
94
|
-
|
95
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
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!(:
|
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
|
-
|
58
|
-
|
59
|
-
|
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
|
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
|
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
|