acts-as-taggable-on 2.0.0 → 2.0.6
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.
- data/Gemfile +7 -3
- data/README.rdoc +14 -14
- data/VERSION +1 -1
- data/lib/acts-as-taggable-on.rb +1 -1
- data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +3 -3
- data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +56 -22
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +26 -22
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +12 -12
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +7 -6
- data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +2 -2
- data/lib/acts_as_taggable_on/acts_as_tagger.rb +2 -2
- data/lib/acts_as_taggable_on/compatibility/Gemfile +3 -1
- data/lib/acts_as_taggable_on/compatibility/postgresql.rb +44 -0
- data/lib/acts_as_taggable_on/tag.rb +53 -44
- data/lib/acts_as_taggable_on/tag_list.rb +79 -78
- data/lib/acts_as_taggable_on/tagging.rb +19 -18
- data/lib/acts_as_taggable_on/tags_helper.rb +12 -12
- data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +2 -1
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +4 -2
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +3 -3
- data/spec/acts_as_taggable_on/tag_list_spec.rb +3 -3
- data/spec/acts_as_taggable_on/tag_spec.rb +21 -21
- data/spec/acts_as_taggable_on/taggable_spec.rb +37 -12
- data/spec/acts_as_taggable_on/tagger_spec.rb +5 -5
- data/spec/acts_as_taggable_on/tagging_spec.rb +7 -7
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +3 -3
- data/spec/database.yml +17 -0
- data/spec/database.yml.sample +17 -0
- data/spec/models.rb +1 -0
- data/spec/spec_helper.rb +40 -33
- metadata +6 -4
- data/spec/spec.opts +0 -2
data/Gemfile
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
source :gemcutter
|
|
2
2
|
|
|
3
3
|
# Rails 3.0
|
|
4
|
-
gem 'rails', '3.0.0.
|
|
5
|
-
gem 'rspec', '2.0.0.beta.
|
|
6
|
-
gem 'sqlite3-ruby',
|
|
4
|
+
gem 'rails', '3.0.0.beta3'
|
|
5
|
+
gem 'rspec', '2.0.0.beta.8'
|
|
6
|
+
gem 'sqlite3-ruby', :require => 'sqlite3'
|
|
7
|
+
gem 'mysql'
|
|
8
|
+
gem 'pg'
|
|
9
|
+
gem 'jeweler'
|
|
10
|
+
gem 'rcov'
|
data/README.rdoc
CHANGED
|
@@ -38,21 +38,21 @@ After that, you can run "rake gems:install" to install the gem if you don't alre
|
|
|
38
38
|
1. script/generate acts_as_taggable_on_migration
|
|
39
39
|
2. rake db:migrate
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
=== Rails 3.0
|
|
42
42
|
|
|
43
43
|
Acts As Taggable On is now useable in Rails 3.0, thanks to the excellent work of Szymon Nowak
|
|
44
44
|
and Jelle Vandebeeck.
|
|
45
45
|
|
|
46
46
|
To use it, add it to your Gemfile:
|
|
47
47
|
|
|
48
|
-
gem 'acts-as-taggable-on'
|
|
48
|
+
gem 'acts-as-taggable-on'
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
==== Post Installation
|
|
51
51
|
|
|
52
52
|
1. rails generate acts_as_taggable_on:migration
|
|
53
53
|
2. rake db:migrate
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
== Testing
|
|
56
56
|
|
|
57
57
|
Acts As Taggable On uses RSpec for its test coverage. Inside the plugin
|
|
58
58
|
directory, you can run the specs for RoR 3.0.0 with:
|
|
@@ -68,7 +68,7 @@ If you already have RSpec on your application, the specs will run while using:
|
|
|
68
68
|
rake spec:plugins
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
== Usage
|
|
72
72
|
|
|
73
73
|
class User < ActiveRecord::Base
|
|
74
74
|
# Alias for <tt>acts_as_taggable_on :tags</tt>:
|
|
@@ -89,7 +89,7 @@ rake spec:plugins
|
|
|
89
89
|
User.skill_counts # => [<Tag name="joking" count=2>,<Tag name="clowning" count=1>...]
|
|
90
90
|
@frankie.skill_counts
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
=== Finding Tagged Objects
|
|
93
93
|
|
|
94
94
|
Acts As Taggable On utilizes named_scopes to create an association for tags.
|
|
95
95
|
This way you can mix and match to filter down your results, and it also improves
|
|
@@ -109,7 +109,7 @@ compatibility with the will_paginate gem:
|
|
|
109
109
|
# Find a user with any of the tags:
|
|
110
110
|
User.tagged_with(["awesome", "cool"], :any => true)
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
=== Relationships
|
|
113
113
|
|
|
114
114
|
You can find objects of the same type based on similar tags on certain contexts.
|
|
115
115
|
Also, objects will be returned in descending order based on the total number of
|
|
@@ -128,7 +128,7 @@ matched tags.
|
|
|
128
128
|
@bobby.find_related_skills # => [<User name="Tom">]
|
|
129
129
|
@frankie.find_related_skills # => [<User name="Tom">]
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
=== Dynamic Tag Contexts
|
|
132
132
|
|
|
133
133
|
In addition to the generated tag contexts in the definition, it is also possible
|
|
134
134
|
to allow for dynamic tag contexts (this could be user generated tag contexts!)
|
|
@@ -139,9 +139,9 @@ to allow for dynamic tag contexts (this could be user generated tag contexts!)
|
|
|
139
139
|
@user.save
|
|
140
140
|
@user.tags_on(:customs) # => [<Tag name='same'>,...]
|
|
141
141
|
@user.tag_counts_on(:customs)
|
|
142
|
-
User.
|
|
142
|
+
User.tagged_with("same", :on => :customs) # => [@user]
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
=== Tag Ownership
|
|
145
145
|
|
|
146
146
|
Tags can have owners:
|
|
147
147
|
|
|
@@ -158,7 +158,7 @@ Tags can have owners:
|
|
|
158
158
|
@some_user.owned_tags
|
|
159
159
|
@some_photo.locations_from(@some_user)
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
=== Tag cloud calculations
|
|
162
162
|
|
|
163
163
|
To construct tag clouds, the frequency of each tag needs to be calculated.
|
|
164
164
|
Because we specified +acts_as_taggable_on+ on the <tt>User</tt> class, we can
|
|
@@ -174,7 +174,7 @@ Here is an example that generates a tag cloud.
|
|
|
174
174
|
Helper:
|
|
175
175
|
|
|
176
176
|
module PostsHelper
|
|
177
|
-
include TagsHelper
|
|
177
|
+
include ActsAsTaggableOn::TagsHelper
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
Controller:
|
|
@@ -198,7 +198,7 @@ CSS:
|
|
|
198
198
|
.css3 { font-size: 1.4em; }
|
|
199
199
|
.css4 { font-size: 1.6em; }
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
== Contributors
|
|
202
202
|
|
|
203
203
|
* TomEric (i76) - Maintainer
|
|
204
204
|
* Michael Bleigh - Original Author
|
|
@@ -208,7 +208,7 @@ CSS:
|
|
|
208
208
|
* Pradeep Elankumaran - Taggers
|
|
209
209
|
* Sinclair Bain - Patch King
|
|
210
210
|
|
|
211
|
-
|
|
211
|
+
=== Patch Contributors
|
|
212
212
|
|
|
213
213
|
* tristanzdunn - Related objects of other classes
|
|
214
214
|
* azabaj - Fixed migrate down
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.0.
|
|
1
|
+
2.0.6
|
data/lib/acts-as-taggable-on.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
module ActsAsTaggableOn::Taggable
|
|
2
2
|
module Cache
|
|
3
3
|
def self.included(base)
|
|
4
|
-
# Skip adding caching capabilities if no cache columns exist
|
|
5
|
-
return unless base.tag_types.any? { |context| base.column_names.include?("cached_#{context.to_s.singularize}_list") }
|
|
4
|
+
# Skip adding caching capabilities if table not exists or no cache columns exist
|
|
5
|
+
return unless base.table_exists? && base.tag_types.any? { |context| base.column_names.include?("cached_#{context.to_s.singularize}_list") }
|
|
6
6
|
|
|
7
7
|
base.send :include, ActsAsTaggableOn::Taggable::Cache::InstanceMethods
|
|
8
8
|
base.extend ActsAsTaggableOn::Taggable::Cache::ClassMethods
|
|
@@ -50,4 +50,4 @@ module ActsAsTaggableOn::Taggable
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
|
-
end
|
|
53
|
+
end
|
|
@@ -53,39 +53,73 @@ module ActsAsTaggableOn::Taggable
|
|
|
53
53
|
def all_tag_counts(options = {})
|
|
54
54
|
options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
scope = if ActiveRecord::VERSION::MAJOR >= 3
|
|
57
|
+
{}
|
|
58
|
+
else
|
|
59
|
+
scope(:find) || {}
|
|
60
|
+
end
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
## Generate conditions:
|
|
63
|
+
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
|
64
|
+
|
|
65
|
+
start_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
|
66
|
+
end_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
|
67
|
+
|
|
68
|
+
taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?", base_class.name])
|
|
69
|
+
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options.delete(:id)]) if options[:id]
|
|
62
70
|
|
|
63
71
|
conditions = [
|
|
64
|
-
|
|
65
|
-
taggable_id,
|
|
72
|
+
taggable_conditions,
|
|
66
73
|
options[:conditions],
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
scope[:conditions],
|
|
75
|
+
start_at_conditions,
|
|
76
|
+
end_at_conditions
|
|
77
|
+
].compact.reverse
|
|
78
|
+
|
|
79
|
+
## Generate joins:
|
|
80
|
+
tagging_join = "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} ON #{ActsAsTaggableOn::Tag.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.tag_id"
|
|
81
|
+
tagging_join << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on]
|
|
70
82
|
|
|
71
|
-
|
|
83
|
+
taggable_join = "INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id"
|
|
84
|
+
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
|
|
72
85
|
|
|
73
|
-
joins = [
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
joins = [
|
|
87
|
+
tagging_join,
|
|
88
|
+
taggable_join,
|
|
89
|
+
scope[:joins]
|
|
90
|
+
].compact
|
|
91
|
+
|
|
92
|
+
joins = joins.reverse if ActiveRecord::VERSION::MAJOR < 3
|
|
76
93
|
|
|
77
|
-
unless descends_from_active_record?
|
|
78
|
-
# Current model is STI descendant, so add type checking to the join condition
|
|
79
|
-
joins << " AND #{table_name}.#{inheritance_column} = '#{name}'"
|
|
80
|
-
end
|
|
81
94
|
|
|
95
|
+
## Generate scope:
|
|
96
|
+
scope = ActsAsTaggableOn::Tag.scoped(:select => "#{ActsAsTaggableOn::Tag.table_name}.*, COUNT(*) AS count").order(options[:order]).limit(options[:limit])
|
|
97
|
+
|
|
98
|
+
# Joins and conditions
|
|
99
|
+
joins.each { |join| scope = scope.joins(join) }
|
|
100
|
+
conditions.each { |condition| scope = scope.where(condition) }
|
|
101
|
+
|
|
102
|
+
# GROUP BY and HAVING clauses:
|
|
82
103
|
at_least = sanitize_sql(['COUNT(*) >= ?', options.delete(:at_least)]) if options[:at_least]
|
|
83
104
|
at_most = sanitize_sql(['COUNT(*) <= ?', options.delete(:at_most)]) if options[:at_most]
|
|
84
|
-
having = [at_least, at_most].compact.join(' AND ')
|
|
85
|
-
|
|
86
|
-
|
|
105
|
+
having = [at_least, at_most].compact.join(' AND ')
|
|
106
|
+
|
|
107
|
+
if ActiveRecord::VERSION::MAJOR >= 3
|
|
108
|
+
# Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
|
|
109
|
+
scoped_select = "#{table_name}.#{primary_key}"
|
|
110
|
+
scope = scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{select(scoped_select).to_sql})")
|
|
111
|
+
|
|
112
|
+
# We have having() in RoR 3.0 so use it:
|
|
113
|
+
having = having.blank? ? "COUNT(*) > 0" : "COUNT(*) > 0 AND #{having}"
|
|
114
|
+
scope = scope.group(grouped_column_names_for(ActsAsTaggableOn::Tag)).having(having)
|
|
115
|
+
else
|
|
116
|
+
# Having is not available in 2.3.x:
|
|
117
|
+
group_by = "#{grouped_column_names_for(ActsAsTaggableOn::Tag)} HAVING COUNT(*) > 0"
|
|
118
|
+
group_by << " AND #{having}" unless having.blank?
|
|
119
|
+
scope = scope.group(group_by)
|
|
120
|
+
end
|
|
87
121
|
|
|
88
|
-
|
|
122
|
+
scope
|
|
89
123
|
end
|
|
90
124
|
end
|
|
91
125
|
|
|
@@ -20,9 +20,9 @@ module ActsAsTaggableOn::Taggable
|
|
|
20
20
|
context_tags = tags_type.to_sym
|
|
21
21
|
|
|
22
22
|
class_eval do
|
|
23
|
-
has_many context_taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "Tagging",
|
|
24
|
-
:conditions => [
|
|
25
|
-
has_many context_tags, :through => context_taggings, :source => :tag
|
|
23
|
+
has_many context_taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "ActsAsTaggableOn::Tagging",
|
|
24
|
+
:conditions => ["#{ActsAsTaggableOn::Tagging.table_name}.tagger_id IS NULL AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_type]
|
|
25
|
+
has_many context_tags, :through => context_taggings, :source => :tag, :class_name => "ActsAsTaggableOn::Tag"
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
class_eval %(
|
|
@@ -66,7 +66,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
66
66
|
# User.tagged_with("awesome", "cool", :any => true) # Users that are tagged with awesome or cool
|
|
67
67
|
# User.tagged_with("awesome", "cool", :match_all => true) # Users that are tagged with just awesome and cool
|
|
68
68
|
def tagged_with(tags, options = {})
|
|
69
|
-
tag_list = TagList.from(tags)
|
|
69
|
+
tag_list = ActsAsTaggableOn::TagList.from(tags)
|
|
70
70
|
|
|
71
71
|
return {} if tag_list.empty?
|
|
72
72
|
|
|
@@ -76,16 +76,16 @@ module ActsAsTaggableOn::Taggable
|
|
|
76
76
|
context = options.delete(:on)
|
|
77
77
|
|
|
78
78
|
if options.delete(:exclude)
|
|
79
|
-
tags_conditions = tag_list.map { |t| sanitize_sql(["#{Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
|
|
80
|
-
conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{Tagging.table_name}.taggable_id FROM #{Tagging.table_name} JOIN #{Tag.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND (#{tags_conditions}) WHERE #{Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"
|
|
79
|
+
tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
|
|
80
|
+
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}.id AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"
|
|
81
81
|
|
|
82
82
|
elsif options.delete(:any)
|
|
83
|
-
tags_conditions = tag_list.map { |t| sanitize_sql(["#{Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
|
|
84
|
-
conditions << "#{table_name}.#{primary_key} IN (SELECT #{Tagging.table_name}.taggable_id FROM #{Tagging.table_name} JOIN #{Tag.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND (#{tags_conditions}) WHERE #{Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"
|
|
83
|
+
tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
|
|
84
|
+
conditions << "#{table_name}.#{primary_key} 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}.id AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"
|
|
85
85
|
|
|
86
86
|
else
|
|
87
|
-
tags = Tag.named_any(tag_list)
|
|
88
|
-
return
|
|
87
|
+
tags = ActsAsTaggableOn::Tag.named_any(tag_list)
|
|
88
|
+
return scoped(:conditions => "1 = 0") unless tags.length == tag_list.length
|
|
89
89
|
|
|
90
90
|
tags.each do |tag|
|
|
91
91
|
safe_tag = tag.name.gsub(/[^a-zA-Z0-9]/, '')
|
|
@@ -93,7 +93,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
93
93
|
|
|
94
94
|
taggings_alias = "#{table_name}_taggings_#{prefix}"
|
|
95
95
|
|
|
96
|
-
tagging_join = "JOIN #{Tagging.table_name} #{taggings_alias}" +
|
|
96
|
+
tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
|
|
97
97
|
" ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
|
|
98
98
|
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}" +
|
|
99
99
|
" AND #{taggings_alias}.tag_id = #{tag.id}"
|
|
@@ -106,7 +106,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
106
106
|
taggings_alias, tags_alias = "#{table_name}_taggings_group", "#{table_name}_tags_group"
|
|
107
107
|
|
|
108
108
|
if options.delete(:match_all)
|
|
109
|
-
joins << "LEFT OUTER JOIN #{Tagging.table_name} #{taggings_alias}" +
|
|
109
|
+
joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
|
|
110
110
|
" ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
|
|
111
111
|
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}"
|
|
112
112
|
|
|
@@ -117,6 +117,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
117
117
|
scoped(:joins => joins.join(" "),
|
|
118
118
|
:group => group,
|
|
119
119
|
:conditions => conditions.join(" AND "),
|
|
120
|
+
:order => options[:order],
|
|
120
121
|
:readonly => false)
|
|
121
122
|
end
|
|
122
123
|
|
|
@@ -154,7 +155,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
154
155
|
|
|
155
156
|
def tag_list_cache_on(context)
|
|
156
157
|
variable_name = "@#{context.to_s.singularize}_list"
|
|
157
|
-
instance_variable_get(variable_name) || instance_variable_set(variable_name, TagList.new(tags_on(context).map(&:name)))
|
|
158
|
+
instance_variable_get(variable_name) || instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(tags_on(context).map(&:name)))
|
|
158
159
|
end
|
|
159
160
|
|
|
160
161
|
def tag_list_on(context)
|
|
@@ -166,40 +167,43 @@ module ActsAsTaggableOn::Taggable
|
|
|
166
167
|
variable_name = "@all_#{context.to_s.singularize}_list"
|
|
167
168
|
return instance_variable_get(variable_name) if instance_variable_get(variable_name)
|
|
168
169
|
|
|
169
|
-
instance_variable_set(variable_name, TagList.new(all_tags_on(context).map(&:name)).freeze)
|
|
170
|
+
instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(all_tags_on(context).map(&:name)).freeze)
|
|
170
171
|
end
|
|
171
172
|
|
|
172
173
|
##
|
|
173
174
|
# Returns all tags of a given context
|
|
174
175
|
def all_tags_on(context)
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
tag_table_name = ActsAsTaggableOn::Tag.table_name
|
|
177
|
+
tagging_table_name = ActsAsTaggableOn::Tagging.table_name
|
|
178
|
+
|
|
179
|
+
opts = ["#{tagging_table_name}.context = ?", context.to_s]
|
|
180
|
+
base_tags.where(opts).order("max(#{tagging_table_name}.created_at)").group("#{tag_table_name}.id, #{tag_table_name}.name").all
|
|
177
181
|
end
|
|
178
182
|
|
|
179
183
|
##
|
|
180
184
|
# Returns all tags that are not owned of a given context
|
|
181
185
|
def tags_on(context)
|
|
182
|
-
base_tags.where(["#{Tagging.table_name}.context = ? AND #{Tagging.table_name}.tagger_id IS NULL", context.to_s]).all
|
|
186
|
+
base_tags.where(["#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id IS NULL", context.to_s]).all
|
|
183
187
|
end
|
|
184
188
|
|
|
185
189
|
def set_tag_list_on(context, new_list)
|
|
186
190
|
add_custom_context(context)
|
|
187
191
|
|
|
188
192
|
variable_name = "@#{context.to_s.singularize}_list"
|
|
189
|
-
instance_variable_set(variable_name, TagList.from(new_list))
|
|
193
|
+
instance_variable_set(variable_name, ActsAsTaggableOn::TagList.from(new_list))
|
|
190
194
|
end
|
|
191
195
|
|
|
192
196
|
def tagging_contexts
|
|
193
197
|
custom_contexts + self.class.tag_types.map(&:to_s)
|
|
194
198
|
end
|
|
195
199
|
|
|
196
|
-
def reload
|
|
200
|
+
def reload(*args)
|
|
197
201
|
self.class.tag_types.each do |context|
|
|
198
202
|
instance_variable_set("@#{context.to_s.singularize}_list", nil)
|
|
199
203
|
instance_variable_set("@all_#{context.to_s.singularize}_list", nil)
|
|
200
204
|
end
|
|
201
205
|
|
|
202
|
-
super
|
|
206
|
+
super(*args)
|
|
203
207
|
end
|
|
204
208
|
|
|
205
209
|
def save_tags
|
|
@@ -209,7 +213,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
209
213
|
tag_list = tag_list_cache_on(context).uniq
|
|
210
214
|
|
|
211
215
|
# Find existing tags or create non-existing tags:
|
|
212
|
-
tag_list = Tag.find_or_create_all_with_like_by_name(tag_list)
|
|
216
|
+
tag_list = ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list)
|
|
213
217
|
|
|
214
218
|
current_tags = tags_on(context)
|
|
215
219
|
old_tags = current_tags - tag_list
|
|
@@ -221,7 +225,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
221
225
|
|
|
222
226
|
if old_taggings.present?
|
|
223
227
|
# Destroy old taggings:
|
|
224
|
-
Tagging.destroy_all :id => old_taggings.map(&:id)
|
|
228
|
+
ActsAsTaggableOn::Tagging.destroy_all :id => old_taggings.map(&:id)
|
|
225
229
|
end
|
|
226
230
|
|
|
227
231
|
# Create new taggings:
|
|
@@ -30,9 +30,9 @@ module ActsAsTaggableOn::Taggable
|
|
|
30
30
|
|
|
31
31
|
module InstanceMethods
|
|
32
32
|
def owner_tags_on(owner, context)
|
|
33
|
-
base_tags.where([%(#{Tagging.table_name}.context = ? AND
|
|
34
|
-
#{Tagging.table_name}.tagger_id = ? AND
|
|
35
|
-
#{Tagging.table_name}.tagger_type = ?), context.to_s, owner.id, owner.class.to_s]).all
|
|
33
|
+
base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND
|
|
34
|
+
#{ActsAsTaggableOn::Tagging.table_name}.tagger_id = ? AND
|
|
35
|
+
#{ActsAsTaggableOn::Tagging.table_name}.tagger_type = ?), context.to_s, owner.id, owner.class.to_s]).all
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def cached_owned_tag_list_on(context)
|
|
@@ -46,7 +46,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
46
46
|
cache = cached_owned_tag_list_on(context)
|
|
47
47
|
cache.delete_if { |key, value| key.id == owner.id && key.class == owner.class }
|
|
48
48
|
|
|
49
|
-
cache[owner] ||= TagList.new(*owner_tags_on(owner, context).map(&:name))
|
|
49
|
+
cache[owner] ||= ActsAsTaggableOn::TagList.new(*owner_tags_on(owner, context).map(&:name))
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def set_owner_tag_list_on(owner, context, new_list)
|
|
@@ -55,22 +55,22 @@ module ActsAsTaggableOn::Taggable
|
|
|
55
55
|
cache = cached_owned_tag_list_on(context)
|
|
56
56
|
cache.delete_if { |key, value| key.id == owner.id && key.class == owner.class }
|
|
57
57
|
|
|
58
|
-
cache[owner] = TagList.from(new_list)
|
|
58
|
+
cache[owner] = ActsAsTaggableOn::TagList.from(new_list)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
def reload
|
|
61
|
+
def reload(*args)
|
|
62
62
|
self.class.tag_types.each do |context|
|
|
63
63
|
instance_variable_set("@owned_#{context}_list", nil)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
super
|
|
66
|
+
super(*args)
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
def save_owned_tags
|
|
70
70
|
tagging_contexts.each do |context|
|
|
71
71
|
cached_owned_tag_list_on(context).each do |owner, tag_list|
|
|
72
72
|
# Find existing tags or create non-existing tags:
|
|
73
|
-
tag_list = Tag.find_or_create_all_with_like_by_name(tag_list.uniq)
|
|
73
|
+
tag_list = ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list.uniq)
|
|
74
74
|
|
|
75
75
|
owned_tags = owner_tags_on(owner, context)
|
|
76
76
|
old_tags = owned_tags - tag_list
|
|
@@ -78,13 +78,13 @@ module ActsAsTaggableOn::Taggable
|
|
|
78
78
|
|
|
79
79
|
# Find all taggings that belong to the taggable (self), are owned by the owner,
|
|
80
80
|
# have the correct context, and are removed from the list.
|
|
81
|
-
old_taggings = Tagging.where(:taggable_id => id, :taggable_type => self.class.base_class.to_s,
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
old_taggings = ActsAsTaggableOn::Tagging.where(:taggable_id => id, :taggable_type => self.class.base_class.to_s,
|
|
82
|
+
:tagger_type => owner.class.to_s, :tagger_id => owner.id,
|
|
83
|
+
:tag_id => old_tags, :context => context).all
|
|
84
84
|
|
|
85
85
|
if old_taggings.present?
|
|
86
86
|
# Destroy old taggings:
|
|
87
|
-
Tagging.destroy_all(:id => old_taggings.map(&:id))
|
|
87
|
+
ActsAsTaggableOn::Tagging.destroy_all(:id => old_taggings.map(&:id))
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
# Create new taggings:
|
|
@@ -3,6 +3,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
3
3
|
def self.included(base)
|
|
4
4
|
base.send :include, ActsAsTaggableOn::Taggable::Related::InstanceMethods
|
|
5
5
|
base.extend ActsAsTaggableOn::Taggable::Related::ClassMethods
|
|
6
|
+
base.initialize_acts_as_taggable_on_related
|
|
6
7
|
end
|
|
7
8
|
|
|
8
9
|
module ClassMethods
|
|
@@ -41,9 +42,9 @@ module ActsAsTaggableOn::Taggable
|
|
|
41
42
|
|
|
42
43
|
exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
|
|
43
44
|
|
|
44
|
-
klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
|
|
45
|
-
:from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
|
|
46
|
-
:conditions => ["#{exclude_self} #{klass.table_name}.id = #{Tagging.table_name}.taggable_id AND #{Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND #{Tag.table_name}.name IN (?) AND #{Tagging.table_name}.context = ?", tags_to_find, result_context],
|
|
45
|
+
klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.id) AS count",
|
|
46
|
+
:from => "#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}",
|
|
47
|
+
:conditions => ["#{exclude_self} #{klass.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context],
|
|
47
48
|
:group => grouped_column_names_for(klass),
|
|
48
49
|
:order => "count DESC" }.update(options))
|
|
49
50
|
end
|
|
@@ -53,9 +54,9 @@ module ActsAsTaggableOn::Taggable
|
|
|
53
54
|
|
|
54
55
|
exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
|
|
55
56
|
|
|
56
|
-
klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
|
|
57
|
-
:from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
|
|
58
|
-
:conditions => ["#{exclude_self} #{klass.table_name}.id = #{Tagging.table_name}.taggable_id AND #{Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND #{Tag.table_name}.name IN (?)", tags_to_find],
|
|
57
|
+
klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.id) AS count",
|
|
58
|
+
:from => "#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}",
|
|
59
|
+
:conditions => ["#{exclude_self} #{klass.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find],
|
|
59
60
|
:group => grouped_column_names_for(klass),
|
|
60
61
|
:order => "count DESC" }.update(options))
|
|
61
62
|
end
|
|
@@ -34,8 +34,8 @@ module ActsAsTaggableOn
|
|
|
34
34
|
class_inheritable_reader(:tag_types)
|
|
35
35
|
|
|
36
36
|
class_eval do
|
|
37
|
-
has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag
|
|
38
|
-
has_many :base_tags, :
|
|
37
|
+
has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "ActsAsTaggableOn::Tagging"
|
|
38
|
+
has_many :base_tags, :through => :taggings, :source => :tag, :class_name => "ActsAsTaggableOn::Tag"
|
|
39
39
|
|
|
40
40
|
def self.taggable?
|
|
41
41
|
true
|
|
@@ -16,8 +16,8 @@ module ActsAsTaggableOn
|
|
|
16
16
|
def acts_as_tagger(opts={})
|
|
17
17
|
class_eval do
|
|
18
18
|
has_many :owned_taggings, opts.merge(:as => :tagger, :dependent => :destroy,
|
|
19
|
-
:include => :tag, :class_name => "Tagging")
|
|
20
|
-
has_many :owned_tags, :through => :owned_taggings, :source => :tag, :uniq => true
|
|
19
|
+
:include => :tag, :class_name => "ActsAsTaggableOn::Tagging")
|
|
20
|
+
has_many :owned_tags, :through => :owned_taggings, :source => :tag, :uniq => true, :class_name => "ActsAsTaggableOn::Tag"
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
include ActsAsTaggableOn::Tagger::InstanceMethods
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module ActsAsTaggableOn
|
|
2
|
+
module Taggable
|
|
3
|
+
module PostgreSQL
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.send :include, ActsAsTaggableOn::Taggable::PostgreSQL::InstanceMethods
|
|
6
|
+
base.extend ActsAsTaggableOn::Taggable::PostgreSQL::ClassMethods
|
|
7
|
+
|
|
8
|
+
ActsAsTaggableOn::Tag.class_eval do
|
|
9
|
+
def self.named(name)
|
|
10
|
+
where(["name ILIKE ?", name])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.named_any(list)
|
|
14
|
+
where(list.map { |tag| sanitize_sql(["name ILIKE ?", tag.to_s]) }.join(" OR "))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.named_like(name)
|
|
18
|
+
where(["name ILIKE ?", "%#{name}%"])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.named_like_any(list)
|
|
22
|
+
where(list.map { |tag| sanitize_sql(["name ILIKE ?", "%#{tag.to_s}%"]) }.join(" OR "))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module InstanceMethods
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module ClassMethods
|
|
31
|
+
# all column names are necessary for PostgreSQL group clause
|
|
32
|
+
def grouped_column_names_for(*objects)
|
|
33
|
+
object = objects.shift
|
|
34
|
+
columns = object.column_names.map { |column| "#{object.table_name}.#{column}" }
|
|
35
|
+
columns << objects.map do |object|
|
|
36
|
+
"#{object.table_name}.created_at"
|
|
37
|
+
end.flatten
|
|
38
|
+
|
|
39
|
+
columns.flatten.join(", ")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|