sb-acts-as-taggable-on 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +39 -0
- data/Appraisals +15 -0
- data/CHANGELOG.md +330 -0
- data/CONTRIBUTING.md +57 -0
- data/Gemfile +11 -0
- data/Guardfile +5 -0
- data/LICENSE.md +20 -0
- data/README.md +555 -0
- data/Rakefile +21 -0
- data/UPGRADING.md +8 -0
- data/acts-as-taggable-on.gemspec +32 -0
- data/db/migrate/1_acts_as_taggable_on_migration.rb +36 -0
- data/db/migrate/2_add_missing_unique_indices.rb +25 -0
- data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +19 -0
- data/db/migrate/4_add_missing_taggable_index.rb +14 -0
- data/db/migrate/5_change_collation_for_tag_names.rb +14 -0
- data/db/migrate/6_add_missing_indexes_on_taggings.rb +22 -0
- data/gemfiles/activerecord_5.0.gemfile +21 -0
- data/gemfiles/activerecord_5.1.gemfile +21 -0
- data/gemfiles/activerecord_5.2.gemfile +21 -0
- data/gemfiles/activerecord_6.0.gemfile +21 -0
- data/lib/acts-as-taggable-on.rb +133 -0
- data/lib/acts_as_taggable_on.rb +6 -0
- data/lib/acts_as_taggable_on/default_parser.rb +79 -0
- data/lib/acts_as_taggable_on/engine.rb +4 -0
- data/lib/acts_as_taggable_on/generic_parser.rb +19 -0
- data/lib/acts_as_taggable_on/tag.rb +139 -0
- data/lib/acts_as_taggable_on/tag_list.rb +106 -0
- data/lib/acts_as_taggable_on/taggable.rb +101 -0
- data/lib/acts_as_taggable_on/taggable/cache.rb +90 -0
- data/lib/acts_as_taggable_on/taggable/collection.rb +183 -0
- data/lib/acts_as_taggable_on/taggable/core.rb +322 -0
- data/lib/acts_as_taggable_on/taggable/ownership.rb +136 -0
- data/lib/acts_as_taggable_on/taggable/related.rb +71 -0
- data/lib/acts_as_taggable_on/taggable/tag_list_type.rb +4 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query.rb +16 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb +111 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +70 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb +82 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +61 -0
- data/lib/acts_as_taggable_on/tagger.rb +89 -0
- data/lib/acts_as_taggable_on/tagging.rb +36 -0
- data/lib/acts_as_taggable_on/tags_helper.rb +15 -0
- data/lib/acts_as_taggable_on/utils.rb +37 -0
- data/lib/acts_as_taggable_on/version.rb +3 -0
- data/lib/tasks/tags_collate_utf8.rake +21 -0
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +285 -0
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +112 -0
- data/spec/acts_as_taggable_on/caching_spec.rb +129 -0
- data/spec/acts_as_taggable_on/default_parser_spec.rb +47 -0
- data/spec/acts_as_taggable_on/dirty_spec.rb +142 -0
- data/spec/acts_as_taggable_on/generic_parser_spec.rb +14 -0
- data/spec/acts_as_taggable_on/related_spec.rb +99 -0
- data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +231 -0
- data/spec/acts_as_taggable_on/tag_list_spec.rb +176 -0
- data/spec/acts_as_taggable_on/tag_spec.rb +340 -0
- data/spec/acts_as_taggable_on/taggable_spec.rb +817 -0
- data/spec/acts_as_taggable_on/tagger_spec.rb +153 -0
- data/spec/acts_as_taggable_on/tagging_spec.rb +117 -0
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +45 -0
- data/spec/acts_as_taggable_on/utils_spec.rb +23 -0
- data/spec/internal/app/models/altered_inheriting_taggable_model.rb +5 -0
- data/spec/internal/app/models/cached_model.rb +3 -0
- data/spec/internal/app/models/cached_model_with_array.rb +11 -0
- data/spec/internal/app/models/columns_override_model.rb +5 -0
- data/spec/internal/app/models/company.rb +15 -0
- data/spec/internal/app/models/inheriting_taggable_model.rb +4 -0
- data/spec/internal/app/models/market.rb +2 -0
- 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 +4 -0
- data/spec/internal/app/models/taggable_model.rb +14 -0
- data/spec/internal/app/models/untaggable_model.rb +3 -0
- data/spec/internal/app/models/user.rb +3 -0
- data/spec/internal/config/database.yml.sample +19 -0
- data/spec/internal/db/schema.rb +110 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/0-helpers.rb +32 -0
- data/spec/support/array.rb +9 -0
- data/spec/support/database.rb +36 -0
- data/spec/support/database_cleaner.rb +21 -0
- metadata +269 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActsAsTaggableOn
|
2
|
+
##
|
3
|
+
# Returns a new TagList using the given tag string.
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
# tag_list = ActsAsTaggableOn::GenericParser.new.parse("One , Two, Three")
|
7
|
+
# tag_list # ["One", "Two", "Three"]
|
8
|
+
class GenericParser
|
9
|
+
def initialize(tag_list)
|
10
|
+
@tag_list = tag_list
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse
|
14
|
+
TagList.new.tap do |tag_list|
|
15
|
+
tag_list.add @tag_list.split(',').map(&:strip).reject(&:empty?)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module ActsAsTaggableOn
|
3
|
+
class Tag < ::ActiveRecord::Base
|
4
|
+
self.table_name = ActsAsTaggableOn.tags_table
|
5
|
+
|
6
|
+
### ASSOCIATIONS:
|
7
|
+
|
8
|
+
has_many :taggings, dependent: :destroy, class_name: '::ActsAsTaggableOn::Tagging'
|
9
|
+
|
10
|
+
### VALIDATIONS:
|
11
|
+
|
12
|
+
validates_presence_of :name
|
13
|
+
validates_uniqueness_of :name, if: :validates_name_uniqueness?, case_sensitive: true
|
14
|
+
validates_length_of :name, maximum: 255
|
15
|
+
|
16
|
+
# monkey patch this method if don't need name uniqueness validation
|
17
|
+
def validates_name_uniqueness?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
### SCOPES:
|
22
|
+
scope :most_used, ->(limit = 20) { order('taggings_count desc').limit(limit) }
|
23
|
+
scope :least_used, ->(limit = 20) { order('taggings_count asc').limit(limit) }
|
24
|
+
|
25
|
+
def self.named(name)
|
26
|
+
if ActsAsTaggableOn.strict_case_match
|
27
|
+
where(["name = #{binary}?", as_8bit_ascii(name)])
|
28
|
+
else
|
29
|
+
where(['LOWER(name) = LOWER(?)', as_8bit_ascii(unicode_downcase(name))])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.named_any(list)
|
34
|
+
clause = list.map { |tag|
|
35
|
+
sanitize_sql_for_named_any(tag).force_encoding('BINARY')
|
36
|
+
}.join(' OR ')
|
37
|
+
where(clause)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.named_like(name)
|
41
|
+
clause = ["name #{ActsAsTaggableOn::Utils.like_operator} ? ESCAPE '!'", "%#{ActsAsTaggableOn::Utils.escape_like(name)}%"]
|
42
|
+
where(clause)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.named_like_any(list)
|
46
|
+
clause = list.map { |tag|
|
47
|
+
sanitize_sql(["name #{ActsAsTaggableOn::Utils.like_operator} ? ESCAPE '!'", "%#{ActsAsTaggableOn::Utils.escape_like(tag.to_s)}%"])
|
48
|
+
}.join(' OR ')
|
49
|
+
where(clause)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.for_context(context)
|
53
|
+
joins(:taggings).
|
54
|
+
where(["#{ActsAsTaggableOn.taggings_table}.context = ?", context]).
|
55
|
+
select("DISTINCT #{ActsAsTaggableOn.tags_table}.*")
|
56
|
+
end
|
57
|
+
|
58
|
+
### CLASS METHODS:
|
59
|
+
|
60
|
+
def self.find_or_create_with_like_by_name(name)
|
61
|
+
if ActsAsTaggableOn.strict_case_match
|
62
|
+
self.find_or_create_all_with_like_by_name([name]).first
|
63
|
+
else
|
64
|
+
named_like(name).first || create(name: name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.find_or_create_all_with_like_by_name(*list)
|
69
|
+
list = Array(list).flatten
|
70
|
+
|
71
|
+
return [] if list.empty?
|
72
|
+
|
73
|
+
existing_tags = named_any(list)
|
74
|
+
list.map do |tag_name|
|
75
|
+
begin
|
76
|
+
tries ||= 3
|
77
|
+
comparable_tag_name = comparable_name(tag_name)
|
78
|
+
existing_tag = existing_tags.find { |tag| comparable_name(tag.name) == comparable_tag_name }
|
79
|
+
existing_tag || create(name: tag_name)
|
80
|
+
rescue ActiveRecord::RecordNotUnique
|
81
|
+
if (tries -= 1).positive?
|
82
|
+
ActiveRecord::Base.connection.execute 'ROLLBACK'
|
83
|
+
existing_tags = named_any(list)
|
84
|
+
retry
|
85
|
+
end
|
86
|
+
|
87
|
+
raise DuplicateTagError.new("'#{tag_name}' has already been taken")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
### INSTANCE METHODS:
|
93
|
+
|
94
|
+
def ==(object)
|
95
|
+
super || (object.is_a?(Tag) && name == object.name)
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_s
|
99
|
+
name
|
100
|
+
end
|
101
|
+
|
102
|
+
def count
|
103
|
+
read_attribute(:count).to_i
|
104
|
+
end
|
105
|
+
|
106
|
+
class << self
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def comparable_name(str)
|
111
|
+
if ActsAsTaggableOn.strict_case_match
|
112
|
+
str
|
113
|
+
else
|
114
|
+
unicode_downcase(str.to_s)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def binary
|
119
|
+
ActsAsTaggableOn::Utils.using_mysql? ? 'BINARY ' : nil
|
120
|
+
end
|
121
|
+
|
122
|
+
def as_8bit_ascii(string)
|
123
|
+
string.to_s.mb_chars
|
124
|
+
end
|
125
|
+
|
126
|
+
def unicode_downcase(string)
|
127
|
+
as_8bit_ascii(string).downcase
|
128
|
+
end
|
129
|
+
|
130
|
+
def sanitize_sql_for_named_any(tag)
|
131
|
+
if ActsAsTaggableOn.strict_case_match
|
132
|
+
sanitize_sql(["name = #{binary}?", as_8bit_ascii(tag)])
|
133
|
+
else
|
134
|
+
sanitize_sql(['LOWER(name) = LOWER(?)', as_8bit_ascii(unicode_downcase(tag))])
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
3
|
+
|
4
|
+
module ActsAsTaggableOn
|
5
|
+
class TagList < Array
|
6
|
+
attr_accessor :owner
|
7
|
+
attr_accessor :parser
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
@parser = ActsAsTaggableOn.default_parser
|
11
|
+
add(*args)
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Add tags to the tag_list. Duplicate or blank tags will be ignored.
|
16
|
+
# Use the <tt>:parse</tt> option to add an unparsed tag string.
|
17
|
+
#
|
18
|
+
# Example:
|
19
|
+
# tag_list.add("Fun", "Happy")
|
20
|
+
# tag_list.add("Fun, Happy", :parse => true)
|
21
|
+
def add(*names)
|
22
|
+
extract_and_apply_options!(names)
|
23
|
+
concat(names)
|
24
|
+
clean!
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
# Append---Add the tag to the tag_list. This
|
29
|
+
# expression returns the tag_list itself, so several appends
|
30
|
+
# may be chained together.
|
31
|
+
def <<(obj)
|
32
|
+
add(obj)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Concatenation --- Returns a new tag list built by concatenating the
|
36
|
+
# two tag lists together to produce a third tag list.
|
37
|
+
def +(other_tag_list)
|
38
|
+
TagList.new.add(self).add(other_tag_list)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Appends the elements of +other_tag_list+ to +self+.
|
42
|
+
def concat(other_tag_list)
|
43
|
+
super(other_tag_list).send(:clean!)
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Remove specific tags from the tag_list.
|
49
|
+
# Use the <tt>:parse</tt> option to add an unparsed tag string.
|
50
|
+
#
|
51
|
+
# Example:
|
52
|
+
# tag_list.remove("Sad", "Lonely")
|
53
|
+
# tag_list.remove("Sad, Lonely", :parse => true)
|
54
|
+
def remove(*names)
|
55
|
+
extract_and_apply_options!(names)
|
56
|
+
delete_if { |name| names.include?(name) }
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Transform the tag_list into a tag string suitable for editing in a form.
|
62
|
+
# The tags are joined with <tt>TagList.delimiter</tt> and quoted if necessary.
|
63
|
+
#
|
64
|
+
# Example:
|
65
|
+
# tag_list = TagList.new("Round", "Square,Cube")
|
66
|
+
# tag_list.to_s # 'Round, "Square,Cube"'
|
67
|
+
def to_s
|
68
|
+
tags = frozen? ? self.dup : self
|
69
|
+
tags.send(:clean!)
|
70
|
+
|
71
|
+
tags.map do |name|
|
72
|
+
d = ActsAsTaggableOn.delimiter
|
73
|
+
d = Regexp.new d.join('|') if d.kind_of? Array
|
74
|
+
name.index(d) ? "\"#{name}\"" : name
|
75
|
+
end.join(ActsAsTaggableOn.glue)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# Convert everything to string, remove whitespace, duplicates, and blanks.
|
81
|
+
def clean!
|
82
|
+
reject!(&:blank?)
|
83
|
+
map!(&:to_s)
|
84
|
+
map!(&:strip)
|
85
|
+
map! { |tag| tag.mb_chars.downcase.to_s } if ActsAsTaggableOn.force_lowercase
|
86
|
+
map!(&:parameterize) if ActsAsTaggableOn.force_parameterize
|
87
|
+
|
88
|
+
ActsAsTaggableOn.strict_case_match ? uniq! : uniq!{ |tag| tag.downcase }
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def extract_and_apply_options!(args)
|
94
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
95
|
+
options.assert_valid_keys :parse, :parser
|
96
|
+
|
97
|
+
parser = options[:parser] ? options[:parser] : @parser
|
98
|
+
|
99
|
+
args.map! { |a| parser.new(a).parse } if options[:parse] || options[:parser]
|
100
|
+
|
101
|
+
args.flatten!
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module ActsAsTaggableOn
|
2
|
+
module Taggable
|
3
|
+
|
4
|
+
def taggable?
|
5
|
+
false
|
6
|
+
end
|
7
|
+
|
8
|
+
##
|
9
|
+
# This is an alias for calling <tt>acts_as_taggable_on :tags</tt>.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
# class Book < ActiveRecord::Base
|
13
|
+
# acts_as_taggable
|
14
|
+
# end
|
15
|
+
def acts_as_taggable
|
16
|
+
acts_as_taggable_on :tags
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# This is an alias for calling <tt>acts_as_ordered_taggable_on :tags</tt>.
|
21
|
+
#
|
22
|
+
# Example:
|
23
|
+
# class Book < ActiveRecord::Base
|
24
|
+
# acts_as_ordered_taggable
|
25
|
+
# end
|
26
|
+
def acts_as_ordered_taggable
|
27
|
+
acts_as_ordered_taggable_on :tags
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Make a model taggable on specified contexts.
|
32
|
+
#
|
33
|
+
# @param [Array] tag_types An array of taggable contexts
|
34
|
+
#
|
35
|
+
# Example:
|
36
|
+
# class User < ActiveRecord::Base
|
37
|
+
# acts_as_taggable_on :languages, :skills
|
38
|
+
# end
|
39
|
+
def acts_as_taggable_on(*tag_types)
|
40
|
+
taggable_on(false, tag_types)
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Make a model taggable on specified contexts
|
45
|
+
# and preserves the order in which tags are created
|
46
|
+
#
|
47
|
+
# @param [Array] tag_types An array of taggable contexts
|
48
|
+
#
|
49
|
+
# Example:
|
50
|
+
# class User < ActiveRecord::Base
|
51
|
+
# acts_as_ordered_taggable_on :languages, :skills
|
52
|
+
# end
|
53
|
+
def acts_as_ordered_taggable_on(*tag_types)
|
54
|
+
taggable_on(true, tag_types)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# Make a model taggable on specified contexts
|
60
|
+
# and optionally preserves the order in which tags are created
|
61
|
+
#
|
62
|
+
# Separate methods used above for backwards compatibility
|
63
|
+
# so that the original acts_as_taggable_on method is unaffected
|
64
|
+
# as it's not possible to add another argument to the method
|
65
|
+
# without the tag_types being enclosed in square brackets
|
66
|
+
#
|
67
|
+
# NB: method overridden in core module in order to create tag type
|
68
|
+
# associations and methods after this logic has executed
|
69
|
+
#
|
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
|
81
|
+
|
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'
|
85
|
+
|
86
|
+
def self.taggable?
|
87
|
+
true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# each of these add context-specific methods and must be
|
93
|
+
# called on each call of taggable_on
|
94
|
+
include Core
|
95
|
+
include Collection
|
96
|
+
include Cache
|
97
|
+
include Ownership
|
98
|
+
include Related
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module ActsAsTaggableOn::Taggable
|
2
|
+
module Cache
|
3
|
+
def self.included(base)
|
4
|
+
# When included, conditionally adds tag caching methods when the model
|
5
|
+
# has any "cached_#{tag_type}_list" column
|
6
|
+
base.extend Columns
|
7
|
+
end
|
8
|
+
|
9
|
+
module Columns
|
10
|
+
# ActiveRecord::Base.columns makes a database connection and caches the
|
11
|
+
# calculated columns hash for the record as @columns. Since we don't
|
12
|
+
# want to add caching methods until we confirm the presence of a
|
13
|
+
# caching column, and we don't want to force opening a database
|
14
|
+
# connection when the class is loaded, here we intercept and cache
|
15
|
+
# the call to :columns as @acts_as_taggable_on_cache_columns
|
16
|
+
# to mimic the underlying behavior. While processing this first
|
17
|
+
# call to columns, we do the caching column check and dynamically add
|
18
|
+
# the class and instance methods
|
19
|
+
# FIXME: this method cannot compile in rubinius
|
20
|
+
def columns
|
21
|
+
@acts_as_taggable_on_cache_columns ||= begin
|
22
|
+
db_columns = super
|
23
|
+
_add_tags_caching_methods if _has_tags_cache_columns?(db_columns)
|
24
|
+
db_columns
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def reset_column_information
|
29
|
+
super
|
30
|
+
@acts_as_taggable_on_cache_columns = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# @private
|
36
|
+
def _has_tags_cache_columns?(db_columns)
|
37
|
+
db_column_names = db_columns.map(&:name)
|
38
|
+
tag_types.any? do |context|
|
39
|
+
db_column_names.include?("cached_#{context.to_s.singularize}_list")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @private
|
44
|
+
def _add_tags_caching_methods
|
45
|
+
send :include, ActsAsTaggableOn::Taggable::Cache::InstanceMethods
|
46
|
+
extend ActsAsTaggableOn::Taggable::Cache::ClassMethods
|
47
|
+
|
48
|
+
before_save :save_cached_tag_list
|
49
|
+
|
50
|
+
initialize_tags_cache
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module ClassMethods
|
55
|
+
def initialize_tags_cache
|
56
|
+
tag_types.map(&:to_s).each do |tag_type|
|
57
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
58
|
+
def self.caching_#{tag_type.singularize}_list?
|
59
|
+
caching_tag_list_on?("#{tag_type}")
|
60
|
+
end
|
61
|
+
RUBY
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def acts_as_taggable_on(*args)
|
66
|
+
super(*args)
|
67
|
+
initialize_tags_cache
|
68
|
+
end
|
69
|
+
|
70
|
+
def caching_tag_list_on?(context)
|
71
|
+
column_names.include?("cached_#{context.to_s.singularize}_list")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module InstanceMethods
|
76
|
+
def save_cached_tag_list
|
77
|
+
tag_types.map(&:to_s).each do |tag_type|
|
78
|
+
if self.class.send("caching_#{tag_type.singularize}_list?")
|
79
|
+
if tag_list_cache_set_on(tag_type)
|
80
|
+
list = tag_list_cache_on(tag_type).to_a.flatten.compact.join("#{ActsAsTaggableOn.delimiter} ")
|
81
|
+
self["cached_#{tag_type.singularize}_list"] = list
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|