acts-as-taggable-on 3.3.0 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Appraisals +8 -2
- data/CHANGELOG.md +81 -4
- data/README.md +60 -0
- data/acts-as-taggable-on.gemspec +2 -2
- data/gemfiles/activerecord_4.0.gemfile +1 -1
- data/gemfiles/activerecord_4.1.gemfile +1 -1
- data/gemfiles/activerecord_4.2.gemfile +16 -0
- data/lib/acts-as-taggable-on.rb +13 -2
- data/lib/acts_as_taggable_on/default_parser.rb +79 -0
- data/lib/acts_as_taggable_on/generic_parser.rb +19 -0
- data/lib/acts_as_taggable_on/tag.rb +5 -0
- data/lib/acts_as_taggable_on/tag_list.rb +8 -4
- data/lib/acts_as_taggable_on/tag_list_parser.rb +7 -64
- data/lib/acts_as_taggable_on/taggable/collection.rb +1 -1
- data/lib/acts_as_taggable_on/taggable/core.rb +25 -13
- data/lib/acts_as_taggable_on/taggable/ownership.rb +1 -1
- data/lib/acts_as_taggable_on/version.rb +1 -1
- 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/tag_list_spec.rb +29 -0
- data/spec/acts_as_taggable_on/tag_spec.rb +20 -0
- metadata +16 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d8fbbfcf90748ab7a565ca2d911de6cff93a5d6
|
4
|
+
data.tar.gz: bcdadfcc7559b5d1e1cbe41214493366a700b6e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8af6b4c5ca019197c14bf67980889f0639f61fd7d53333316b6202d9c00bbbac3533bb77a6b055706c13f24102ed8d30064675dd4609f932a3413a54dbd7e674
|
7
|
+
data.tar.gz: 103300024612ce7fd41600b58158aa05edee3c672cec7c611bdab5bdacb7d98b59aa78e7c0418e753b990c9be6962da56a5b4acf999dbbf5047f9d2aadc26ab2
|
data/Appraisals
CHANGED
@@ -3,11 +3,17 @@ appraise "activerecord-3.2" do
|
|
3
3
|
end
|
4
4
|
|
5
5
|
appraise "activerecord-4.0" do
|
6
|
-
gem "activerecord", "~> 4.0"
|
6
|
+
gem "activerecord", "~> 4.0.0"
|
7
7
|
end
|
8
8
|
|
9
9
|
appraise "activerecord-4.1" do
|
10
|
-
gem "activerecord", "~> 4.1"
|
10
|
+
gem "activerecord", "~> 4.1.0"
|
11
|
+
end
|
12
|
+
|
13
|
+
appraise "activerecord-4.2" do
|
14
|
+
gem "railties", ">= 4.2.0.beta1"
|
15
|
+
gem "activerecord", ">= 4.2.0.beta1"
|
16
|
+
gem "rack", ">= 1.6.0.beta"
|
11
17
|
end
|
12
18
|
|
13
19
|
appraise "activerecord-edge" do
|
data/CHANGELOG.md
CHANGED
@@ -4,18 +4,95 @@ Each change should fall into categories that would affect whether the release is
|
|
4
4
|
|
5
5
|
As such, a _Feature_ would map to either major or minor. A _bug fix_ to a patch. And _misc_ is either minor or patch, the difference being kind of fuzzy for the purposes of history. Adding tests would be patch level.
|
6
6
|
|
7
|
-
### Master [changes](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.
|
7
|
+
### Master [changes](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.4.0...master)
|
8
8
|
|
9
9
|
* Breaking Changes
|
10
|
-
* Taggable models are not extend with ActsAsTaggableOn::Utils anymore
|
11
10
|
* Features
|
12
11
|
* Fixes
|
13
12
|
* Performance
|
14
13
|
* Misc
|
15
|
-
* Deleted outdated benchmark script
|
16
14
|
|
15
|
+
### [3.4.0 / 2014-08-29](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.3.0...v3.4.0)
|
17
16
|
|
18
|
-
|
17
|
+
* Features
|
18
|
+
* [@ProGM Support for custom parsers for tags](https://github.com/mbleigh/acts-as-taggable-on/pull/579)
|
19
|
+
* [@damzcodes #577 Popular feature](https://github.com/mbleigh/acts-as-taggable-on/pull/577)
|
20
|
+
* Fixes
|
21
|
+
* [@twalpole Update for rails edge (4.2)](https://github.com/mbleigh/acts-as-taggable-on/pull/583)
|
22
|
+
* Performance
|
23
|
+
* [@dontfidget #587 Use pluck instead of select](https://github.com/mbleigh/acts-as-taggable-on/pull/587)
|
24
|
+
|
25
|
+
### [3.3.0 / 2014-07-08](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.2.6...v3.3.0)
|
26
|
+
|
27
|
+
* Features
|
28
|
+
* [@felipeclopes #488 Support for `start_at` and `end_at` restrictions when selecting tags](https://github.com/mbleigh/acts-as-taggable-on/pull/488)
|
29
|
+
|
30
|
+
* Fixes
|
31
|
+
* [@tonytonyjan #560 Fix for `ActsAsTaggableOn.remove_unused_tags` doesn't work](https://github.com/mbleigh/acts-as-taggable-on/pull/560)
|
32
|
+
* [@ThearkInn #555 Fix for `tag_cloud` helper to generate correct css tags](https://github.com/mbleigh/acts-as-taggable-on/pull/555)
|
33
|
+
|
34
|
+
* Performance
|
35
|
+
* [@pcai #556 Add back taggables index in the taggins table](https://github.com/mbleigh/acts-as-taggable-on/pull/556)
|
36
|
+
|
37
|
+
|
38
|
+
### [3.2.6 / 2014-05-28](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.2.5...v3.2.6)
|
39
|
+
|
40
|
+
* Fixes
|
41
|
+
* [@seuros #548 Fix dirty marking when tags are not ordered](https://github.com/mbleigh/acts-as-taggable-on/issues/548)
|
42
|
+
|
43
|
+
* Misc
|
44
|
+
* [@seuros Remove actionpack dependency](https://github.com/mbleigh/acts-as-taggable-on/commit/5d20e0486c892fbe21af42fdcd79d0b6ebe87ed4)
|
45
|
+
* [@seuros #547 Add tests for update_attributes](https://github.com/mbleigh/acts-as-taggable-on/issues/547)
|
46
|
+
|
47
|
+
|
48
|
+
### [3.2.5 / 2014-05-25](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.2.4...v3.2.5)
|
49
|
+
|
50
|
+
* Fixes
|
51
|
+
* [@seuros #546 Fix autoload bug. Now require engine file instead of autoloading it](https://github.com/mbleigh/acts-as-taggable-on/issues/546)
|
52
|
+
|
53
|
+
|
54
|
+
### [3.2.4 / 2014-05-24](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.2.3...v3.2.4)
|
55
|
+
|
56
|
+
* Fixes
|
57
|
+
* [@seuros #544 Fix incorrect query generation related to `GROUP BY` SQL statement](https://github.com/mbleigh/acts-as-taggable-on/issues/544)
|
58
|
+
|
59
|
+
* Misc
|
60
|
+
* [@seuros #545 Remove `ammeter` development dependency](https://github.com/mbleigh/acts-as-taggable-on/pull/545)
|
61
|
+
* [@seuros #545 Deprecate `TagList.from` in favor of `TagListParser.parse`](https://github.com/mbleigh/acts-as-taggable-on/pull/545)
|
62
|
+
* [@seuros #543 Introduce lazy loading](https://github.com/mbleigh/acts-as-taggable-on/pull/543)
|
63
|
+
* [@seuros #541 Deprecate ActsAsTaggableOn::Utils](https://github.com/mbleigh/acts-as-taggable-on/pull/541)
|
64
|
+
|
65
|
+
|
66
|
+
### [3.2.3 / 2014-05-16](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.2.2...v3.2.3)
|
67
|
+
|
68
|
+
* Fixes
|
69
|
+
* [@seuros #540 Fix for tags removal (it was affecting all records with the same tag)](https://github.com/mbleigh/acts-as-taggable-on/pull/540)
|
70
|
+
* [@akcho8 #535 Fix for `options` Hash passed to methods from being deleted by those methods](https://github.com/mbleigh/acts-as-taggable-on/pull/535)
|
71
|
+
|
72
|
+
|
73
|
+
### [3.2.2 / 2014-05-07](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.2.1...v3.2.2)
|
74
|
+
|
75
|
+
* Breaking Changes
|
76
|
+
* [@seuros #526 Taggable models are not extended with ActsAsTaggableOn::Utils anymore](https://github.com/mbleigh/acts-as-taggable-on/pull/526)
|
77
|
+
|
78
|
+
* Fixes
|
79
|
+
* [@seuros #536 Add explicit conversion of tags to strings (when assigning tags)](https://github.com/mbleigh/acts-as-taggable-on/pull/536)
|
80
|
+
|
81
|
+
* Misc
|
82
|
+
* [@seuros #526 Delete outdated benchmark script](https://github.com/mbleigh/acts-as-taggable-on/pull/526)
|
83
|
+
* [@seuros #525 Fix tests so that they pass with MySQL](https://github.com/mbleigh/acts-as-taggable-on/pull/525)
|
84
|
+
|
85
|
+
|
86
|
+
### [3.2.1 / 2014-05-06](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.2.0...v3.2.1)
|
87
|
+
|
88
|
+
* Misc
|
89
|
+
* [@seuros #523 Run tests loading only ActiveRecord (without the full Rails stack)](https://github.com/mbleigh/acts-as-taggable-on/pull/523)
|
90
|
+
* [@seuros #523 Remove activesupport dependency](https://github.com/mbleigh/acts-as-taggable-on/pull/523)
|
91
|
+
* [@seuros #523 Introduce database_cleaner in specs](https://github.com/mbleigh/acts-as-taggable-on/pull/523)
|
92
|
+
* [@seuros #520 Tag_list cleanup](https://github.com/mbleigh/acts-as-taggable-on/pull/520)
|
93
|
+
|
94
|
+
|
95
|
+
### [3.2.0 / 2014-05-01](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.1.1...v3.2.0)
|
19
96
|
|
20
97
|
* Breaking Changes
|
21
98
|
* ActsAsTaggableOn::Tag is not extend with ActsAsTaggableOn::Utils anymore
|
data/README.md
CHANGED
@@ -70,6 +70,12 @@ class User < ActiveRecord::Base
|
|
70
70
|
acts_as_taggable_on :skills, :interests
|
71
71
|
end
|
72
72
|
|
73
|
+
class UsersController < ApplicationController
|
74
|
+
def user_params
|
75
|
+
params.require(:user).permit(:name, :tag_list) ## Rails 4 strong params usage
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
73
79
|
@user = User.new(:name => "Bobby")
|
74
80
|
```
|
75
81
|
|
@@ -164,6 +170,22 @@ end
|
|
164
170
|
@user.tag_list # => ["north", "east", "south", "west"]
|
165
171
|
```
|
166
172
|
|
173
|
+
### Finding most or least used tags
|
174
|
+
|
175
|
+
You can find the most or least used tags by using:
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
ActsAsTaggableOn::Tag.most_used
|
179
|
+
ActsAsTaggableOn::Tag.least_used
|
180
|
+
```
|
181
|
+
|
182
|
+
You can also filter the results by passing the method a limit, however the default limit is 50.
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
ActsAsTaggableOn::Tag.most_used(10)
|
186
|
+
ActsAsTaggableOn::Tag.least_used(10)
|
187
|
+
```
|
188
|
+
|
167
189
|
### Finding Tagged Objects
|
168
190
|
|
169
191
|
Acts As Taggable On uses scopes to create an association for tags.
|
@@ -195,6 +217,7 @@ You can also use `:wild => true` option along with `:any` or `:exclude` option.
|
|
195
217
|
|
196
218
|
__Tip:__ `User.tagged_with([])` or `User.tagged_with('')` will return `[]`, an empty set of records.
|
197
219
|
|
220
|
+
|
198
221
|
### Relationships
|
199
222
|
|
200
223
|
You can find objects of the same type based on similar tags on certain contexts.
|
@@ -231,6 +254,43 @@ to allow for dynamic tag contexts (this could be user generated tag contexts!)
|
|
231
254
|
User.tagged_with("same", :on => :customs) # => [@user]
|
232
255
|
```
|
233
256
|
|
257
|
+
### Tag Parsers
|
258
|
+
|
259
|
+
If you want to change how tags are parsed, you can define a your own implementation:
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
class MyParser < ActsAsTaggableOn::GenericParser
|
263
|
+
def parse
|
264
|
+
TagList.new.tap do |tag_list|
|
265
|
+
tag_list.add @tag_list.split('|')
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
```
|
270
|
+
|
271
|
+
Now you can use this parser, passing it as parameter:
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
@user = User.new(:name => "Bobby")
|
275
|
+
@user.tag_list = "east, south"
|
276
|
+
@user.tag_list.add("north|west", parser: MyParser)
|
277
|
+
@user.tag_list # => ["north", "east", "south", "west"]
|
278
|
+
|
279
|
+
# Or also:
|
280
|
+
@user.tag_list.parser = MyParser
|
281
|
+
@user.tag_list.add("north|west")
|
282
|
+
@user.tag_list # => ["north", "east", "south", "west"]
|
283
|
+
```
|
284
|
+
|
285
|
+
Or change it globally:
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
ActsAsTaggable.default_parser = MyParser
|
289
|
+
@user = User.new(:name => "Bobby")
|
290
|
+
@user.tag_list = "east|south"
|
291
|
+
@user.tag_list # => ["east", "south"]
|
292
|
+
```
|
293
|
+
|
234
294
|
### Tag Ownership
|
235
295
|
|
236
296
|
Tags can have owners:
|
data/acts-as-taggable-on.gemspec
CHANGED
@@ -22,13 +22,13 @@ Gem::Specification.new do |gem|
|
|
22
22
|
gem.post_install_message = File.read('UPGRADING.md')
|
23
23
|
end
|
24
24
|
|
25
|
-
gem.add_runtime_dependency 'activerecord', ['>= 3', '< 5']
|
25
|
+
gem.add_runtime_dependency 'activerecord', ['>= 3.2', '< 5']
|
26
26
|
|
27
27
|
gem.add_development_dependency 'sqlite3'
|
28
28
|
gem.add_development_dependency 'mysql2', '~> 0.3.7'
|
29
29
|
gem.add_development_dependency 'pg'
|
30
30
|
|
31
|
-
gem.add_development_dependency 'rspec-rails'
|
31
|
+
gem.add_development_dependency 'rspec-rails'
|
32
32
|
gem.add_development_dependency 'rspec-its'
|
33
33
|
gem.add_development_dependency 'rspec'
|
34
34
|
gem.add_development_dependency 'barrier'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "railties", ">= 4.2.0.beta1"
|
6
|
+
gem "activerecord", ">= 4.2.0.beta1"
|
7
|
+
gem "rack", ">= 1.6.0.beta"
|
8
|
+
|
9
|
+
group :local_development do
|
10
|
+
gem "guard"
|
11
|
+
gem "guard-rspec"
|
12
|
+
gem "appraisal"
|
13
|
+
gem "rake"
|
14
|
+
end
|
15
|
+
|
16
|
+
gemspec :path => "../"
|
data/lib/acts-as-taggable-on.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
require 'active_record/version'
|
3
3
|
require 'active_support/core_ext/module'
|
4
|
-
require 'action_view'
|
5
4
|
|
6
5
|
require_relative 'acts_as_taggable_on/engine' if defined?(Rails)
|
7
6
|
|
@@ -12,6 +11,8 @@ module ActsAsTaggableOn
|
|
12
11
|
|
13
12
|
autoload :Tag
|
14
13
|
autoload :TagList
|
14
|
+
autoload :GenericParser
|
15
|
+
autoload :DefaultParser
|
15
16
|
autoload :TagListParser
|
16
17
|
autoload :Taggable
|
17
18
|
autoload :Tagger
|
@@ -57,7 +58,7 @@ module ActsAsTaggableOn
|
|
57
58
|
|
58
59
|
class Configuration
|
59
60
|
attr_accessor :delimiter, :force_lowercase, :force_parameterize,
|
60
|
-
:strict_case_match, :remove_unused_tags
|
61
|
+
:strict_case_match, :remove_unused_tags, :default_parser
|
61
62
|
|
62
63
|
def initialize
|
63
64
|
@delimiter = ','
|
@@ -65,6 +66,16 @@ module ActsAsTaggableOn
|
|
65
66
|
@force_parameterize = false
|
66
67
|
@strict_case_match = false
|
67
68
|
@remove_unused_tags = false
|
69
|
+
@default_parser = DefaultParser
|
70
|
+
end
|
71
|
+
|
72
|
+
def delimiter=(string)
|
73
|
+
ActiveRecord::Base.logger.warn <<WARNING
|
74
|
+
ActsAsTaggableOn.delimiter is deprecated \
|
75
|
+
and will be removed from v4.0+, use \
|
76
|
+
a ActsAsTaggableOn.default_parser instead
|
77
|
+
WARNING
|
78
|
+
@delimiter = string
|
68
79
|
end
|
69
80
|
end
|
70
81
|
|
@@ -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
|
@@ -20,6 +20,8 @@ module ActsAsTaggableOn
|
|
20
20
|
end
|
21
21
|
|
22
22
|
### SCOPES:
|
23
|
+
scope :most_used, ->(limit = 20) { order('taggings_count desc').limit(limit) }
|
24
|
+
scope :least_used, ->(limit = 20) { order('taggings_count asc').limit(limit) }
|
23
25
|
|
24
26
|
def self.named(name)
|
25
27
|
if ActsAsTaggableOn.strict_case_match
|
@@ -101,6 +103,9 @@ module ActsAsTaggableOn
|
|
101
103
|
end
|
102
104
|
|
103
105
|
class << self
|
106
|
+
|
107
|
+
|
108
|
+
|
104
109
|
private
|
105
110
|
|
106
111
|
def comparable_name(str)
|
@@ -4,8 +4,10 @@ require 'active_support/core_ext/module/delegation'
|
|
4
4
|
module ActsAsTaggableOn
|
5
5
|
class TagList < Array
|
6
6
|
attr_accessor :owner
|
7
|
+
attr_accessor :parser
|
7
8
|
|
8
9
|
def initialize(*args)
|
10
|
+
@parser = ActsAsTaggableOn.default_parser
|
9
11
|
add(*args)
|
10
12
|
end
|
11
13
|
|
@@ -88,9 +90,11 @@ module ActsAsTaggableOn
|
|
88
90
|
|
89
91
|
def extract_and_apply_options!(args)
|
90
92
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
91
|
-
options.assert_valid_keys :parse
|
93
|
+
options.assert_valid_keys :parse, :parser
|
92
94
|
|
93
|
-
|
95
|
+
parser = options[:parser] ? options[:parser] : @parser
|
96
|
+
|
97
|
+
args.map! { |a| parser.new(a).parse } if options[:parse] || options[:parser]
|
94
98
|
|
95
99
|
args.flatten!
|
96
100
|
end
|
@@ -101,9 +105,9 @@ module ActsAsTaggableOn
|
|
101
105
|
ActiveRecord::Base.logger.warn <<WARNING
|
102
106
|
ActsAsTaggableOn::TagList.from is deprecated \
|
103
107
|
and will be removed from v4.0+, use \
|
104
|
-
ActsAsTaggableOn::
|
108
|
+
ActsAsTaggableOn::DefaultParser.new instead
|
105
109
|
WARNING
|
106
|
-
|
110
|
+
@parser.new(string).parse
|
107
111
|
end
|
108
112
|
|
109
113
|
|
@@ -7,72 +7,15 @@ module ActsAsTaggableOn
|
|
7
7
|
# tag_list # ["One", "Two", "Three"]
|
8
8
|
module TagListParser
|
9
9
|
class << self
|
10
|
+
## DEPRECATED
|
10
11
|
def parse(string)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# Append the matched tag to the tag list
|
18
|
-
tag_list << Regexp.last_match[2]
|
19
|
-
# Return the matched delimiter ($3) to replace the matched items
|
20
|
-
''
|
21
|
-
}
|
22
|
-
|
23
|
-
string.gsub!(single_quote_pattern) {
|
24
|
-
# Append the matched tag ($2) to the tag list
|
25
|
-
tag_list << Regexp.last_match[2]
|
26
|
-
# Return an empty string to replace the matched items
|
27
|
-
''
|
28
|
-
}
|
29
|
-
|
30
|
-
# split the string by the delimiter
|
31
|
-
# and add to the tag_list
|
32
|
-
tag_list.add(string.split(Regexp.new delimiter))
|
33
|
-
end
|
12
|
+
ActiveRecord::Base.logger.warn <<WARNING
|
13
|
+
ActsAsTaggableOn::TagListParser.parse is deprecated \
|
14
|
+
and will be removed from v4.0+, use \
|
15
|
+
ActsAsTaggableOn::TagListParser.new instead
|
16
|
+
WARNING
|
17
|
+
DefaultParser.new(string).parse
|
34
18
|
end
|
35
|
-
|
36
|
-
|
37
|
-
# private
|
38
|
-
def delimiter
|
39
|
-
# Parse the quoted tags
|
40
|
-
d = ActsAsTaggableOn.delimiter
|
41
|
-
# Separate multiple delimiters by bitwise operator
|
42
|
-
d = d.join('|') if d.kind_of?(Array)
|
43
|
-
d
|
44
|
-
end
|
45
|
-
|
46
|
-
# ( # Tag start delimiter ($1)
|
47
|
-
# \A | # Either string start or
|
48
|
-
# #{delimiter} # a delimiter
|
49
|
-
# )
|
50
|
-
# \s*" # quote (") optionally preceded by whitespace
|
51
|
-
# (.*?) # Tag ($2)
|
52
|
-
# "\s* # quote (") optionally followed by whitespace
|
53
|
-
# (?= # Tag end delimiter (not consumed; is zero-length lookahead)
|
54
|
-
# #{delimiter}\s* | # Either a delimiter optionally followed by whitespace or
|
55
|
-
# \z # string end
|
56
|
-
# )
|
57
|
-
def double_quote_pattern
|
58
|
-
/(\A|#{delimiter})\s*"(.*?)"\s*(?=#{delimiter}\s*|\z)/
|
59
|
-
end
|
60
|
-
|
61
|
-
# ( # Tag start delimiter ($1)
|
62
|
-
# \A | # Either string start or
|
63
|
-
# #{delimiter} # a delimiter
|
64
|
-
# )
|
65
|
-
# \s*' # quote (') optionally preceded by whitespace
|
66
|
-
# (.*?) # Tag ($2)
|
67
|
-
# '\s* # quote (') optionally followed by whitespace
|
68
|
-
# (?= # Tag end delimiter (not consumed; is zero-length lookahead)
|
69
|
-
# #{delimiter}\s* | d # Either a delimiter optionally followed by whitespace or
|
70
|
-
# \z # string end
|
71
|
-
# )
|
72
|
-
def single_quote_pattern
|
73
|
-
/(\A|#{delimiter})\s*'(.*?)'\s*(?=#{delimiter}\s*|\z)/
|
74
|
-
end
|
75
|
-
|
76
19
|
end
|
77
20
|
end
|
78
21
|
end
|
@@ -135,7 +135,7 @@ module ActsAsTaggableOn::Taggable
|
|
135
135
|
table_name_pkey = "#{table_name}.#{primary_key}"
|
136
136
|
if ActsAsTaggableOn::Utils.using_mysql?
|
137
137
|
# See https://github.com/mbleigh/acts-as-taggable-on/pull/457 for details
|
138
|
-
scoped_ids =
|
138
|
+
scoped_ids = pluck(table_name_pkey)
|
139
139
|
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN (?)", scoped_ids)
|
140
140
|
else
|
141
141
|
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(select(table_name_pkey))})")
|
@@ -77,15 +77,15 @@ module ActsAsTaggableOn::Taggable
|
|
77
77
|
# * <tt>:end_at</tt> - Restrict the tags to those created before a certain time
|
78
78
|
#
|
79
79
|
# Example:
|
80
|
-
# User.tagged_with("awesome", "cool") # Users that are tagged with awesome and cool
|
81
|
-
# User.tagged_with("awesome", "cool", :exclude => true) # Users that are not tagged with awesome or cool
|
82
|
-
# User.tagged_with("awesome", "cool", :any => true) # Users that are tagged with awesome or cool
|
83
|
-
# User.tagged_with("awesome", "cool", :any => true, :order_by_matching_tag_count => true) # Sort by users who match the most tags, descending
|
84
|
-
# User.tagged_with("awesome", "cool", :match_all => true) # Users that are tagged with just awesome and cool
|
85
|
-
# User.tagged_with("awesome", "cool", :owned_by => foo ) # Users that are tagged with just awesome and cool by 'foo'
|
86
|
-
# User.tagged_with("awesome", "cool", :owned_by => foo, :start_at => Date.today ) # Users that are tagged with just awesome, cool by 'foo' and starting today
|
80
|
+
# User.tagged_with(["awesome", "cool"]) # Users that are tagged with awesome and cool
|
81
|
+
# User.tagged_with(["awesome", "cool"], :exclude => true) # Users that are not tagged with awesome or cool
|
82
|
+
# User.tagged_with(["awesome", "cool"], :any => true) # Users that are tagged with awesome or cool
|
83
|
+
# User.tagged_with(["awesome", "cool"], :any => true, :order_by_matching_tag_count => true) # Sort by users who match the most tags, descending
|
84
|
+
# User.tagged_with(["awesome", "cool"], :match_all => true) # Users that are tagged with just awesome and cool
|
85
|
+
# User.tagged_with(["awesome", "cool"], :owned_by => foo ) # Users that are tagged with just awesome and cool by 'foo'
|
86
|
+
# User.tagged_with(["awesome", "cool"], :owned_by => foo, :start_at => Date.today ) # Users that are tagged with just awesome, cool by 'foo' and starting today
|
87
87
|
def tagged_with(tags, options = {})
|
88
|
-
tag_list = ActsAsTaggableOn
|
88
|
+
tag_list = ActsAsTaggableOn.default_parser.new(tags).parse
|
89
89
|
options = options.dup
|
90
90
|
empty_result = where('1 = 0')
|
91
91
|
|
@@ -278,7 +278,7 @@ module ActsAsTaggableOn::Taggable
|
|
278
278
|
if instance_variable_get(variable_name)
|
279
279
|
instance_variable_get(variable_name)
|
280
280
|
elsif cached_tag_list_on(context) && self.class.caching_tag_list_on?(context)
|
281
|
-
instance_variable_set(variable_name, ActsAsTaggableOn
|
281
|
+
instance_variable_set(variable_name, ActsAsTaggableOn.default_parser.new(cached_tag_list_on(context)).parse)
|
282
282
|
else
|
283
283
|
instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(tags_on(context).map(&:name)))
|
284
284
|
end
|
@@ -328,7 +328,7 @@ module ActsAsTaggableOn::Taggable
|
|
328
328
|
variable_name = "@#{context.to_s.singularize}_list"
|
329
329
|
process_dirty_object(context, new_list) unless custom_contexts.include?(context.to_s)
|
330
330
|
|
331
|
-
instance_variable_set(variable_name, ActsAsTaggableOn
|
331
|
+
instance_variable_set(variable_name, ActsAsTaggableOn.default_parser.new(new_list).parse)
|
332
332
|
end
|
333
333
|
|
334
334
|
def tagging_contexts
|
@@ -342,13 +342,13 @@ module ActsAsTaggableOn::Taggable
|
|
342
342
|
if changed_attributes.include?(attrib)
|
343
343
|
# The attribute already has an unsaved change.
|
344
344
|
old = changed_attributes[attrib]
|
345
|
-
changed_attributes.delete(attrib) if old.to_s == value.to_s
|
345
|
+
@changed_attributes.delete(attrib) if old.to_s == value.to_s
|
346
346
|
else
|
347
347
|
old = tag_list_on(context)
|
348
348
|
if self.class.preserve_tag_order
|
349
|
-
changed_attributes[attrib] = old if old.to_s != value.to_s
|
349
|
+
@changed_attributes[attrib] = old if old.to_s != value.to_s
|
350
350
|
else
|
351
|
-
changed_attributes[attrib] = old.to_s if old.sort != ActsAsTaggableOn
|
351
|
+
@changed_attributes[attrib] = old.to_s if old.sort != ActsAsTaggableOn.default_parser.new(value).parse.sort
|
352
352
|
end
|
353
353
|
end
|
354
354
|
end
|
@@ -420,6 +420,18 @@ module ActsAsTaggableOn::Taggable
|
|
420
420
|
|
421
421
|
private
|
422
422
|
|
423
|
+
# Filters the tag lists from the attribute names.
|
424
|
+
def attributes_for_update(attribute_names)
|
425
|
+
tag_lists = tag_types.map {|tags_type| "#{tags_type.to_s.singularize}_list"}
|
426
|
+
super.delete_if {|attr| tag_lists.include? attr }
|
427
|
+
end
|
428
|
+
|
429
|
+
# Filters the tag lists from the attribute names.
|
430
|
+
def attributes_for_create(attribute_names)
|
431
|
+
tag_lists = tag_types.map {|tags_type| "#{tags_type.to_s.singularize}_list"}
|
432
|
+
super.delete_if {|attr| tag_lists.include? attr }
|
433
|
+
end
|
434
|
+
|
423
435
|
##
|
424
436
|
# Override this hook if you wish to subclass {ActsAsTaggableOn::Tag} --
|
425
437
|
# context is provided so that you may conditionally use a Tag subclass
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe ActsAsTaggableOn::DefaultParser do
|
5
|
+
it '#parse should return empty array if empty array is passed' do
|
6
|
+
parser = ActsAsTaggableOn::DefaultParser.new([])
|
7
|
+
expect(parser.parse).to be_empty
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'Multiple Delimiter' do
|
11
|
+
before do
|
12
|
+
@old_delimiter = ActsAsTaggableOn.delimiter
|
13
|
+
end
|
14
|
+
|
15
|
+
after do
|
16
|
+
ActsAsTaggableOn.delimiter = @old_delimiter
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should separate tags by delimiters' do
|
20
|
+
ActsAsTaggableOn.delimiter = [',', ' ', '\|']
|
21
|
+
parser = ActsAsTaggableOn::DefaultParser.new('cool, data|I have')
|
22
|
+
expect(parser.parse.to_s).to eq('cool, data, I, have')
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should escape quote' do
|
26
|
+
ActsAsTaggableOn.delimiter = [',', ' ', '\|']
|
27
|
+
parser = ActsAsTaggableOn::DefaultParser.new("'I have'|cool, data")
|
28
|
+
expect(parser.parse.to_s).to eq('"I have", cool, data')
|
29
|
+
|
30
|
+
parser = ActsAsTaggableOn::DefaultParser.new('"I, have"|cool, data')
|
31
|
+
expect(parser.parse.to_s).to eq('"I, have", cool, data')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should work for utf8 delimiter and long delimiter' do
|
35
|
+
ActsAsTaggableOn.delimiter = [',', '的', '可能是']
|
36
|
+
parser = ActsAsTaggableOn::DefaultParser.new('我的东西可能是不见了,还好有备份')
|
37
|
+
expect(parser.parse.to_s).to eq('我, 东西, 不见了, 还好有备份')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should work for multiple quoted tags' do
|
41
|
+
ActsAsTaggableOn.delimiter = [',']
|
42
|
+
parser = ActsAsTaggableOn::DefaultParser.new('"Ruby Monsters","eat Katzenzungen"')
|
43
|
+
expect(parser.parse.to_s).to eq('Ruby Monsters, eat Katzenzungen')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe ActsAsTaggableOn::GenericParser do
|
5
|
+
it '#parse should return empty array if empty tag string is passed' do
|
6
|
+
tag_list = ActsAsTaggableOn::GenericParser.new('')
|
7
|
+
expect(tag_list.parse).to be_empty
|
8
|
+
end
|
9
|
+
|
10
|
+
it '#parse should separate tags by comma' do
|
11
|
+
tag_list = ActsAsTaggableOn::GenericParser.new('cool,data,,I,have')
|
12
|
+
expect(tag_list.parse).to eq(%w(cool data I have))
|
13
|
+
end
|
14
|
+
end
|
@@ -114,7 +114,36 @@ describe ActsAsTaggableOn::TagList do
|
|
114
114
|
|
115
115
|
ActsAsTaggableOn.force_lowercase = false
|
116
116
|
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe 'custom parser' do
|
120
|
+
let(:parser) { double(parse: %w(cool wicked)) }
|
121
|
+
let(:parser_class) { stub_const('MyParser', Class) }
|
122
|
+
|
123
|
+
it 'should use a the default parser if none is set as parameter' do
|
124
|
+
allow(ActsAsTaggableOn.default_parser).to receive(:new).and_return(parser)
|
125
|
+
ActsAsTaggableOn::TagList.new('cool, wicked', parse: true)
|
126
|
+
|
127
|
+
expect(parser).to have_received(:parse)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should use the custom parser passed as parameter' do
|
131
|
+
allow(parser_class).to receive(:new).and_return(parser)
|
117
132
|
|
133
|
+
ActsAsTaggableOn::TagList.new('cool, wicked', parser: parser_class)
|
134
|
+
|
135
|
+
expect(parser).to have_received(:parse)
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'should use the parser setted as attribute' do
|
139
|
+
allow(parser_class).to receive(:new).with('new, tag').and_return(parser)
|
140
|
+
|
141
|
+
tag_list = ActsAsTaggableOn::TagList.new('example')
|
142
|
+
tag_list.parser = parser_class
|
143
|
+
tag_list.add('new, tag', parse: true)
|
144
|
+
|
145
|
+
expect(parser).to have_received(:parse)
|
146
|
+
end
|
118
147
|
end
|
119
148
|
|
120
149
|
|
@@ -286,4 +286,24 @@ describe ActsAsTaggableOn::Tag do
|
|
286
286
|
end
|
287
287
|
end
|
288
288
|
end
|
289
|
+
|
290
|
+
describe 'popular tags' do
|
291
|
+
before do
|
292
|
+
%w(sports rails linux tennis golden_syrup).each_with_index do |t, i|
|
293
|
+
tag = ActsAsTaggableOn::Tag.new(name: t)
|
294
|
+
tag.taggings_count = i
|
295
|
+
tag.save!
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
it 'should find the most popular tags' do
|
300
|
+
expect(ActsAsTaggableOn::Tag.most_used(3).first.name).to eq("golden_syrup")
|
301
|
+
expect(ActsAsTaggableOn::Tag.most_used(3).length).to eq(3)
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'should find the least popular tags' do
|
305
|
+
expect(ActsAsTaggableOn::Tag.least_used(3).first.name).to eq("sports")
|
306
|
+
expect(ActsAsTaggableOn::Tag.least_used(3).length).to eq(3)
|
307
|
+
end
|
308
|
+
end
|
289
309
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts-as-taggable-on
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Bleigh
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-08-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -17,7 +17,7 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '3'
|
20
|
+
version: '3.2'
|
21
21
|
- - "<"
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: '5'
|
@@ -27,7 +27,7 @@ dependencies:
|
|
27
27
|
requirements:
|
28
28
|
- - ">="
|
29
29
|
- !ruby/object:Gem::Version
|
30
|
-
version: '3'
|
30
|
+
version: '3.2'
|
31
31
|
- - "<"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '5'
|
@@ -77,16 +77,16 @@ dependencies:
|
|
77
77
|
name: rspec-rails
|
78
78
|
requirement: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
82
|
+
version: '0'
|
83
83
|
type: :development
|
84
84
|
prerelease: false
|
85
85
|
version_requirements: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- - "
|
87
|
+
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
89
|
+
version: '0'
|
90
90
|
- !ruby/object:Gem::Dependency
|
91
91
|
name: rspec-its
|
92
92
|
requirement: !ruby/object:Gem::Requirement
|
@@ -172,11 +172,14 @@ files:
|
|
172
172
|
- gemfiles/activerecord_3.2.gemfile
|
173
173
|
- gemfiles/activerecord_4.0.gemfile
|
174
174
|
- gemfiles/activerecord_4.1.gemfile
|
175
|
+
- gemfiles/activerecord_4.2.gemfile
|
175
176
|
- gemfiles/activerecord_edge.gemfile
|
176
177
|
- lib/acts-as-taggable-on.rb
|
177
178
|
- lib/acts_as_taggable_on.rb
|
178
179
|
- lib/acts_as_taggable_on/compatibility.rb
|
180
|
+
- lib/acts_as_taggable_on/default_parser.rb
|
179
181
|
- lib/acts_as_taggable_on/engine.rb
|
182
|
+
- lib/acts_as_taggable_on/generic_parser.rb
|
180
183
|
- lib/acts_as_taggable_on/tag.rb
|
181
184
|
- lib/acts_as_taggable_on/tag_list.rb
|
182
185
|
- lib/acts_as_taggable_on/tag_list_parser.rb
|
@@ -195,6 +198,8 @@ files:
|
|
195
198
|
- spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb
|
196
199
|
- spec/acts_as_taggable_on/acts_as_tagger_spec.rb
|
197
200
|
- spec/acts_as_taggable_on/caching_spec.rb
|
201
|
+
- spec/acts_as_taggable_on/default_parser_spec.rb
|
202
|
+
- spec/acts_as_taggable_on/generic_parser_spec.rb
|
198
203
|
- spec/acts_as_taggable_on/related_spec.rb
|
199
204
|
- spec/acts_as_taggable_on/single_table_inheritance_spec.rb
|
200
205
|
- spec/acts_as_taggable_on/tag_list_parser_spec.rb
|
@@ -261,7 +266,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
261
266
|
version: '0'
|
262
267
|
requirements: []
|
263
268
|
rubyforge_project:
|
264
|
-
rubygems_version: 2.
|
269
|
+
rubygems_version: 2.4.1
|
265
270
|
signing_key:
|
266
271
|
specification_version: 4
|
267
272
|
summary: Advanced tagging for Rails.
|
@@ -269,6 +274,8 @@ test_files:
|
|
269
274
|
- spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb
|
270
275
|
- spec/acts_as_taggable_on/acts_as_tagger_spec.rb
|
271
276
|
- spec/acts_as_taggable_on/caching_spec.rb
|
277
|
+
- spec/acts_as_taggable_on/default_parser_spec.rb
|
278
|
+
- spec/acts_as_taggable_on/generic_parser_spec.rb
|
272
279
|
- spec/acts_as_taggable_on/related_spec.rb
|
273
280
|
- spec/acts_as_taggable_on/single_table_inheritance_spec.rb
|
274
281
|
- spec/acts_as_taggable_on/tag_list_parser_spec.rb
|