acts-as-taggable-on 3.2.3 → 3.3.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -7
  3. data/Appraisals +0 -4
  4. data/Gemfile +0 -1
  5. data/README.md +1 -1
  6. data/acts-as-taggable-on.gemspec +6 -8
  7. data/db/migrate/4_add_missing_taggable_index.rb +9 -0
  8. data/gemfiles/activerecord_3.2.gemfile +0 -2
  9. data/gemfiles/activerecord_4.0.gemfile +0 -2
  10. data/gemfiles/activerecord_4.1.gemfile +0 -2
  11. data/gemfiles/activerecord_edge.gemfile +0 -2
  12. data/lib/acts-as-taggable-on.rb +28 -20
  13. data/lib/acts_as_taggable_on/tag_list.rb +13 -77
  14. data/lib/acts_as_taggable_on/tag_list_parser.rb +78 -0
  15. data/lib/acts_as_taggable_on/{acts_as_taggable_on → taggable}/core.rb +46 -23
  16. data/lib/acts_as_taggable_on/{acts_as_taggable_on → taggable}/ownership.rb +1 -1
  17. data/lib/acts_as_taggable_on/taggable.rb +7 -6
  18. data/lib/acts_as_taggable_on/tagging.rb +1 -1
  19. data/lib/acts_as_taggable_on/tags_helper.rb +2 -2
  20. data/lib/acts_as_taggable_on/utils.rb +2 -26
  21. data/lib/acts_as_taggable_on/version.rb +1 -1
  22. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +1 -1
  23. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +1 -0
  24. data/spec/acts_as_taggable_on/caching_spec.rb +1 -0
  25. data/spec/acts_as_taggable_on/related_spec.rb +1 -0
  26. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +1 -0
  27. data/spec/acts_as_taggable_on/tag_list_parser_spec.rb +46 -0
  28. data/spec/acts_as_taggable_on/tag_list_spec.rb +3 -40
  29. data/spec/acts_as_taggable_on/tag_spec.rb +29 -35
  30. data/spec/acts_as_taggable_on/taggable/dirty_spec.rb +127 -0
  31. data/spec/acts_as_taggable_on/taggable_spec.rb +76 -163
  32. data/spec/acts_as_taggable_on/tagger_spec.rb +1 -1
  33. data/spec/acts_as_taggable_on/tagging_spec.rb +13 -1
  34. data/spec/acts_as_taggable_on/tags_helper_spec.rb +1 -0
  35. data/spec/acts_as_taggable_on/utils_spec.rb +1 -0
  36. data/spec/internal/app/models/cached_model_with_array.rb +1 -1
  37. data/spec/internal/app/models/models.rb +2 -2
  38. data/spec/internal/db/schema.rb +2 -2
  39. data/spec/spec_helper.rb +0 -1
  40. data/spec/support/0-helpers.rb +32 -0
  41. metadata +39 -67
  42. data/spec/schema.rb +0 -82
  43. /data/lib/acts_as_taggable_on/{acts_as_taggable_on/compatibility.rb → compatibility.rb} +0 -0
  44. /data/lib/acts_as_taggable_on/{acts_as_taggable_on → taggable}/cache.rb +0 -0
  45. /data/lib/acts_as_taggable_on/{acts_as_taggable_on → taggable}/collection.rb +0 -0
  46. /data/lib/acts_as_taggable_on/{acts_as_taggable_on → taggable}/dirty.rb +0 -0
  47. /data/lib/acts_as_taggable_on/{acts_as_taggable_on → taggable}/related.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 755e4d829ddc31ccd6ffb8385594ed8c5c64971f
4
- data.tar.gz: 1ce12972b7d9d436484d619843a7bdf316a5c3b7
3
+ metadata.gz: 5c20928991764b079db87d3d1ba32846d0a3e01f
4
+ data.tar.gz: 94cae20632e6d61d21cd55921cb7ee284cb6f766
5
5
  SHA512:
