acts-as-taggable-on 3.2.1 → 4.0.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 +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +25 -12
- data/Appraisals +9 -12
- data/CHANGELOG.md +120 -10
- data/Gemfile +2 -1
- data/README.md +163 -27
- data/Rakefile +2 -0
- data/UPGRADING.md +2 -7
- data/acts-as-taggable-on.gemspec +4 -6
- data/db/migrate/2_add_missing_unique_indices.rb +3 -2
- data/db/migrate/4_add_missing_taggable_index.rb +9 -0
- data/db/migrate/5_change_collation_for_tag_names.rb +9 -0
- data/db/migrate/6_add_missing_indexes.rb +12 -0
- data/gemfiles/activerecord_4.0.gemfile +3 -2
- data/gemfiles/activerecord_4.1.gemfile +3 -2
- data/gemfiles/{activerecord_3.2.gemfile → activerecord_4.2.gemfile} +2 -2
- data/gemfiles/{activerecord_edge.gemfile → activerecord_5.0.gemfile} +2 -3
- data/lib/acts-as-taggable-on.rb +79 -23
- data/lib/acts_as_taggable_on/default_parser.rb +79 -0
- data/lib/acts_as_taggable_on/engine.rb +0 -1
- data/lib/acts_as_taggable_on/generic_parser.rb +19 -0
- data/lib/acts_as_taggable_on/tag.rb +25 -15
- data/lib/acts_as_taggable_on/tag_list.rb +11 -81
- data/lib/acts_as_taggable_on/{acts_as_taggable_on → taggable}/cache.rb +6 -1
- data/lib/acts_as_taggable_on/{acts_as_taggable_on → taggable}/collection.rb +9 -4
- data/lib/acts_as_taggable_on/{acts_as_taggable_on → taggable}/core.rb +103 -61
- data/lib/acts_as_taggable_on/{acts_as_taggable_on → taggable}/ownership.rb +5 -5
- data/lib/acts_as_taggable_on/{acts_as_taggable_on → taggable}/related.rb +7 -7
- data/lib/acts_as_taggable_on/taggable.rb +9 -10
- data/lib/acts_as_taggable_on/tagger.rb +12 -11
- data/lib/acts_as_taggable_on/tagging.rb +14 -14
- data/lib/acts_as_taggable_on/tags_helper.rb +2 -2
- data/lib/acts_as_taggable_on/utils.rb +25 -50
- data/lib/acts_as_taggable_on/version.rb +1 -1
- data/lib/tasks/tags_collate_utf8.rake +21 -0
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +27 -2
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +2 -1
- data/spec/acts_as_taggable_on/caching_spec.rb +29 -0
- data/spec/acts_as_taggable_on/default_parser_spec.rb +47 -0
- data/spec/acts_as_taggable_on/generic_parser_spec.rb +14 -0
- data/spec/acts_as_taggable_on/related_spec.rb +10 -0
- data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +1 -0
- data/spec/acts_as_taggable_on/tag_list_spec.rb +48 -30
- data/spec/acts_as_taggable_on/tag_spec.rb +86 -39
- data/spec/acts_as_taggable_on/taggable/dirty_spec.rb +127 -0
- data/spec/acts_as_taggable_on/taggable_spec.rb +103 -168
- data/spec/acts_as_taggable_on/tagger_spec.rb +14 -1
- data/spec/acts_as_taggable_on/tagging_spec.rb +91 -1
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +1 -0
- data/spec/acts_as_taggable_on/utils_spec.rb +13 -11
- data/spec/internal/app/models/cached_model_with_array.rb +1 -1
- data/spec/internal/app/models/models.rb +2 -2
- data/spec/internal/db/schema.rb +9 -5
- data/spec/spec_helper.rb +4 -1
- data/spec/support/0-helpers.rb +32 -0
- data/spec/support/database.rb +1 -7
- data/spec/support/database_cleaner.rb +4 -0
- metadata +34 -71
- data/lib/acts_as_taggable_on/acts_as_taggable_on/compatibility.rb +0 -35
- data/spec/bm.rb +0 -52
- data/spec/schema.rb +0 -82
- /data/lib/acts_as_taggable_on/{acts_as_taggable_on → taggable}/dirty.rb +0 -0
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
source "https://rubygems.org"
|
|
4
4
|
|
|
5
|
-
gem "activerecord", "~> 4.0"
|
|
6
|
-
gem "
|
|
5
|
+
gem "activerecord", "~> 4.0.0"
|
|
6
|
+
gem "mysql2", "~> 0.3.21"
|
|
7
7
|
|
|
8
8
|
group :local_development do
|
|
9
9
|
gem "guard"
|
|
10
10
|
gem "guard-rspec"
|
|
11
11
|
gem "appraisal"
|
|
12
12
|
gem "rake"
|
|
13
|
+
gem "byebug", :platforms => [:mri_21, :mri_22, :mri_23]
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
gemspec :path => "../"
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
source "https://rubygems.org"
|
|
4
4
|
|
|
5
|
-
gem "activerecord", "~> 4.1"
|
|
6
|
-
gem "
|
|
5
|
+
gem "activerecord", "~> 4.1.0"
|
|
6
|
+
gem "mysql2", "~> 0.3.21"
|
|
7
7
|
|
|
8
8
|
group :local_development do
|
|
9
9
|
gem "guard"
|
|
10
10
|
gem "guard-rspec"
|
|
11
11
|
gem "appraisal"
|
|
12
12
|
gem "rake"
|
|
13
|
+
gem "byebug", :platforms => [:mri_21, :mri_22, :mri_23]
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
gemspec :path => "../"
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
source "https://rubygems.org"
|
|
4
4
|
|
|
5
|
-
gem "activerecord", "~>
|
|
6
|
-
gem "actionpack", "~> 3.2"
|
|
5
|
+
gem "activerecord", "~> 4.2.0"
|
|
7
6
|
|
|
8
7
|
group :local_development do
|
|
9
8
|
gem "guard"
|
|
10
9
|
gem "guard-rspec"
|
|
11
10
|
gem "appraisal"
|
|
12
11
|
gem "rake"
|
|
12
|
+
gem "byebug", :platforms => [:mri_21, :mri_22, :mri_23]
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
gemspec :path => "../"
|
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
source "https://rubygems.org"
|
|
4
4
|
|
|
5
|
-
gem "activerecord",
|
|
6
|
-
gem "actionpack", :github => "rails/rails"
|
|
7
|
-
gem "arel", :github => "rails/arel"
|
|
5
|
+
gem "activerecord", "~> 5.0.0"
|
|
8
6
|
|
|
9
7
|
group :local_development do
|
|
10
8
|
gem "guard"
|
|
11
9
|
gem "guard-rspec"
|
|
12
10
|
gem "appraisal"
|
|
13
11
|
gem "rake"
|
|
12
|
+
gem "byebug", :platforms => [:mri_21, :mri_22, :mri_23]
|
|
14
13
|
end
|
|
15
14
|
|
|
16
15
|
gemspec :path => "../"
|
data/lib/acts-as-taggable-on.rb
CHANGED
|
@@ -1,11 +1,42 @@
|
|
|
1
1
|
require 'active_record'
|
|
2
2
|
require 'active_record/version'
|
|
3
3
|
require 'active_support/core_ext/module'
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
require 'rails/engine'
|
|
7
|
+
require 'acts_as_taggable_on/engine'
|
|
8
|
+
rescue LoadError
|
|
9
|
+
|
|
10
|
+
end
|
|
5
11
|
|
|
6
12
|
require 'digest/sha1'
|
|
7
13
|
|
|
8
14
|
module ActsAsTaggableOn
|
|
15
|
+
extend ActiveSupport::Autoload
|
|
16
|
+
|
|
17
|
+
autoload :Tag
|
|
18
|
+
autoload :TagList
|
|
19
|
+
autoload :GenericParser
|
|
20
|
+
autoload :DefaultParser
|
|
21
|
+
autoload :Taggable
|
|
22
|
+
autoload :Tagger
|
|
23
|
+
autoload :Tagging
|
|
24
|
+
autoload :TagsHelper
|
|
25
|
+
autoload :VERSION
|
|
26
|
+
|
|
27
|
+
autoload_under 'taggable' do
|
|
28
|
+
autoload :Cache
|
|
29
|
+
autoload :Collection
|
|
30
|
+
autoload :Core
|
|
31
|
+
autoload :Dirty
|
|
32
|
+
autoload :Ownership
|
|
33
|
+
autoload :Related
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
autoload :Utils
|
|
37
|
+
autoload :Compatibility
|
|
38
|
+
|
|
39
|
+
|
|
9
40
|
class DuplicateTagError < StandardError
|
|
10
41
|
end
|
|
11
42
|
|
|
@@ -16,7 +47,7 @@ module ActsAsTaggableOn
|
|
|
16
47
|
|
|
17
48
|
def self.method_missing(method_name, *args, &block)
|
|
18
49
|
@configuration.respond_to?(method_name) ?
|
|
19
|
-
|
|
50
|
+
@configuration.send(method_name, *args, &block) : super
|
|
20
51
|
end
|
|
21
52
|
|
|
22
53
|
def self.respond_to?(method_name, include_private=false)
|
|
@@ -30,8 +61,10 @@ module ActsAsTaggableOn
|
|
|
30
61
|
end
|
|
31
62
|
|
|
32
63
|
class Configuration
|
|
33
|
-
attr_accessor :
|
|
34
|
-
|
|
64
|
+
attr_accessor :force_lowercase, :force_parameterize,
|
|
65
|
+
:remove_unused_tags, :default_parser,
|
|
66
|
+
:tags_counter
|
|
67
|
+
attr_reader :delimiter, :strict_case_match
|
|
35
68
|
|
|
36
69
|
def initialize
|
|
37
70
|
@delimiter = ','
|
|
@@ -39,32 +72,55 @@ module ActsAsTaggableOn
|
|
|
39
72
|
@force_parameterize = false
|
|
40
73
|
@strict_case_match = false
|
|
41
74
|
@remove_unused_tags = false
|
|
75
|
+
@tags_counter = true
|
|
76
|
+
@default_parser = DefaultParser
|
|
77
|
+
@force_binary_collation = false
|
|
42
78
|
end
|
|
43
|
-
end
|
|
44
79
|
|
|
45
|
-
|
|
46
|
-
|
|
80
|
+
def strict_case_match=(force_cs)
|
|
81
|
+
@strict_case_match = force_cs unless @force_binary_collation
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def delimiter=(string)
|
|
85
|
+
ActiveRecord::Base.logger.warn <<WARNING
|
|
86
|
+
ActsAsTaggableOn.delimiter is deprecated \
|
|
87
|
+
and will be removed from v4.0+, use \
|
|
88
|
+
a ActsAsTaggableOn.default_parser instead
|
|
89
|
+
WARNING
|
|
90
|
+
@delimiter = string
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def force_binary_collation=(force_bin)
|
|
94
|
+
if Utils.using_mysql?
|
|
95
|
+
if force_bin
|
|
96
|
+
Configuration.apply_binary_collation(true)
|
|
97
|
+
@force_binary_collation = true
|
|
98
|
+
@strict_case_match = true
|
|
99
|
+
else
|
|
100
|
+
Configuration.apply_binary_collation(false)
|
|
101
|
+
@force_binary_collation = false
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
47
105
|
|
|
48
|
-
|
|
106
|
+
def self.apply_binary_collation(bincoll)
|
|
107
|
+
if Utils.using_mysql?
|
|
108
|
+
coll = 'utf8_general_ci'
|
|
109
|
+
coll = 'utf8_bin' if bincoll
|
|
110
|
+
begin
|
|
111
|
+
ActiveRecord::Migration.execute("ALTER TABLE #{Tag.table_name} MODIFY name varchar(255) CHARACTER SET utf8 COLLATE #{coll};")
|
|
112
|
+
rescue Exception => e
|
|
113
|
+
puts "Trapping #{e.class}: collation parameter ignored while migrating for the first time."
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
49
117
|
|
|
50
|
-
|
|
51
|
-
require 'acts_as_taggable_on/acts_as_taggable_on/compatibility'
|
|
52
|
-
require 'acts_as_taggable_on/acts_as_taggable_on/core'
|
|
53
|
-
require 'acts_as_taggable_on/acts_as_taggable_on/collection'
|
|
54
|
-
require 'acts_as_taggable_on/acts_as_taggable_on/cache'
|
|
55
|
-
require 'acts_as_taggable_on/acts_as_taggable_on/ownership'
|
|
56
|
-
require 'acts_as_taggable_on/acts_as_taggable_on/related'
|
|
57
|
-
require 'acts_as_taggable_on/acts_as_taggable_on/dirty'
|
|
118
|
+
end
|
|
58
119
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
require 'acts_as_taggable_on/tag_list'
|
|
62
|
-
require 'acts_as_taggable_on/tags_helper'
|
|
63
|
-
require 'acts_as_taggable_on/tagging'
|
|
64
|
-
require 'acts_as_taggable_on/engine'
|
|
120
|
+
setup
|
|
121
|
+
end
|
|
65
122
|
|
|
66
123
|
ActiveSupport.on_load(:active_record) do
|
|
67
|
-
extend ActsAsTaggableOn::Compatibility
|
|
68
124
|
extend ActsAsTaggableOn::Taggable
|
|
69
125
|
include ActsAsTaggableOn::Tagger
|
|
70
126
|
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module ActsAsTaggableOn
|
|
2
|
+
##
|
|
3
|
+
# Returns a new TagList using the given tag string.
|
|
4
|
+
#
|
|
5
|
+
# Example:
|
|
6
|
+
# tag_list = ActsAsTaggableOn::DefaultParser.parse("One , Two, Three")
|
|
7
|
+
# tag_list # ["One", "Two", "Three"]
|
|
8
|
+
class DefaultParser < GenericParser
|
|
9
|
+
|
|
10
|
+
def parse
|
|
11
|
+
string = @tag_list
|
|
12
|
+
|
|
13
|
+
string = string.join(ActsAsTaggableOn.glue) if string.respond_to?(:join)
|
|
14
|
+
TagList.new.tap do |tag_list|
|
|
15
|
+
string = string.to_s.dup
|
|
16
|
+
|
|
17
|
+
string.gsub!(double_quote_pattern) {
|
|
18
|
+
# Append the matched tag to the tag list
|
|
19
|
+
tag_list << Regexp.last_match[2]
|
|
20
|
+
# Return the matched delimiter ($3) to replace the matched items
|
|
21
|
+
''
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
string.gsub!(single_quote_pattern) {
|
|
25
|
+
# Append the matched tag ($2) to the tag list
|
|
26
|
+
tag_list << Regexp.last_match[2]
|
|
27
|
+
# Return an empty string to replace the matched items
|
|
28
|
+
''
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# split the string by the delimiter
|
|
32
|
+
# and add to the tag_list
|
|
33
|
+
tag_list.add(string.split(Regexp.new delimiter))
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# private
|
|
39
|
+
def delimiter
|
|
40
|
+
# Parse the quoted tags
|
|
41
|
+
d = ActsAsTaggableOn.delimiter
|
|
42
|
+
# Separate multiple delimiters by bitwise operator
|
|
43
|
+
d = d.join('|') if d.kind_of?(Array)
|
|
44
|
+
d
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# ( # Tag start delimiter ($1)
|
|
48
|
+
# \A | # Either string start or
|
|
49
|
+
# #{delimiter} # a delimiter
|
|
50
|
+
# )
|
|
51
|
+
# \s*" # quote (") optionally preceded by whitespace
|
|
52
|
+
# (.*?) # Tag ($2)
|
|
53
|
+
# "\s* # quote (") optionally followed by whitespace
|
|
54
|
+
# (?= # Tag end delimiter (not consumed; is zero-length lookahead)
|
|
55
|
+
# #{delimiter}\s* | # Either a delimiter optionally followed by whitespace or
|
|
56
|
+
# \z # string end
|
|
57
|
+
# )
|
|
58
|
+
def double_quote_pattern
|
|
59
|
+
/(\A|#{delimiter})\s*"(.*?)"\s*(?=#{delimiter}\s*|\z)/
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# ( # Tag start delimiter ($1)
|
|
63
|
+
# \A | # Either string start or
|
|
64
|
+
# #{delimiter} # a delimiter
|
|
65
|
+
# )
|
|
66
|
+
# \s*' # quote (') optionally preceded by whitespace
|
|
67
|
+
# (.*?) # Tag ($2)
|
|
68
|
+
# '\s* # quote (') optionally followed by whitespace
|
|
69
|
+
# (?= # Tag end delimiter (not consumed; is zero-length lookahead)
|
|
70
|
+
# #{delimiter}\s* | d # Either a delimiter optionally followed by whitespace or
|
|
71
|
+
# \z # string end
|
|
72
|
+
# )
|
|
73
|
+
def single_quote_pattern
|
|
74
|
+
/(\A|#{delimiter})\s*'(.*?)'\s*(?=#{delimiter}\s*|\z)/
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end
|
|
@@ -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
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
#
|
|
1
|
+
# encoding: utf-8
|
|
2
2
|
module ActsAsTaggableOn
|
|
3
3
|
class Tag < ::ActiveRecord::Base
|
|
4
4
|
|
|
5
|
-
attr_accessible :name if defined?(ActiveModel::MassAssignmentSecurity)
|
|
6
|
-
|
|
7
5
|
### ASSOCIATIONS:
|
|
8
6
|
|
|
9
|
-
has_many :taggings, dependent: :destroy, class_name: 'ActsAsTaggableOn::Tagging'
|
|
7
|
+
has_many :taggings, dependent: :destroy, class_name: '::ActsAsTaggableOn::Tagging'
|
|
10
8
|
|
|
11
9
|
### VALIDATIONS:
|
|
12
10
|
|
|
@@ -20,6 +18,8 @@ module ActsAsTaggableOn
|
|
|
20
18
|
end
|
|
21
19
|
|
|
22
20
|
### SCOPES:
|
|
21
|
+
scope :most_used, ->(limit = 20) { order('taggings_count desc').limit(limit) }
|
|
22
|
+
scope :least_used, ->(limit = 20) { order('taggings_count asc').limit(limit) }
|
|
23
23
|
|
|
24
24
|
def self.named(name)
|
|
25
25
|
if ActsAsTaggableOn.strict_case_match
|
|
@@ -30,17 +30,10 @@ module ActsAsTaggableOn
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def self.named_any(list)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
where(clause)
|
|
38
|
-
else
|
|
39
|
-
clause = list.map { |tag|
|
|
40
|
-
sanitize_sql(['LOWER(name) = LOWER(?)', as_8bit_ascii(unicode_downcase(tag))])
|
|
41
|
-
}.join(' OR ')
|
|
42
|
-
where(clause)
|
|
43
|
-
end
|
|
33
|
+
clause = list.map { |tag|
|
|
34
|
+
sanitize_sql_for_named_any(tag).force_encoding('BINARY')
|
|
35
|
+
}.join(' OR ')
|
|
36
|
+
where(clause)
|
|
44
37
|
end
|
|
45
38
|
|
|
46
39
|
def self.named_like(name)
|
|
@@ -55,6 +48,12 @@ module ActsAsTaggableOn
|
|
|
55
48
|
where(clause)
|
|
56
49
|
end
|
|
57
50
|
|
|
51
|
+
def self.for_context(context)
|
|
52
|
+
joins(:taggings).
|
|
53
|
+
where(["taggings.context = ?", context]).
|
|
54
|
+
select("DISTINCT tags.*")
|
|
55
|
+
end
|
|
56
|
+
|
|
58
57
|
### CLASS METHODS:
|
|
59
58
|
|
|
60
59
|
def self.find_or_create_with_like_by_name(name)
|
|
@@ -101,6 +100,9 @@ module ActsAsTaggableOn
|
|
|
101
100
|
end
|
|
102
101
|
|
|
103
102
|
class << self
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
104
106
|
private
|
|
105
107
|
|
|
106
108
|
def comparable_name(str)
|
|
@@ -130,6 +132,14 @@ module ActsAsTaggableOn
|
|
|
130
132
|
string.to_s.mb_chars
|
|
131
133
|
end
|
|
132
134
|
end
|
|
135
|
+
|
|
136
|
+
def sanitize_sql_for_named_any(tag)
|
|
137
|
+
if ActsAsTaggableOn.strict_case_match
|
|
138
|
+
sanitize_sql(["name = #{binary}?", as_8bit_ascii(tag)])
|
|
139
|
+
else
|
|
140
|
+
sanitize_sql(['LOWER(name) = LOWER(?)', as_8bit_ascii(unicode_downcase(tag))])
|
|
141
|
+
end
|
|
142
|
+
end
|
|
133
143
|
end
|
|
134
144
|
end
|
|
135
145
|
end
|
|
@@ -1,89 +1,16 @@
|
|
|
1
|
+
|
|
1
2
|
require 'active_support/core_ext/module/delegation'
|
|
2
3
|
|
|
3
4
|
module ActsAsTaggableOn
|
|
4
5
|
class TagList < Array
|
|
5
6
|
attr_accessor :owner
|
|
7
|
+
attr_accessor :parser
|
|
6
8
|
|
|
7
9
|
def initialize(*args)
|
|
10
|
+
@parser = ActsAsTaggableOn.default_parser
|
|
8
11
|
add(*args)
|
|
9
12
|
end
|
|
10
13
|
|
|
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
|
|
44
|
-
|
|
45
|
-
def delimiter
|
|
46
|
-
# Parse the quoted tags
|
|
47
|
-
d = ActsAsTaggableOn.delimiter
|
|
48
|
-
# Separate multiple delimiters by bitwise operator
|
|
49
|
-
d = d.join('|') if d.kind_of?(Array)
|
|
50
|
-
|
|
51
|
-
d
|
|
52
|
-
end
|
|
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
|
|
87
14
|
##
|
|
88
15
|
# Add tags to the tag_list. Duplicate or blank tags will be ignored.
|
|
89
16
|
# Use the <tt>:parse</tt> option to add an unparsed tag string.
|
|
@@ -149,27 +76,30 @@ module ActsAsTaggableOn
|
|
|
149
76
|
|
|
150
77
|
private
|
|
151
78
|
|
|
152
|
-
#
|
|
79
|
+
# Convert everything to string, remove whitespace, duplicates, and blanks.
|
|
153
80
|
def clean!
|
|
154
81
|
reject!(&:blank?)
|
|
82
|
+
map!(&:to_s)
|
|
155
83
|
map!(&:strip)
|
|
156
84
|
map! { |tag| tag.mb_chars.downcase.to_s } if ActsAsTaggableOn.force_lowercase
|
|
157
85
|
map!(&:parameterize) if ActsAsTaggableOn.force_parameterize
|
|
158
86
|
|
|
159
|
-
uniq!
|
|
87
|
+
ActsAsTaggableOn.strict_case_match ? uniq! : uniq!{ |tag| tag.downcase }
|
|
88
|
+
self
|
|
160
89
|
end
|
|
161
90
|
|
|
162
91
|
|
|
163
92
|
def extract_and_apply_options!(args)
|
|
164
93
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
165
|
-
options.assert_valid_keys :parse
|
|
94
|
+
options.assert_valid_keys :parse, :parser
|
|
95
|
+
|
|
96
|
+
parser = options[:parser] ? options[:parser] : @parser
|
|
166
97
|
|
|
167
|
-
args.map! { |a|
|
|
98
|
+
args.map! { |a| parser.new(a).parse } if options[:parse] || options[:parser]
|
|
168
99
|
|
|
169
100
|
args.flatten!
|
|
170
101
|
end
|
|
171
102
|
|
|
172
|
-
|
|
173
103
|
end
|
|
174
104
|
end
|
|
175
105
|
|
|
@@ -31,6 +31,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
31
31
|
# to mimic the underlying behavior. While processing this first
|
|
32
32
|
# call to columns, we do the caching column check and dynamically add
|
|
33
33
|
# the class and instance methods
|
|
34
|
+
# FIXME: this method cannot compile in rubinius
|
|
34
35
|
def columns
|
|
35
36
|
@acts_as_taggable_on_cache_columns ||= begin
|
|
36
37
|
db_columns = super
|
|
@@ -39,6 +40,10 @@ module ActsAsTaggableOn::Taggable
|
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
|
|
43
|
+
def reset_column_information
|
|
44
|
+
super
|
|
45
|
+
@acts_as_taggable_on_cache_columns = nil
|
|
46
|
+
end
|
|
42
47
|
end
|
|
43
48
|
end
|
|
44
49
|
|
|
@@ -68,7 +73,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
68
73
|
tag_types.map(&:to_s).each do |tag_type|
|
|
69
74
|
if self.class.send("caching_#{tag_type.singularize}_list?")
|
|
70
75
|
if tag_list_cache_set_on(tag_type)
|
|
71
|
-
list = tag_list_cache_on(tag_type).to_a.flatten.compact.join(
|
|
76
|
+
list = tag_list_cache_on(tag_type).to_a.flatten.compact.join("#{ActsAsTaggableOn.delimiter} ")
|
|
72
77
|
self["cached_#{tag_type.singularize}_list"] = list
|
|
73
78
|
end
|
|
74
79
|
end
|
|
@@ -53,6 +53,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
53
53
|
# * :order - A piece of SQL to order by. Eg 'tags.count desc' or 'taggings.created_at desc'
|
|
54
54
|
# * :on - Scope the find to only include a certain context
|
|
55
55
|
def all_tags(options = {})
|
|
56
|
+
options = options.dup
|
|
56
57
|
options.assert_valid_keys :start_at, :end_at, :conditions, :order, :limit, :on
|
|
57
58
|
|
|
58
59
|
## Generate conditions:
|
|
@@ -87,6 +88,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
87
88
|
# * :at_most - Exclude tags with a frequency greater than the given value
|
|
88
89
|
# * :on - Scope the find to only include a certain context
|
|
89
90
|
def all_tag_counts(options = {})
|
|
91
|
+
options = options.dup
|
|
90
92
|
options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id
|
|
91
93
|
|
|
92
94
|
## Generate conditions:
|
|
@@ -133,10 +135,10 @@ module ActsAsTaggableOn::Taggable
|
|
|
133
135
|
table_name_pkey = "#{table_name}.#{primary_key}"
|
|
134
136
|
if ActsAsTaggableOn::Utils.using_mysql?
|
|
135
137
|
# See https://github.com/mbleigh/acts-as-taggable-on/pull/457 for details
|
|
136
|
-
scoped_ids =
|
|
138
|
+
scoped_ids = pluck(table_name_pkey)
|
|
137
139
|
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN (?)", scoped_ids)
|
|
138
140
|
else
|
|
139
|
-
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(select(table_name_pkey))})")
|
|
141
|
+
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(except(:select).select(table_name_pkey))})")
|
|
140
142
|
end
|
|
141
143
|
|
|
142
144
|
tagging_scope
|
|
@@ -167,9 +169,12 @@ module ActsAsTaggableOn::Taggable
|
|
|
167
169
|
end
|
|
168
170
|
|
|
169
171
|
module CalculationMethods
|
|
170
|
-
|
|
172
|
+
# Rails 5 TODO: Remove options argument as soon we remove support to
|
|
173
|
+
# activerecord-deprecated_finders.
|
|
174
|
+
# See https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/calculations.rb#L38
|
|
175
|
+
def count(column_name = :all, options = {})
|
|
171
176
|
# https://github.com/rails/rails/commit/da9b5d4a8435b744fcf278fffd6d7f1e36d4a4f2
|
|
172
|
-
super
|
|
177
|
+
ActsAsTaggableOn::Utils.active_record5? ? super(column_name) : super(column_name, options)
|
|
173
178
|
end
|
|
174
179
|
end
|
|
175
180
|
end
|