acts-as-taggable-on 3.1.0.rc1 → 3.1.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 +8 -8
- data/.travis.yml +2 -0
- data/Appraisals +4 -0
- data/CHANGELOG.md +14 -1
- data/CONTRIBUTING.md +1 -1
- data/README.md +4 -1
- data/Rakefile +3 -3
- data/acts-as-taggable-on.gemspec +1 -0
- data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +13 -0
- data/gemfiles/rails_edge.gemfile +7 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +10 -8
- data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +33 -57
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +2 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +11 -17
- data/lib/acts_as_taggable_on/tag.rb +14 -7
- data/lib/acts_as_taggable_on/tagging.rb +16 -2
- data/lib/acts_as_taggable_on/utils.rb +17 -3
- data/lib/acts_as_taggable_on/version.rb +1 -1
- data/spec/acts_as_taggable_on/tag_spec.rb +79 -29
- data/spec/acts_as_taggable_on/taggable_spec.rb +38 -3
- data/spec/schema.rb +6 -0
- metadata +8 -6
checksums.yaml
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
!binary "U0hBMQ==":
|
|
3
3
|
metadata.gz: !binary |-
|
|
4
|
-
|
|
4
|
+
MjM0ZjVlNjhmY2MyOWIwYTBkYzUwMTFiODc0YzQxMDYyNWEzNDA1Nw==
|
|
5
5
|
data.tar.gz: !binary |-
|
|
6
|
-
|
|
6
|
+
NWYzMTZhOWNiYTEyZjcwNTBlMTIyYTcxMTVjZmRjMGEzMjgyZDljNw==
|
|
7
7
|
SHA512:
|
|
8
8
|
metadata.gz: !binary |-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
NThiZDVmYTFjZGFiMTFkNTM4Y2Y1M2Y1OGI1ZWM5N2U4YzVhZWMzMjJjMTZl
|
|
10
|
+
Y2ZlYzkwMjFlY2JlMDdkYzk3YmNmYjNhNDAzZDFjMDhkYmQxZGQ4MGJhM2Vi
|
|
11
|
+
Mzg4MWMyNDNkZmVkNWMwYmI1ZDBkZDQxMzgyNWQzMTI2NTYwNmY=
|
|
12
12
|
data.tar.gz: !binary |-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
OWZkOTMxM2ZiODMyYWQyOTBkN2MyMDQ5NmMzYTJlNjUyOWJkMzliZjFkZDg1
|
|
14
|
+
YjU5M2M4Y2NmOWZhZDcxODUzODRjNzc2YjE0NWQ2ZjVkMDhjNWY1NGE2MzQy
|
|
15
|
+
OWVmZTVkZWU2OTljNzMwZmVmZDY5YTkxMjY5YzRiOGZhZDgxOWM=
|
data/.travis.yml
CHANGED
|
@@ -2,6 +2,7 @@ rvm:
|
|
|
2
2
|
- 1.9.3
|
|
3
3
|
- 2.0.0
|
|
4
4
|
- 2.1.0
|
|
5
|
+
- 2.1.1
|
|
5
6
|
env:
|
|
6
7
|
- DB=sqlite3
|
|
7
8
|
- DB=mysql
|
|
@@ -10,6 +11,7 @@ gemfile:
|
|
|
10
11
|
- gemfiles/rails_3.2.gemfile
|
|
11
12
|
- gemfiles/rails_4.0.gemfile
|
|
12
13
|
- gemfiles/rails_4.1.gemfile
|
|
14
|
+
- gemfiles/rails_edge.gemfile
|
|
13
15
|
cache: bundler
|
|
14
16
|
script: bundle exec rake
|
|
15
17
|
before_install:
|
data/Appraisals
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -4,13 +4,25 @@ 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.0
|
|
7
|
+
### Master [changes](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.1.0...master)
|
|
8
8
|
|
|
9
9
|
* Breaking Changes
|
|
10
10
|
* Features
|
|
11
11
|
* Fixes
|
|
12
|
+
* Performance
|
|
12
13
|
* Misc
|
|
13
14
|
|
|
15
|
+
### [3.1.0 / 2014-03-31](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.0.1...v3.1.0)
|
|
16
|
+
|
|
17
|
+
* Fixes
|
|
18
|
+
* [@mikehale #487 Match_all respects context](https://github.com/mbleigh/acts-as-taggable-on/pull/487)
|
|
19
|
+
* Performance
|
|
20
|
+
* [@dgilperez #390 Add taggings counter cache](https://github.com/mbleigh/acts-as-taggable-on/pull/390)
|
|
21
|
+
* Misc
|
|
22
|
+
* [@jonseaberg Add missing indexes to schema used in specs #474](https://github.com/mbleigh/acts-as-taggable-on/pull/474)
|
|
23
|
+
* [@seuros Specify Ruby >= 1.9.3 required in gemspec](https://github.com/mbleigh/acts-as-taggable-on/pull/502)
|
|
24
|
+
* [@kiasaki Add missing quotes to code example](https://github.com/mbleigh/acts-as-taggable-on/pull/501)
|
|
25
|
+
|
|
14
26
|
### [3.1.0.rc1 / 2014-02-26](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.0.1...v3.1.0.rc1)
|
|
15
27
|
|
|
16
28
|
* Features
|
|
@@ -22,6 +34,7 @@ As such, a _Feature_ would map to either major or minor. A _bug fix_ to a patch.
|
|
|
22
34
|
* [@rgould #417 Let '.count' work when tagged_with is accompanied by a group clause](https://github.com/mbleigh/acts-as-taggable-on/pull/417)
|
|
23
35
|
* [@developer88 #461 Move 'Distinct' out of select string and use .uniq instead](https://github.com/mbleigh/acts-as-taggable-on/pull/461)
|
|
24
36
|
* [@gerard-leijdekkers #473 Fixed down migration index name](https://github.com/mbleigh/acts-as-taggable-on/pull/473)
|
|
37
|
+
* [@leo-souza #498 Use database's lower function for case-insensitive match](https://github.com/mbleigh/acts-as-taggable-on/pull/498)
|
|
25
38
|
* Misc
|
|
26
39
|
* [@billychan #463 Thread safe support](https://github.com/mbleigh/acts-as-taggable-on/pull/463)
|
|
27
40
|
* [@billychan #386 Add parse:true instructions to README](https://github.com/mbleigh/acts-as-taggable-on/pull/386)
|
data/CONTRIBUTING.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
2. Install the gem dependencies: `bundle install`
|
|
12
12
|
3. Make the changes you want and back them up with tests.
|
|
13
13
|
* [Run the tests](https://github.com/mbleigh/acts-as-taggable-on#testing) (`bundle exec rake spec`)
|
|
14
|
-
4. Update the
|
|
14
|
+
4. Update the CHANGELOG.md file with your changes and give yourself credit
|
|
15
15
|
5. Commit and create a pull request with details as to what has been changed and why
|
|
16
16
|
* Use well-described, small (atomic) commits.
|
|
17
17
|
* Include links to any relevant github issues.
|
data/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# ActsAsTaggableOn
|
|
2
2
|
[](http://travis-ci.org/mbleigh/acts-as-taggable-on)
|
|
3
3
|
[](https://codeclimate.com/github/mbleigh/acts-as-taggable-on)
|
|
4
|
+
[](http://inch-pages.github.io/github/mbleigh/acts-as-taggable-on)
|
|
4
5
|
|
|
5
6
|
This plugin was originally based on Acts as Taggable on Steroids by Jonathan Viney.
|
|
6
7
|
It has evolved substantially since that point, but all credit goes to him for the
|
|
@@ -175,7 +176,7 @@ User.tagged_with(["awesome", "cool"], :any => true)
|
|
|
175
176
|
User.tagged_with(["awesome", "cool"], :exclude => true)
|
|
176
177
|
|
|
177
178
|
# Find a user with any of tags based on context:
|
|
178
|
-
User.tagged_with(['awesome, cool'], :on => :tags, :any => true).tagged_with(['smart', 'shy'], :on => :skills, :any => true)
|
|
179
|
+
User.tagged_with(['awesome', 'cool'], :on => :tags, :any => true).tagged_with(['smart', 'shy'], :on => :skills, :any => true)
|
|
179
180
|
```
|
|
180
181
|
|
|
181
182
|
You can also use `:wild => true` option along with `:any` or `:exclude` option. It will looking for `%awesome%` and `%cool%` in sql.
|
|
@@ -339,6 +340,8 @@ If you want to change the default delimiter (it defaults to ','). You can also p
|
|
|
339
340
|
ActsAsTaggableOn.delimiter = ','
|
|
340
341
|
```
|
|
341
342
|
|
|
343
|
+
*NOTE: SQLite by default can't upcase or downcase multibyte characters, resulting in unwanted behavior. Load the SQLite ICU extension for proper handle of such characters. [See docs](http://www.sqlite.org/src/artifact?ci=trunk&filename=ext/icu/README.txt)*
|
|
344
|
+
|
|
342
345
|
## Contributors
|
|
343
346
|
|
|
344
347
|
We have a long list of valued contributors. [Check them all](https://github.com/mbleigh/acts-as-taggable-on/contributors)
|
data/Rakefile
CHANGED
|
@@ -5,6 +5,9 @@ rescue LoadError
|
|
|
5
5
|
STDERR.puts "Bundler not loaded"
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
+
desc 'Default: run specs'
|
|
9
|
+
task :default => :spec
|
|
10
|
+
|
|
8
11
|
desc 'Copy sample spec database.yml over if not exists'
|
|
9
12
|
task :copy_db_config do
|
|
10
13
|
cp 'spec/database.yml.sample', 'spec/database.yml'
|
|
@@ -12,9 +15,6 @@ end
|
|
|
12
15
|
|
|
13
16
|
task :spec => [:copy_db_config]
|
|
14
17
|
|
|
15
|
-
desc 'Default: run specs'
|
|
16
|
-
task :default => :spec
|
|
17
|
-
|
|
18
18
|
begin
|
|
19
19
|
require 'appraisal'
|
|
20
20
|
desc 'Run tests across gemfiles specified in Appraisals'
|
data/acts-as-taggable-on.gemspec
CHANGED
|
@@ -17,6 +17,7 @@ Gem::Specification.new do |gem|
|
|
|
17
17
|
gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
19
19
|
gem.require_paths = ["lib"]
|
|
20
|
+
gem.required_ruby_version = '>= 1.9.3'
|
|
20
21
|
|
|
21
22
|
if File.exists?('UPGRADING.md')
|
|
22
23
|
gem.post_install_message = File.read('UPGRADING.md')
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class AddTaggingsCounterCacheToTags < ActiveRecord::Migration
|
|
2
|
+
def self.up
|
|
3
|
+
add_column :tags, :taggings_count, :integer, :default => 0
|
|
4
|
+
|
|
5
|
+
ActsAsTaggableOn::Tag.find_each do |tag|
|
|
6
|
+
ActsAsTaggableOn::Tag.reset_counters(tag.id, :taggings)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.down
|
|
11
|
+
remove_column :tags, :taggings_count
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -22,15 +22,17 @@ module ActsAsTaggableOn::Taggable
|
|
|
22
22
|
initialize_tags_cache
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
# ActiveRecord::Base.columns makes a database connection and caches the
|
|
26
|
-
# columns hash for the record as @columns. Since we don't
|
|
27
|
-
# methods until we confirm the presence of a
|
|
28
|
-
# want to force opening a database
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
25
|
+
# ActiveRecord::Base.columns makes a database connection and caches the
|
|
26
|
+
# calculated columns hash for the record as @columns. Since we don't
|
|
27
|
+
# want to add caching methods until we confirm the presence of a
|
|
28
|
+
# caching column, and we don't want to force opening a database
|
|
29
|
+
# connection when the class is loaded, here we intercept and cache
|
|
30
|
+
# the call to :columns as @acts_as_taggable_on_cache_columns
|
|
31
|
+
# to mimic the underlying behavior. While processing this first
|
|
32
|
+
# call to columns, we do the caching column check and dynamically add
|
|
33
|
+
# the class and instance methods
|
|
32
34
|
def columns
|
|
33
|
-
@
|
|
35
|
+
@acts_as_taggable_on_cache_columns ||= begin
|
|
34
36
|
db_columns = super
|
|
35
37
|
if _has_tags_cache_columns?(db_columns)
|
|
36
38
|
_add_tags_caching_methods
|
|
@@ -58,29 +58,13 @@ module ActsAsTaggableOn::Taggable
|
|
|
58
58
|
## Generate conditions:
|
|
59
59
|
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
|
60
60
|
|
|
61
|
-
start_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
|
62
|
-
end_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
|
63
|
-
|
|
64
|
-
taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?", base_class.name])
|
|
65
|
-
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on]
|
|
66
|
-
|
|
67
|
-
tagging_conditions = [
|
|
68
|
-
taggable_conditions,
|
|
69
|
-
start_at_conditions,
|
|
70
|
-
end_at_conditions
|
|
71
|
-
].compact.reverse
|
|
72
|
-
|
|
73
|
-
tag_conditions = [
|
|
74
|
-
options[:conditions]
|
|
75
|
-
].compact.reverse
|
|
76
|
-
|
|
77
61
|
## Generate scope:
|
|
78
62
|
tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id")
|
|
79
63
|
tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*").order(options[:order]).limit(options[:limit])
|
|
80
64
|
|
|
81
65
|
# Joins and conditions
|
|
82
|
-
tagging_conditions.each { |condition| tagging_scope = tagging_scope.where(condition) }
|
|
83
|
-
|
|
66
|
+
tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) }
|
|
67
|
+
tag_scope = tag_scope.where(options[:conditions])
|
|
84
68
|
|
|
85
69
|
group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
|
|
86
70
|
|
|
@@ -88,10 +72,10 @@ module ActsAsTaggableOn::Taggable
|
|
|
88
72
|
scoped_select = "#{table_name}.#{primary_key}"
|
|
89
73
|
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(select(scoped_select))})").group(group_columns)
|
|
90
74
|
|
|
91
|
-
tag_scope
|
|
92
|
-
tag_scope.extending(CalculationMethods)
|
|
75
|
+
tag_scope_joins(tag_scope, tagging_scope)
|
|
93
76
|
end
|
|
94
77
|
|
|
78
|
+
|
|
95
79
|
##
|
|
96
80
|
# Calculate the tag counts for all tags.
|
|
97
81
|
#
|
|
@@ -107,56 +91,28 @@ module ActsAsTaggableOn::Taggable
|
|
|
107
91
|
def all_tag_counts(options = {})
|
|
108
92
|
options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id
|
|
109
93
|
|
|
110
|
-
scope = {}
|
|
111
|
-
|
|
112
94
|
## Generate conditions:
|
|
113
95
|
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
|
114
96
|
|
|
115
|
-
start_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
|
116
|
-
end_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
|
117
|
-
|
|
118
|
-
taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?", base_class.name])
|
|
119
|
-
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options[:id]]) if options[:id]
|
|
120
|
-
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on]
|
|
121
|
-
|
|
122
|
-
tagging_conditions = [
|
|
123
|
-
taggable_conditions,
|
|
124
|
-
scope[:conditions],
|
|
125
|
-
start_at_conditions,
|
|
126
|
-
end_at_conditions
|
|
127
|
-
].compact.reverse
|
|
128
|
-
|
|
129
|
-
tag_conditions = [
|
|
130
|
-
options[:conditions]
|
|
131
|
-
].compact.reverse
|
|
132
|
-
|
|
133
97
|
## Generate joins:
|
|
134
98
|
taggable_join = "INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id"
|
|
135
99
|
taggable_join << " AND #{table_name}.#{inheritance_column} = '#{name}'" unless descends_from_active_record? # Current model is STI descendant, so add type checking to the join condition
|
|
136
100
|
|
|
137
|
-
tagging_joins = [
|
|
138
|
-
taggable_join,
|
|
139
|
-
scope[:joins]
|
|
140
|
-
].compact
|
|
141
|
-
|
|
142
|
-
tag_joins = [
|
|
143
|
-
].compact
|
|
144
101
|
|
|
145
102
|
## Generate scope:
|
|
146
103
|
tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id, COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) AS tags_count")
|
|
147
104
|
tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*, #{ActsAsTaggableOn::Tagging.table_name}.tags_count AS count").order(options[:order]).limit(options[:limit])
|
|
148
105
|
|
|
149
106
|
# Joins and conditions
|
|
150
|
-
|
|
151
|
-
tagging_conditions.each { |condition| tagging_scope = tagging_scope.where(condition) }
|
|
152
|
-
|
|
153
|
-
tag_joins.each { |join| tag_scope = tag_scope.joins(join) }
|
|
154
|
-
tag_conditions.each { |condition| tag_scope = tag_scope.where(condition) }
|
|
107
|
+
tagging_scope = tagging_scope.joins(taggable_join)
|
|
108
|
+
tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) }
|
|
109
|
+
tag_scope = tag_scope.where(options[:conditions])
|
|
155
110
|
|
|
156
111
|
# GROUP BY and HAVING clauses:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
having
|
|
112
|
+
having = ["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) > 0"]
|
|
113
|
+
having.push sanitize_sql(["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) >= ?", options.delete(:at_least)]) if options[:at_least]
|
|
114
|
+
having.push sanitize_sql(["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) <= ?", options.delete(:at_most)]) if options[:at_most]
|
|
115
|
+
having = having.compact.join(' AND ')
|
|
160
116
|
|
|
161
117
|
group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
|
|
162
118
|
|
|
@@ -168,13 +124,33 @@ module ActsAsTaggableOn::Taggable
|
|
|
168
124
|
|
|
169
125
|
tagging_scope = tagging_scope.group(group_columns).having(having)
|
|
170
126
|
|
|
171
|
-
tag_scope
|
|
172
|
-
tag_scope.extending(CalculationMethods)
|
|
127
|
+
tag_scope_joins(tag_scope, tagging_scope)
|
|
173
128
|
end
|
|
174
129
|
|
|
175
130
|
def safe_to_sql(relation)
|
|
176
131
|
connection.respond_to?(:unprepared_statement) ? connection.unprepared_statement{relation.to_sql} : relation.to_sql
|
|
177
132
|
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def tagging_conditions(options)
|
|
137
|
+
tagging_conditions = []
|
|
138
|
+
tagging_conditions.push sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
|
139
|
+
tagging_conditions.push sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
|
140
|
+
|
|
141
|
+
taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?", base_class.name])
|
|
142
|
+
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on]
|
|
143
|
+
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options[:id]]) if options[:id]
|
|
144
|
+
|
|
145
|
+
tagging_conditions.push taggable_conditions
|
|
146
|
+
|
|
147
|
+
tagging_conditions
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def tag_scope_joins(tag_scope, tagging_scope)
|
|
151
|
+
tag_scope = tag_scope.joins("JOIN (#{safe_to_sql(tagging_scope)}) AS #{ActsAsTaggableOn::Tagging.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id")
|
|
152
|
+
tag_scope.extending(CalculationMethods)
|
|
153
|
+
end
|
|
178
154
|
end
|
|
179
155
|
|
|
180
156
|
def tag_counts_on(context, options={})
|
|
@@ -193,6 +193,8 @@ module ActsAsTaggableOn::Taggable
|
|
|
193
193
|
" ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
|
|
194
194
|
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
|
|
195
195
|
|
|
196
|
+
joins << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
|
|
197
|
+
|
|
196
198
|
group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
|
|
197
199
|
group = group_columns
|
|
198
200
|
having = "COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
|
|
@@ -37,33 +37,19 @@ module ActsAsTaggableOn::Taggable
|
|
|
37
37
|
|
|
38
38
|
def matching_contexts_for(search_context, result_context, klass, options = {})
|
|
39
39
|
tags_to_find = tags_on(search_context).collect { |t| t.name }
|
|
40
|
-
|
|
41
|
-
klass.select("#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count").
|
|
42
|
-
from("#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}").
|
|
43
|
-
where(["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context]).
|
|
44
|
-
group(group_columns(klass)).
|
|
45
|
-
order("count DESC")
|
|
40
|
+
related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context])
|
|
46
41
|
end
|
|
47
42
|
|
|
48
43
|
def related_tags_for(context, klass, options = {})
|
|
49
44
|
tags_to_ignore = Array.wrap(options.delete(:ignore)).map(&:to_s) || []
|
|
50
45
|
tags_to_find = tags_on(context).collect { |t| t.name }.reject { |t| tags_to_ignore.include? t }
|
|
51
|
-
|
|
52
|
-
klass.select("#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count").
|
|
53
|
-
from("#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}").
|
|
54
|
-
where(["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find]).
|
|
55
|
-
group(group_columns(klass)).
|
|
56
|
-
order("count DESC")
|
|
46
|
+
related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find])
|
|
57
47
|
end
|
|
58
48
|
|
|
59
49
|
private
|
|
60
50
|
|
|
61
51
|
def exclude_self(klass, id)
|
|
62
|
-
|
|
63
|
-
"#{klass.table_name}.#{klass.primary_key} != #{id} AND"
|
|
64
|
-
else
|
|
65
|
-
nil
|
|
66
|
-
end
|
|
52
|
+
"#{klass.table_name}.#{klass.primary_key} != #{id} AND" if [self.class.base_class, self.class].include? klass
|
|
67
53
|
end
|
|
68
54
|
|
|
69
55
|
def group_columns(klass)
|
|
@@ -73,5 +59,13 @@ module ActsAsTaggableOn::Taggable
|
|
|
73
59
|
"#{klass.table_name}.#{klass.primary_key}"
|
|
74
60
|
end
|
|
75
61
|
end
|
|
62
|
+
|
|
63
|
+
def related_where(klass, conditions)
|
|
64
|
+
klass.select("#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count").
|
|
65
|
+
from("#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}").
|
|
66
|
+
group(group_columns(klass)).
|
|
67
|
+
order("count DESC").
|
|
68
|
+
where(conditions)
|
|
69
|
+
end
|
|
76
70
|
end
|
|
77
71
|
end
|
|
@@ -24,9 +24,9 @@ module ActsAsTaggableOn
|
|
|
24
24
|
|
|
25
25
|
def self.named(name)
|
|
26
26
|
if ActsAsTaggableOn.strict_case_match
|
|
27
|
-
where(["name = #{binary}?", name])
|
|
27
|
+
where(["name = #{binary}?", as_8bit_ascii(name)])
|
|
28
28
|
else
|
|
29
|
-
where(["
|
|
29
|
+
where(["LOWER(name) = LOWER(?)", as_8bit_ascii(unicode_downcase(name))])
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -38,8 +38,7 @@ module ActsAsTaggableOn
|
|
|
38
38
|
where(clause)
|
|
39
39
|
else
|
|
40
40
|
clause = list.map { |tag|
|
|
41
|
-
|
|
42
|
-
sanitize_sql(["lower(name) = ?", lowercase_ascii_tag])
|
|
41
|
+
sanitize_sql(["LOWER(name) = LOWER(?)", as_8bit_ascii(unicode_downcase(tag))])
|
|
43
42
|
}.join(" OR ")
|
|
44
43
|
where(clause)
|
|
45
44
|
end
|
|
@@ -101,14 +100,22 @@ module ActsAsTaggableOn
|
|
|
101
100
|
|
|
102
101
|
def comparable_name(str)
|
|
103
102
|
if ActsAsTaggableOn.strict_case_match
|
|
104
|
-
|
|
103
|
+
str
|
|
105
104
|
else
|
|
106
|
-
|
|
105
|
+
unicode_downcase(str.to_s)
|
|
107
106
|
end
|
|
108
107
|
end
|
|
109
108
|
|
|
110
109
|
def binary
|
|
111
|
-
|
|
110
|
+
using_mysql? ? "BINARY " : nil
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def unicode_downcase(string)
|
|
114
|
+
if ActiveSupport::Multibyte::Unicode.respond_to?(:downcase)
|
|
115
|
+
ActiveSupport::Multibyte::Unicode.downcase(string)
|
|
116
|
+
else
|
|
117
|
+
ActiveSupport::Multibyte::Chars.new(string).downcase.to_s
|
|
118
|
+
end
|
|
112
119
|
end
|
|
113
120
|
|
|
114
121
|
def as_8bit_ascii(string)
|
|
@@ -9,7 +9,6 @@ module ActsAsTaggableOn
|
|
|
9
9
|
:tagger,
|
|
10
10
|
:tagger_type,
|
|
11
11
|
:tagger_id if defined?(ActiveModel::MassAssignmentSecurity)
|
|
12
|
-
|
|
13
12
|
belongs_to :tag, :class_name => 'ActsAsTaggableOn::Tag'
|
|
14
13
|
belongs_to :taggable, :polymorphic => true
|
|
15
14
|
belongs_to :tagger, :polymorphic => true
|
|
@@ -17,14 +16,29 @@ module ActsAsTaggableOn
|
|
|
17
16
|
validates_presence_of :context
|
|
18
17
|
validates_presence_of :tag_id
|
|
19
18
|
|
|
20
|
-
validates_uniqueness_of :tag_id, :scope => [
|
|
19
|
+
validates_uniqueness_of :tag_id, :scope => [:taggable_type, :taggable_id, :context, :tagger_id, :tagger_type]
|
|
21
20
|
|
|
22
21
|
after_destroy :remove_unused_tags
|
|
23
22
|
|
|
23
|
+
# Conditionally adds a counter cache when cache column is present.
|
|
24
|
+
# We just regenerate the association. It's the easiest way.
|
|
25
|
+
# TODO: require the counter cache in release 4.0.0 and remove these methods
|
|
26
|
+
# @see :columns in ActsAsTaggableOn::Taggable::Cache
|
|
27
|
+
def self.columns
|
|
28
|
+
@acts_as_taggable_on_counter_columns ||= begin
|
|
29
|
+
db_columns = super
|
|
30
|
+
belongs_to :tag, :class_name => 'ActsAsTaggableOn::Tag', :counter_cache => ActsAsTaggableOn::Tag.column_names.include?('taggings_count')
|
|
31
|
+
db_columns
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
24
37
|
private
|
|
25
38
|
|
|
26
39
|
def remove_unused_tags
|
|
27
40
|
if ActsAsTaggableOn.remove_unused_tags
|
|
41
|
+
# TODO: use taggings_count in release 4.0.0
|
|
28
42
|
if tag.taggings.count.zero?
|
|
29
43
|
tag.destroy
|
|
30
44
|
end
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
module ActsAsTaggableOn
|
|
2
2
|
module Utils
|
|
3
|
+
|
|
4
|
+
def connection
|
|
5
|
+
::ActiveRecord::Base.connection
|
|
6
|
+
end
|
|
7
|
+
|
|
3
8
|
def using_postgresql?
|
|
4
|
-
|
|
9
|
+
connection && connection.adapter_name == 'PostgreSQL'
|
|
5
10
|
end
|
|
6
11
|
|
|
7
12
|
def using_sqlite?
|
|
8
|
-
|
|
13
|
+
connection && connection.adapter_name == 'SQLite'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def using_mysql?
|
|
17
|
+
#We should probably use regex for mysql to support prehistoric adapters
|
|
18
|
+
connection && connection.adapter_name == 'Mysql2'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def using_case_insensitive_collation?
|
|
22
|
+
using_mysql? && ::ActiveRecord::Base.connection.collation =~ /_ci\Z/
|
|
9
23
|
end
|
|
10
24
|
|
|
11
25
|
def sha_prefix(string)
|
|
@@ -20,7 +34,7 @@ module ActsAsTaggableOn
|
|
|
20
34
|
|
|
21
35
|
# escape _ and % characters in strings, since these are wildcards in SQL.
|
|
22
36
|
def escape_like(str)
|
|
23
|
-
str.gsub(/[!%_]/){ |x| '!' + x }
|
|
37
|
+
str.gsub(/[!%_]/) { |x| '!' + x }
|
|
24
38
|
end
|
|
25
39
|
end
|
|
26
40
|
end
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# encoding: utf-8
|
|
2
2
|
require 'spec_helper'
|
|
3
|
+
require 'db/migrate/2_add_missing_unique_indices.rb'
|
|
4
|
+
|
|
5
|
+
shared_examples_for 'without unique index' do
|
|
6
|
+
before { AddMissingUniqueIndices.down }
|
|
7
|
+
after { ActsAsTaggableOn::Tag.delete_all; AddMissingUniqueIndices.up }
|
|
8
|
+
end
|
|
3
9
|
|
|
4
10
|
describe ActsAsTaggableOn::Tag do
|
|
5
11
|
before(:each) do
|
|
@@ -9,14 +15,33 @@ describe ActsAsTaggableOn::Tag do
|
|
|
9
15
|
end
|
|
10
16
|
|
|
11
17
|
describe "named like any" do
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
context "case insensitive collation and unique index on tag name" do
|
|
19
|
+
if described_class.using_case_insensitive_collation?
|
|
20
|
+
before(:each) do
|
|
21
|
+
ActsAsTaggableOn::Tag.create(:name => "Awesome")
|
|
22
|
+
ActsAsTaggableOn::Tag.create(:name => "epic")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "should find both tags" do
|
|
26
|
+
ActsAsTaggableOn::Tag.named_like_any(["awesome", "epic"]).should have(2).items
|
|
27
|
+
end
|
|
28
|
+
end
|
|
16
29
|
end
|
|
17
30
|
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
context "case insensitive collation without indexes or case sensitive collation with indexes" do
|
|
32
|
+
if described_class.using_case_insensitive_collation?
|
|
33
|
+
include_context 'without unique index'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
before(:each) do
|
|
37
|
+
ActsAsTaggableOn::Tag.create(:name => "Awesome")
|
|
38
|
+
ActsAsTaggableOn::Tag.create(:name => "awesome")
|
|
39
|
+
ActsAsTaggableOn::Tag.create(:name => "epic")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "should find both tags" do
|
|
43
|
+
ActsAsTaggableOn::Tag.named_like_any(["awesome", "epic"]).should have(3).items
|
|
44
|
+
end
|
|
20
45
|
end
|
|
21
46
|
end
|
|
22
47
|
|
|
@@ -72,11 +97,17 @@ describe ActsAsTaggableOn::Tag do
|
|
|
72
97
|
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name("AWESOME").should == [@tag]
|
|
73
98
|
end
|
|
74
99
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
100
|
+
context "case sensitive" do
|
|
101
|
+
if described_class.using_case_insensitive_collation?
|
|
102
|
+
include_context 'without unique index'
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "should find by name case sensitive" do
|
|
106
|
+
ActsAsTaggableOn.strict_case_match = true
|
|
107
|
+
expect {
|
|
108
|
+
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name("AWESOME")
|
|
109
|
+
}.to change(ActsAsTaggableOn::Tag, :count).by(1)
|
|
110
|
+
end
|
|
80
111
|
end
|
|
81
112
|
|
|
82
113
|
it "should create by name" do
|
|
@@ -85,11 +116,17 @@ describe ActsAsTaggableOn::Tag do
|
|
|
85
116
|
}.to change(ActsAsTaggableOn::Tag, :count).by(1)
|
|
86
117
|
end
|
|
87
118
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
119
|
+
context "case sensitive" do
|
|
120
|
+
if described_class.using_case_insensitive_collation?
|
|
121
|
+
include_context 'without unique index'
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "should find or create by name case sensitive" do
|
|
125
|
+
ActsAsTaggableOn.strict_case_match = true
|
|
126
|
+
expect {
|
|
127
|
+
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name("AWESOME", 'awesome').map(&:name).should == ["AWESOME", "awesome"]
|
|
128
|
+
}.to change(ActsAsTaggableOn::Tag, :count).by(1)
|
|
129
|
+
end
|
|
93
130
|
end
|
|
94
131
|
|
|
95
132
|
it "should find or create by name" do
|
|
@@ -178,21 +215,33 @@ describe ActsAsTaggableOn::Tag do
|
|
|
178
215
|
ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("awesome").should == @tag
|
|
179
216
|
end
|
|
180
217
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
218
|
+
context "case sensitive" do
|
|
219
|
+
if described_class.using_case_insensitive_collation?
|
|
220
|
+
include_context 'without unique index'
|
|
221
|
+
end
|
|
185
222
|
|
|
186
|
-
|
|
223
|
+
it "should find by name case sensitively" do
|
|
224
|
+
expect {
|
|
225
|
+
ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("AWESOME")
|
|
226
|
+
}.to change(ActsAsTaggableOn::Tag, :count)
|
|
227
|
+
|
|
228
|
+
ActsAsTaggableOn::Tag.last.name.should == "AWESOME"
|
|
229
|
+
end
|
|
187
230
|
end
|
|
188
231
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
232
|
+
context "case sensitive" do
|
|
233
|
+
if described_class.using_case_insensitive_collation?
|
|
234
|
+
include_context 'without unique index'
|
|
235
|
+
end
|
|
193
236
|
|
|
194
|
-
|
|
195
|
-
|
|
237
|
+
it "should have a named_scope named(something) that matches exactly" do
|
|
238
|
+
uppercase_tag = ActsAsTaggableOn::Tag.create(:name => "Cool")
|
|
239
|
+
@tag.name = "cool"
|
|
240
|
+
@tag.save!
|
|
241
|
+
|
|
242
|
+
ActsAsTaggableOn::Tag.named('cool').should include(@tag)
|
|
243
|
+
ActsAsTaggableOn::Tag.named('cool').should_not include(uppercase_tag)
|
|
244
|
+
end
|
|
196
245
|
end
|
|
197
246
|
|
|
198
247
|
it "should not change enconding" do
|
|
@@ -209,16 +258,17 @@ describe ActsAsTaggableOn::Tag do
|
|
|
209
258
|
before { ActsAsTaggableOn::Tag.create(:name => 'ror') }
|
|
210
259
|
|
|
211
260
|
context "when don't need unique names" do
|
|
261
|
+
include_context 'without unique index'
|
|
212
262
|
it "should not run uniqueness validation" do
|
|
213
263
|
duplicate_tag.stub(:validates_name_uniqueness?).and_return(false)
|
|
214
264
|
duplicate_tag.save
|
|
215
265
|
duplicate_tag.should be_persisted
|
|
216
|
-
end
|
|
266
|
+
end
|
|
217
267
|
end
|
|
218
268
|
|
|
219
269
|
context "when do need unique names" do
|
|
220
270
|
it "should run uniqueness validation" do
|
|
221
|
-
duplicate_tag.should_not be_valid
|
|
271
|
+
duplicate_tag.should_not be_valid
|
|
222
272
|
end
|
|
223
273
|
|
|
224
274
|
it "add error to name" do
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
1
2
|
require 'spec_helper'
|
|
2
3
|
|
|
3
4
|
describe "Taggable To Preserve Order" do
|
|
@@ -242,6 +243,34 @@ describe "Taggable" do
|
|
|
242
243
|
TaggableModel.tagged_with("ruby").to_a.should == TaggableModel.tagged_with("Ruby").to_a
|
|
243
244
|
end
|
|
244
245
|
|
|
246
|
+
unless ActsAsTaggableOn::Tag.using_sqlite?
|
|
247
|
+
it "should not care about case for unicode names" do
|
|
248
|
+
ActsAsTaggableOn.strict_case_match = false
|
|
249
|
+
|
|
250
|
+
anya = TaggableModel.create(:name => "Anya", :tag_list => "ПРИВЕТ")
|
|
251
|
+
igor = TaggableModel.create(:name => "Igor", :tag_list => "привет")
|
|
252
|
+
katia = TaggableModel.create(:name => "Katia", :tag_list => "ПРИВЕТ")
|
|
253
|
+
|
|
254
|
+
ActsAsTaggableOn::Tag.all.size.should == 1
|
|
255
|
+
TaggableModel.tagged_with("привет").to_a.should == TaggableModel.tagged_with("ПРИВЕТ").to_a
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
it "should be able to create and find tags in languages without capitalization" do
|
|
260
|
+
ActsAsTaggableOn.strict_case_match = false
|
|
261
|
+
chihiro = TaggableModel.create(:name => "Chihiro", :tag_list => "日本の")
|
|
262
|
+
salim = TaggableModel.create(:name => "Salim", :tag_list => "עברית")
|
|
263
|
+
ieie = TaggableModel.create(:name => "Ieie", :tag_list => "中国的")
|
|
264
|
+
yasser = TaggableModel.create(:name => "Yasser", :tag_list => "العربية")
|
|
265
|
+
emo = TaggableModel.create(:name => "Emo", :tag_list => "✏")
|
|
266
|
+
|
|
267
|
+
TaggableModel.tagged_with("日本の").to_a.size.should == 1
|
|
268
|
+
TaggableModel.tagged_with("עברית").to_a.size.should == 1
|
|
269
|
+
TaggableModel.tagged_with("中国的").to_a.size.should == 1
|
|
270
|
+
TaggableModel.tagged_with("العربية").to_a.size.should == 1
|
|
271
|
+
TaggableModel.tagged_with("✏").to_a.size.should == 1
|
|
272
|
+
end
|
|
273
|
+
|
|
245
274
|
it "should be able to get tag counts on model as a whole" do
|
|
246
275
|
bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
|
|
247
276
|
frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
|
|
@@ -430,6 +459,14 @@ describe "Taggable" do
|
|
|
430
459
|
TaggableModel.tagged_with("fitter, happier", :match_all => true).to_a.should == [steve]
|
|
431
460
|
end
|
|
432
461
|
|
|
462
|
+
it "should be able to find tagged with only the matching tags for a context" do
|
|
463
|
+
bob = TaggableModel.create(:name => "Bob", :tag_list => "lazy, happier", :skill_list => "ruby, rails, css")
|
|
464
|
+
frank = TaggableModel.create(:name => "Frank", :tag_list => "fitter, happier, inefficient", :skill_list => "css")
|
|
465
|
+
steve = TaggableModel.create(:name => 'Steve', :tag_list => "fitter, happier", :skill_list => "ruby, rails, css")
|
|
466
|
+
|
|
467
|
+
TaggableModel.tagged_with("css", :on => :skills, :match_all => true).to_a.should == [frank]
|
|
468
|
+
end
|
|
469
|
+
|
|
433
470
|
it "should be able to find tagged with some excluded tags" do
|
|
434
471
|
bob = TaggableModel.create(:name => "Bob", :tag_list => "happier, lazy")
|
|
435
472
|
frank = TaggableModel.create(:name => "Frank", :tag_list => "happier")
|
|
@@ -468,7 +505,7 @@ describe "Taggable" do
|
|
|
468
505
|
|
|
469
506
|
describe "grouped_column_names_for method" do
|
|
470
507
|
it "should return all column names joined for Tag GROUP clause" do
|
|
471
|
-
@taggable.grouped_column_names_for(ActsAsTaggableOn::Tag).should == "tags.id, tags.name"
|
|
508
|
+
@taggable.grouped_column_names_for(ActsAsTaggableOn::Tag).should == "tags.id, tags.name, tags.taggings_count"
|
|
472
509
|
end
|
|
473
510
|
|
|
474
511
|
it "should return all column names joined for TaggableModel GROUP clause" do
|
|
@@ -654,5 +691,3 @@ describe "Taggable" do
|
|
|
654
691
|
end
|
|
655
692
|
end
|
|
656
693
|
end
|
|
657
|
-
|
|
658
|
-
|
data/spec/schema.rb
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
ActiveRecord::Schema.define :version => 0 do
|
|
2
2
|
create_table :tags, :force => true do |t|
|
|
3
3
|
t.string :name
|
|
4
|
+
t.integer :taggings_count, :default => 0
|
|
4
5
|
end
|
|
6
|
+
add_index "tags", ["name"], name: "index_tags_on_name", unique: true
|
|
5
7
|
|
|
6
8
|
create_table :taggings, :force => true do |t|
|
|
7
9
|
t.references :tag
|
|
@@ -17,6 +19,10 @@ ActiveRecord::Schema.define :version => 0 do
|
|
|
17
19
|
|
|
18
20
|
t.datetime :created_at
|
|
19
21
|
end
|
|
22
|
+
add_index "taggings",
|
|
23
|
+
["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"],
|
|
24
|
+
unique: true, name: "taggings_idx"
|
|
25
|
+
|
|
20
26
|
# above copied from
|
|
21
27
|
# generators/acts_as_taggable_on/migration/migration_generator
|
|
22
28
|
|
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.1.0
|
|
4
|
+
version: 3.1.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-04-01 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: activerecord
|
|
@@ -179,9 +179,11 @@ files:
|
|
|
179
179
|
- acts-as-taggable-on.gemspec
|
|
180
180
|
- db/migrate/1_acts_as_taggable_on_migration.rb
|
|
181
181
|
- db/migrate/2_add_missing_unique_indices.rb
|
|
182
|
+
- db/migrate/3_add_taggings_counter_cache_to_tags.rb
|
|
182
183
|
- gemfiles/rails_3.2.gemfile
|
|
183
184
|
- gemfiles/rails_4.0.gemfile
|
|
184
185
|
- gemfiles/rails_4.1.gemfile
|
|
186
|
+
- gemfiles/rails_edge.gemfile
|
|
185
187
|
- lib/acts-as-taggable-on.rb
|
|
186
188
|
- lib/acts_as_taggable_on.rb
|
|
187
189
|
- lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb
|
|
@@ -231,15 +233,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
231
233
|
requirements:
|
|
232
234
|
- - ! '>='
|
|
233
235
|
- !ruby/object:Gem::Version
|
|
234
|
-
version:
|
|
236
|
+
version: 1.9.3
|
|
235
237
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
236
238
|
requirements:
|
|
237
|
-
- - ! '
|
|
239
|
+
- - ! '>='
|
|
238
240
|
- !ruby/object:Gem::Version
|
|
239
|
-
version:
|
|
241
|
+
version: '0'
|
|
240
242
|
requirements: []
|
|
241
243
|
rubyforge_project:
|
|
242
|
-
rubygems_version: 2.2.
|
|
244
|
+
rubygems_version: 2.2.2
|
|
243
245
|
signing_key:
|
|
244
246
|
specification_version: 4
|
|
245
247
|
summary: Advanced tagging for Rails.
|