6
- metadata.gz: c8f129a07903598bcb765b260bd14db168b4a6fbf6989cef816157ae6350cadd733f75ca6cc0169e00fa8c19b59710198b868f52044f07efca451c44aa9a046c
7
- data.tar.gz: 578b3d36b654b9343a36df3a7086b40b3ef32f6b43973d92c0f31251ea095405b692d2ef131ba74086366d117eef0fcd2334b51ce8f7ce3c0266481819a0a2a1
6
+ metadata.gz: 29cfdac7542aed8bb4f8f55a0c7d33f592739ae1f887a5399e0bd34fd686a5824b3e5335f5037a0ada5928001b4a8b3ca124d21771d5af648fc6f55735bf31da
7
+ data.tar.gz: 535aa2433b00889491d6aaf7f488b372ebc1ac2e60f8d9cd2337ae07a19ce2ce1bce3438a1f2db48c77b52a3a57c56648c8c31ddf1b4f0c2447bebae27d67fe0
data/.travis.yml CHANGED
@@ -25,18 +25,19 @@ matrix:
25
25
  allow_failures:
26
26
  - gemfile: gemfiles/activerecord_edge.gemfile
27
27
  - rvm: rbx-2
28
+ - rvm: ruby-head
28
29
  exclude:
29
30
  - rvm: 1.9.3
30
- gemfile: ci/Gemfile.activerecord-4.0.x
31
+ gemfile: gemfiles/activerecord_4.0.gemfile
31
32
  - rvm: 1.9.3
32
- gemfile: ci/Gemfile.activerecord-4.1.x
33
+ gemfile: gemfiles/activerecord_4.1.gemfile
33
34
  - rvm: 1.9.3
34
- gemfile: ci/Gemfile.activerecord-edge
35
+ gemfile: gemfiles/activerecord_edge.gemfile
35
36
  - rvm: rbx-2
36
- gemfile: ci/Gemfile.activerecord-3.2.x
37
+ gemfile: gemfiles/activerecord_3.2.gemfile
37
38
  - rvm: ruby-head
38
- gemfile: ci/Gemfile.activerecord-3.2.x
39
+ gemfile: gemfiles/activerecord_3.2.gemfile
39
40
  - rvm: ruby-head
40
- gemfile: ci/Gemfile.activerecord-4.0.x
41
+ gemfile: gemfiles/activerecord_4.0.gemfile
41
42
  - rvm: ruby-head
42
- gemfile: ci/Gemfile.activerecord-4.1.x
43
+ gemfile: gemfiles/activerecord_4.1.gemfile
data/Appraisals CHANGED
@@ -1,20 +1,16 @@
1
1
  appraise "activerecord-3.2" do
2
2
  gem "activerecord", "~> 3.2"
3
- gem "actionpack", "~> 3.2"
4
3
  end
5
4
 
6
5
  appraise "activerecord-4.0" do
7
6
  gem "activerecord", "~> 4.0"
8
- gem "actionpack", "~> 4.0"
9
7
  end
10
8
 
11
9
  appraise "activerecord-4.1" do
12
10
  gem "activerecord", "~> 4.1"
13
- gem "actionpack", "~> 4.1"
14
11
  end
15
12
 
16
13
  appraise "activerecord-edge" do
17
14
  gem "activerecord", github: "rails/rails"
18
- gem "actionpack", github: "rails/rails"
19
15
  gem 'arel', github: 'rails/arel'
20
16
  end
data/Gemfile CHANGED
@@ -7,5 +7,4 @@ group :local_development do
7
7
  gem 'guard-rspec'
8
8
  gem 'appraisal'
9
9
  gem 'rake'
10
- gem 'byebug'
11
10
  end
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # ActsAsTaggableOn
2
2
  [![Build Status](https://secure.travis-ci.org/mbleigh/acts-as-taggable-on.png)](http://travis-ci.org/mbleigh/acts-as-taggable-on)
3
3
  [![Code Climate](https://codeclimate.com/github/mbleigh/acts-as-taggable-on.png)](https://codeclimate.com/github/mbleigh/acts-as-taggable-on)
4
- [![Inline docs](http://inch-pages.github.io/github/mbleigh/acts-as-taggable-on.png)](http://inch-pages.github.io/github/mbleigh/acts-as-taggable-on)
4
+ [![Inline docs](http://inch-ci.org/github/mbleigh/acts-as-taggable-on.png)](http://inch-ci.org/github/mbleigh/acts-as-taggable-on)
5
5
 
6
6
  This plugin was originally based on Acts as Taggable on Steroids by Jonathan Viney.
7
7
  It has evolved substantially since that point, but all credit goes to him for the
@@ -23,16 +23,14 @@ Gem::Specification.new do |gem|
23
23
  end
24
24
 
25
25
  gem.add_runtime_dependency 'activerecord', ['>= 3', '< 5']
26
- gem.add_runtime_dependency 'actionpack', ['>= 3', '< 5']
27
26
 
28
27
  gem.add_development_dependency 'sqlite3'
29
- gem.add_development_dependency 'mysql2'
28
+ gem.add_development_dependency 'mysql2', '~> 0.3.7'
30
29
  gem.add_development_dependency 'pg'
31
30
 
32
- gem.add_development_dependency 'rspec-rails' , '~> 3.0.0.beta1'
33
- gem.add_development_dependency 'rspec-its', '~> 1.0'
34
- gem.add_development_dependency 'rspec', '3.0.0.beta2'
35
- gem.add_development_dependency 'ammeter', '~> 1.0'
36
- gem.add_development_dependency 'barrier', '~> 1.0'
37
- gem.add_development_dependency 'database_cleaner', '~> 1.2'
31
+ gem.add_development_dependency 'rspec-rails' , '~> 3.0.0.beta'
32
+ gem.add_development_dependency 'rspec-its'
33
+ gem.add_development_dependency 'rspec'
34
+ gem.add_development_dependency 'barrier'
35
+ gem.add_development_dependency 'database_cleaner'
38
36
  end
@@ -0,0 +1,9 @@
1
+ class AddMissingTaggableIndex < ActiveRecord::Migration
2
+ def self.up
3
+ add_index :taggings, [:taggable_id, :taggable_type, :context]
4
+ end
5
+
6
+ def self.down
7
+ remove_index :taggings, [:taggable_id, :taggable_type, :context]
8
+ end
9
+ end
@@ -3,14 +3,12 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "activerecord", "~> 3.2"
6
- gem "actionpack", "~> 3.2"
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"
13
- gem "byebug"
14
12
  end
15
13
 
16
14
  gemspec :path => "../"
@@ -3,14 +3,12 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "activerecord", "~> 4.0"
6
- gem "actionpack", "~> 4.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"
13
- gem "byebug"
14
12
  end
15
13
 
16
14
  gemspec :path => "../"
@@ -3,14 +3,12 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "activerecord", "~> 4.1"
6
- gem "actionpack", "~> 4.1"
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"
13
- gem "byebug"
14
12
  end
15
13
 
16
14
  gemspec :path => "../"
@@ -3,7 +3,6 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "activerecord", :github => "rails/rails"
6
- gem "actionpack", :github => "rails/rails"
7
6
  gem "arel", :github => "rails/arel"
8
7
 
9
8
  group :local_development do
@@ -11,7 +10,6 @@ group :local_development do
11
10
  gem "guard-rspec"
12
11
  gem "appraisal"
13
12
  gem "rake"
14
- gem "byebug"
15
13
  end
16
14
 
17
15
  gemspec :path => "../"
@@ -3,9 +3,35 @@ require 'active_record/version'
3
3
  require 'active_support/core_ext/module'
4
4
  require 'action_view'
5
5
 
6
+ require_relative 'acts_as_taggable_on/engine' if defined?(Rails)
7
+
6
8
  require 'digest/sha1'
7
9
 
8
10
  module ActsAsTaggableOn
11
+ extend ActiveSupport::Autoload
12
+
13
+ autoload :Tag
14
+ autoload :TagList
15
+ autoload :TagListParser
16
+ autoload :Taggable
17
+ autoload :Tagger
18
+ autoload :Tagging
19
+ autoload :TagsHelper
20
+ autoload :VERSION
21
+
22
+ autoload_under 'taggable' do
23
+ autoload :Cache
24
+ autoload :Collection
25
+ autoload :Core
26
+ autoload :Dirty
27
+ autoload :Ownership
28
+ autoload :Related
29
+ end
30
+
31
+ autoload :Utils
32
+ autoload :Compatibility
33
+
34
+
9
35
  class DuplicateTagError < StandardError
10
36
  end
11
37
 
@@ -16,7 +42,7 @@ module ActsAsTaggableOn
16
42
 
17
43
  def self.method_missing(method_name, *args, &block)
18
44
  @configuration.respond_to?(method_name) ?
19
- @configuration.send(method_name, *args, &block) : super
45
+ @configuration.send(method_name, *args, &block) : super
20
46
  end
21
47
 
22
48
  def self.respond_to?(method_name, include_private=false)
@@ -31,7 +57,7 @@ module ActsAsTaggableOn
31
57
 
32
58
  class Configuration
33
59
  attr_accessor :delimiter, :force_lowercase, :force_parameterize,
34
- :strict_case_match, :remove_unused_tags
60
+ :strict_case_match, :remove_unused_tags
35
61
 
36
62
  def initialize
37
63
  @delimiter = ','
@@ -45,24 +71,6 @@ module ActsAsTaggableOn
45
71
  setup
46
72
  end
47
73
 
48
- require 'acts_as_taggable_on/utils'
49
-
50
- require 'acts_as_taggable_on/taggable'
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'
58
-
59
- require 'acts_as_taggable_on/tagger'
60
- require 'acts_as_taggable_on/tag'
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'
65
-
66
74
  ActiveSupport.on_load(:active_record) do
67
75
  extend ActsAsTaggableOn::Compatibility
68
76
  extend ActsAsTaggableOn::Taggable
@@ -1,3 +1,4 @@
1
+
1
2
  require 'active_support/core_ext/module/delegation'
2
3
 
3
4
  module ActsAsTaggableOn
@@ -8,82 +9,6 @@ module ActsAsTaggableOn
8
9
  add(*args)
9
10
  end
10
11
 
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
12
  ##
88
13
  # Add tags to the tag_list. Duplicate or blank tags will be ignored.
89
14
  # Use the <tt>:parse</tt> option to add an unparsed tag string.
@@ -165,12 +90,23 @@ module ActsAsTaggableOn
165
90
  options = args.last.is_a?(Hash) ? args.pop : {}
166
91
  options.assert_valid_keys :parse
167
92
 
168
- args.map! { |a| self.class.from(a) } if options[:parse]
93
+ args.map! { |a| TagListParser.parse(a) } if options[:parse]
169
94
 
170
95
  args.flatten!
171
96
  end
172
97
 
173
98
 
99
+ ## DEPRECATED
100
+ def self.from(string)
101
+ ActiveRecord::Base.logger.warn <<WARNING
102
+ ActsAsTaggableOn::TagList.from is deprecated \
103
+ and will be removed from v4.0+, use \
104
+ ActsAsTaggableOn::TagListParser.parse instead
105
+ WARNING
106
+ TagListParser.parse(string)
107
+ end
108
+
109
+
174
110
  end
175
111
  end
176
112
 
@@ -0,0 +1,78 @@
1
+ module ActsAsTaggableOn
2
+ ##
3
+ # Returns a new TagList using the given tag string.
4
+ #
5
+ # Example:
6
+ # tag_list = ActsAsTaggableOn::TagListParser.parse("One , Two, Three")
7
+ # tag_list # ["One", "Two", "Three"]
8
+ module TagListParser
9
+ class << self
10
+ def parse(string)
11
+ string = string.join(ActsAsTaggableOn.glue) if string.respond_to?(:join)
12
+ TagList.new.tap do |tag_list|
13
+ string = string.to_s.dup
14
+
15
+
16
+ string.gsub!(double_quote_pattern) {
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
34
+ 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
+ end
77
+ end
78
+ end
@@ -73,6 +73,8 @@ module ActsAsTaggableOn::Taggable
73
73
  # * <tt>:order_by_matching_tag_count</tt> - if set to true and used with :any, sort by objects matching the most tags, descending
74
74
  # * <tt>:match_all</tt> - if set to true, return objects that are *ONLY* tagged with the specified tags
75
75
  # * <tt>:owned_by</tt> - return objects that are *ONLY* owned by the owner
76
+ # * <tt>:start_at</tt> - Restrict the tags to those created after a certain time
77
+ # * <tt>:end_at</tt> - Restrict the tags to those created before a certain time
76
78
  #
77
79
  # Example:
78
80
  # User.tagged_with("awesome", "cool") # Users that are tagged with awesome and cool
@@ -81,8 +83,9 @@ module ActsAsTaggableOn::Taggable
81
83
  # User.tagged_with("awesome", "cool", :any => true, :order_by_matching_tag_count => true) # Sort by users who match the most tags, descending
82
84
  # User.tagged_with("awesome", "cool", :match_all => true) # Users that are tagged with just awesome and cool
83
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
84
87
  def tagged_with(tags, options = {})
85
- tag_list = ActsAsTaggableOn::TagList.from(tags)
88
+ tag_list = ActsAsTaggableOn::TagListParser.parse(tags)
86
89
  options = options.dup
87
90
  empty_result = where('1 = 0')
88
91
 
@@ -109,14 +112,17 @@ module ActsAsTaggableOn::Taggable
109
112
  conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id FROM #{ActsAsTaggableOn::Tagging.table_name} JOIN #{ActsAsTaggableOn::Tag.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)})"
110
113
 
111
114
  if owned_by
112
- joins << "JOIN #{ActsAsTaggableOn::Tagging.table_name}" \
113
- " ON #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" \
114
- " AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)}" \
115
- " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = #{quote_value(owned_by.id, nil)}" \
116
- " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = #{quote_value(owned_by.class.base_class.to_s, nil)}"
115
+ joins << "JOIN #{ActsAsTaggableOn::Tagging.table_name}" +
116
+ " ON #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
117
+ " AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)}" +
118
+ " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = #{quote_value(owned_by.id, nil)}" +
119
+ " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = #{quote_value(owned_by.class.base_class.to_s, nil)}"
120
+
121
+ joins << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
122
+ joins << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
117
123
  end
118
124
 
119
- elsif options.delete(:any)
125
+ elsif any = options.delete(:any)
120
126
  # get tags, drop out if nothing returned (we need at least one)
121
127
  tags = if options.delete(:wild)
122
128
  ActsAsTaggableOn::Tag.named_like_any(tag_list)
@@ -134,14 +140,18 @@ module ActsAsTaggableOn::Taggable
134
140
  "#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{ActsAsTaggableOn::Utils.sha_prefix(tags.map(&:name).join('_'))}"
135
141
  )
136
142
 
137
- tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" \
138
- " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" \
139
- " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
140
- tagging_join << ' AND ' + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
143
+ tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
144
+ " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
145
+ " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
146
+
147
+ tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
148
+ tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
149
+
150
+ tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
141
151
 
142
152
  # don't need to sanitize sql, map all ids and join with OR logic
143
153
  conditions << tags.map { |t| "#{taggings_alias}.tag_id = #{quote_value(t.id, nil)}" }.join(' OR ')
144
- select_clause = " #{table_name}.*" unless context and tag_types.one?
154
+ select_clause << " #{table_name}.*" unless context and tag_types.one?
145
155
 
146
156
  if owned_by
147
157
  tagging_join << ' AND ' +
@@ -153,7 +163,10 @@ module ActsAsTaggableOn::Taggable
153
163
  end
154
164
 
155
165
  joins << tagging_join
156
- group = "#{table_name}.#{primary_key}"
166
+ unless any == 'distinct' # Fix issue #544
167
+ group = "#{table_name}.#{primary_key}"
168
+ select_clause << group
169
+ end
157
170
  else
158
171
  tags = ActsAsTaggableOn::Tag.named_any(tag_list)
159
172
 
@@ -166,7 +179,10 @@ module ActsAsTaggableOn::Taggable
166
179
  " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}" +
167
180
  " AND #{taggings_alias}.tag_id = #{quote_value(tag.id, nil)}"
168
181
 
169
- tagging_join << ' AND ' + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
182
+ tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
183
+ tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
184
+
185
+ tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
170
186
 
171
187
  if owned_by
172
188
  tagging_join << ' AND ' +
@@ -183,7 +199,7 @@ module ActsAsTaggableOn::Taggable
183
199
 
184
200
  group ||= [] # Rails interprets this as a no-op in the group() call below
185
201
  if options.delete(:order_by_matching_tag_count)
186
- select_clause = "#{table_name}.*, COUNT(#{taggings_alias}.tag_id) AS #{taggings_alias}_count"
202
+ select_clause << "#{table_name}.*, COUNT(#{taggings_alias}.tag_id) AS #{taggings_alias}_count"
187
203
  group_columns = ActsAsTaggableOn::Utils.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
188
204
  group = group_columns
189
205
  order_by << "#{taggings_alias}_count DESC"
@@ -194,7 +210,9 @@ module ActsAsTaggableOn::Taggable
194
210
  " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" \
195
211
  " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
196
212
 
197
- joins << ' AND ' + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
213
+ joins << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
214
+ joins << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
215
+ joins << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
198
216
 
199
217
  group_columns = ActsAsTaggableOn::Utils.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
200
218
  group = group_columns
@@ -203,8 +221,9 @@ module ActsAsTaggableOn::Taggable
203
221
 
204
222
  order_by << options[:order] if options[:order].present?
205
223
 
206
- select(select_clause)
207
- .joins(joins.join(' '))
224
+ query = self
225
+ query = self.select(select_clause.join(',')) unless select_clause.empty?
226
+ query.joins(joins.join(' '))
208
227
  .where(conditions.join(' AND '))
209
228
  .group(group)
210
229
  .having(having)
@@ -259,7 +278,7 @@ module ActsAsTaggableOn::Taggable
259
278
  if instance_variable_get(variable_name)
260
279
  instance_variable_get(variable_name)
261
280
  elsif cached_tag_list_on(context) && self.class.caching_tag_list_on?(context)
262
- instance_variable_set(variable_name, ActsAsTaggableOn::TagList.from(cached_tag_list_on(context)))
281
+ instance_variable_set(variable_name, ActsAsTaggableOn::TagListParser.parse(cached_tag_list_on(context)))
263
282
  else
264
283
  instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(tags_on(context).map(&:name)))
265
284
  end
@@ -309,7 +328,7 @@ module ActsAsTaggableOn::Taggable
309
328
  variable_name = "@#{context.to_s.singularize}_list"
310
329
  process_dirty_object(context, new_list) unless custom_contexts.include?(context.to_s)
311
330
 
312
- instance_variable_set(variable_name, ActsAsTaggableOn::TagList.from(new_list))
331
+ instance_variable_set(variable_name, ActsAsTaggableOn::TagListParser.parse(new_list))
313
332
  end
314
333
 
315
334
  def tagging_contexts
@@ -325,8 +344,12 @@ module ActsAsTaggableOn::Taggable
325
344
  old = changed_attributes[attrib]
326
345
  changed_attributes.delete(attrib) if old.to_s == value.to_s
327
346
  else
328
- old = tag_list_on(context).to_s
329
- changed_attributes[attrib] = old if old.to_s != value.to_s
347
+ old = tag_list_on(context)
348
+ if self.class.preserve_tag_order
349
+ changed_attributes[attrib] = old if old.to_s != value.to_s
350
+ else
351
+ changed_attributes[attrib] = old.to_s if old.sort != ActsAsTaggableOn::TagListParser.parse(value).sort
352
+ end
330
353
  end
331
354
  end
332
355
 
@@ -383,7 +406,7 @@ module ActsAsTaggableOn::Taggable
383
406
 
384
407
  # Destroy old taggings:
385
408
  if old_tags.present?
386
- self.taggings.not_owned.by_context(context).destroy_all(tag_id: old_tags)
409
+ taggings.not_owned.by_context(context).destroy_all(tag_id: old_tags)
387
410
  end
388
411
 
389
412
  # Create new taggings:
@@ -63,7 +63,7 @@ module ActsAsTaggableOn::Taggable
63
63
 
64
64
  cache = cached_owned_tag_list_on(context)
65
65
 
66
- cache[owner] = ActsAsTaggableOn::TagList.from(new_list)
66
+ cache[owner] = ActsAsTaggableOn::TagListParser.parse(new_list)
67
67
  end
68
68
 
69
69
  def reload(*args)
@@ -1,5 +1,6 @@
1
1
  module ActsAsTaggableOn
2
2
  module Taggable
3
+
3
4
  def taggable?
4
5
  false
5
6
  end
@@ -90,12 +91,12 @@ module ActsAsTaggableOn
90
91
 
91
92
  # each of these add context-specific methods and must be
92
93
  # called on each call of taggable_on
93
- include ActsAsTaggableOn::Taggable::Core
94
- include ActsAsTaggableOn::Taggable::Collection
95
- include ActsAsTaggableOn::Taggable::Cache
96
- include ActsAsTaggableOn::Taggable::Ownership
97
- include ActsAsTaggableOn::Taggable::Related
98
- include ActsAsTaggableOn::Taggable::Dirty
94
+ include Core
95
+ include Collection
96
+ include Cache
97
+ include Ownership
98
+ include Related
99
+ include Dirty
99
100
  end
100
101
  end
101
102
  end
@@ -32,7 +32,7 @@ module ActsAsTaggableOn
32
32
 
33
33
  def remove_unused_tags
34
34
  if ActsAsTaggableOn.remove_unused_tags
35
- tag.destroy if tag.taggings_count.zero?
35
+ tag.destroy if tag.reload.taggings_count.zero?
36
36
  end
37
37
  end
38
38
  end