acts-as-taggable-on 2.0.0.pre1 → 2.0.0.pre3
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -6
- data/README.rdoc +10 -0
- data/Rakefile +44 -16
- data/VERSION +1 -1
- data/lib/acts-as-taggable-on.rb +19 -16
- data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +30 -415
- data/lib/acts_as_taggable_on/acts_as_taggable_on/aggregate.rb +90 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +39 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +202 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +74 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +74 -0
- data/lib/acts_as_taggable_on/acts_as_tagger.rb +34 -44
- data/lib/acts_as_taggable_on/compatibility/Gemfile +6 -0
- data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +17 -0
- data/lib/acts_as_taggable_on/compatibility/tag.rb +3 -0
- data/lib/acts_as_taggable_on/compatibility/tagging.rb +3 -0
- data/lib/acts_as_taggable_on/tag.rb +16 -5
- data/lib/acts_as_taggable_on/tags_helper.rb +1 -1
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +37 -29
- data/spec/acts_as_taggable_on/tag_spec.rb +13 -2
- data/spec/acts_as_taggable_on/taggable_spec.rb +35 -21
- data/spec/acts_as_taggable_on/tagger_spec.rb +17 -1
- data/spec/acts_as_taggable_on/tagging_spec.rb +6 -1
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +0 -2
- data/spec/models.rb +32 -0
- data/spec/schema.rb +3 -1
- data/spec/spec_helper.rb +18 -37
- metadata +14 -6
- data/lib/acts_as_taggable_on/group_helper.rb +0 -14
- data/spec/acts_as_taggable_on/group_helper_spec.rb +0 -21
data/Gemfile
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
source :gemcutter
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
gem 'sqlite3-ruby', :require => 'sqlite3'
|
8
|
-
end
|
3
|
+
# Rails 3.0
|
4
|
+
gem 'rails', '3.0.0.beta'
|
5
|
+
gem 'rspec', '2.0.0.beta.1'
|
6
|
+
gem 'sqlite3-ruby', '1.2.5', :require => 'sqlite3'
|
data/README.rdoc
CHANGED
@@ -31,6 +31,16 @@ To install the gem, add this to your config/environment.rb:
|
|
31
31
|
|
32
32
|
After that, you can run "rake gems:install" to install the gem if you don't already have it.
|
33
33
|
|
34
|
+
== Rails 3.0
|
35
|
+
|
36
|
+
Acts As Taggable On is now useable in Rails 3.0, thanks to the excellent work of Szymon Nowak
|
37
|
+
and Jelle Vandebeeck. Because backwards compatibility is hard to maintain, their work is available
|
38
|
+
in the feature/rails3_compatibility branch.
|
39
|
+
|
40
|
+
A Rails 3.0 compatible version of the gem is also available:
|
41
|
+
|
42
|
+
gem install acts-as-taggable-on -v=2.0.0.pre1
|
43
|
+
|
34
44
|
=== Post Installation (Rails)
|
35
45
|
|
36
46
|
1. script/generate acts_as_taggable_on_migration
|
data/Rakefile
CHANGED
@@ -1,5 +1,45 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
begin
|
2
|
+
# Rspec 2.0
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
desc 'Default: run specs'
|
6
|
+
task :default => :spec
|
7
|
+
Rspec::Core::RakeTask.new do |t|
|
8
|
+
t.pattern = "spec/**/*_spec.rb"
|
9
|
+
end
|
10
|
+
|
11
|
+
Rspec::Core::RakeTask.new('rcov') do |t|
|
12
|
+
t.pattern = "spec/**/*_spec.rb"
|
13
|
+
t.rcov = true
|
14
|
+
t.rcov_opts = ['--exclude', 'spec']
|
15
|
+
end
|
16
|
+
|
17
|
+
rescue LoadError
|
18
|
+
# Rspec 1.3.0
|
19
|
+
require 'spec/rake/spectask'
|
20
|
+
|
21
|
+
desc 'Default: run specs'
|
22
|
+
task :default => :spec
|
23
|
+
Spec::Rake::SpecTask.new do |t|
|
24
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
25
|
+
end
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new('rcov') do |t|
|
28
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
29
|
+
t.rcov = true
|
30
|
+
t.rcov_opts = ['--exclude', 'spec']
|
31
|
+
end
|
32
|
+
rescue LoadError
|
33
|
+
puts "Rspec not available. Install it with: gem install rspec"
|
34
|
+
end
|
35
|
+
|
36
|
+
namespace 'rails2.3' do
|
37
|
+
task :spec do
|
38
|
+
gemfile = File.join(File.dirname(__FILE__), 'lib', 'acts_as_taggable_on', 'compatibility', 'Gemfile')
|
39
|
+
ENV['BUNDLE_GEMFILE'] = gemfile
|
40
|
+
Rake::Task['spec'].invoke
|
41
|
+
end
|
42
|
+
end
|
3
43
|
|
4
44
|
begin
|
5
45
|
require 'jeweler'
|
@@ -14,17 +54,5 @@ begin
|
|
14
54
|
end
|
15
55
|
Jeweler::GemcutterTasks.new
|
16
56
|
rescue LoadError
|
17
|
-
puts "Jeweler not available. Install it with:
|
18
|
-
end
|
19
|
-
|
20
|
-
desc 'Default: run specs'
|
21
|
-
task :default => :spec
|
22
|
-
Rspec::Core::RakeTask.new do |t|
|
23
|
-
t.pattern = "spec/**/*_spec.rb"
|
24
|
-
end
|
25
|
-
|
26
|
-
Rspec::Core::RakeTask.new('rcov') do |t|
|
27
|
-
t.pattern = "spec/**/*_spec.rb"
|
28
|
-
t.rcov = true
|
29
|
-
t.rcov_opts = ['--exclude', 'spec']
|
30
|
-
end
|
57
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
58
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.0.0.
|
1
|
+
2.0.0.pre3
|
data/lib/acts-as-taggable-on.rb
CHANGED
@@ -1,27 +1,30 @@
|
|
1
|
-
begin
|
2
|
-
# Try to require the preresolved locked set of gems.
|
3
|
-
require File.expand_path("../.bundle/environment", __FILE__)
|
4
|
-
rescue LoadError
|
5
|
-
# Fall back on doing an unlocked resolve at runtime.
|
6
|
-
require "rubygems"
|
7
|
-
require "bundler"
|
8
|
-
Bundler.setup
|
9
|
-
end
|
10
|
-
|
11
|
-
Bundler.require
|
12
|
-
|
13
1
|
require "active_record"
|
14
2
|
require "action_view"
|
15
3
|
|
16
|
-
|
4
|
+
if ActiveRecord::VERSION::MAJOR < 3
|
5
|
+
require "acts_as_taggable_on/compatibility/active_record_backports"
|
6
|
+
require "acts_as_taggable_on/compatibility/tag"
|
7
|
+
require "acts_as_taggable_on/compatibility/tagging"
|
8
|
+
end
|
9
|
+
|
17
10
|
require "acts_as_taggable_on/acts_as_taggable_on"
|
11
|
+
require "acts_as_taggable_on/acts_as_taggable_on/core"
|
12
|
+
require "acts_as_taggable_on/acts_as_taggable_on/aggregate"
|
13
|
+
require "acts_as_taggable_on/acts_as_taggable_on/cache"
|
14
|
+
require "acts_as_taggable_on/acts_as_taggable_on/ownership"
|
15
|
+
require "acts_as_taggable_on/acts_as_taggable_on/related"
|
16
|
+
|
18
17
|
require "acts_as_taggable_on/acts_as_tagger"
|
19
18
|
require "acts_as_taggable_on/tag"
|
20
19
|
require "acts_as_taggable_on/tag_list"
|
21
20
|
require "acts_as_taggable_on/tags_helper"
|
22
21
|
require "acts_as_taggable_on/tagging"
|
23
22
|
|
24
|
-
ActiveRecord::Base
|
25
|
-
ActiveRecord::Base.
|
23
|
+
if defined?(ActiveRecord::Base)
|
24
|
+
ActiveRecord::Base.extend ActsAsTaggableOn::Taggable
|
25
|
+
ActiveRecord::Base.send :include, ActsAsTaggableOn::Tagger
|
26
|
+
end
|
26
27
|
|
27
|
-
ActionView::Base
|
28
|
+
if defined?(ActionView::Base)
|
29
|
+
ActionView::Base.send :include, TagsHelper
|
30
|
+
end
|
@@ -1,426 +1,41 @@
|
|
1
|
-
module
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
base.extend(ClassMethods)
|
7
|
-
end
|
8
|
-
|
9
|
-
module ClassMethods
|
10
|
-
|
11
|
-
def taggable?
|
12
|
-
false
|
13
|
-
end
|
14
|
-
|
15
|
-
def acts_as_taggable
|
16
|
-
acts_as_taggable_on :tags
|
17
|
-
end
|
18
|
-
|
19
|
-
def acts_as_taggable_on(*args)
|
20
|
-
args.flatten! if args
|
21
|
-
args.compact! if args
|
22
|
-
|
23
|
-
for tag_type in args
|
24
|
-
tag_type = tag_type.to_s
|
25
|
-
# use aliased_join_table_name for context condition so that sphinx can join multiple
|
26
|
-
# tag references from same model without getting an ambiguous column error
|
27
|
-
class_eval do
|
28
|
-
has_many "#{tag_type.singularize}_taggings".to_sym, :as => :taggable, :dependent => :destroy,
|
29
|
-
:include => :tag, :conditions => ['#{aliased_join_table_name || Tagging.table_name rescue Tagging.table_name}.context = ?',tag_type], :class_name => "Tagging"
|
30
|
-
has_many "#{tag_type}".to_sym, :through => "#{tag_type.singularize}_taggings".to_sym, :source => :tag
|
31
|
-
end
|
32
|
-
|
33
|
-
class_eval <<-RUBY
|
34
|
-
|
35
|
-
def self.taggable?
|
36
|
-
true
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.caching_#{tag_type.singularize}_list?
|
40
|
-
caching_tag_list_on?("#{tag_type}")
|
41
|
-
end
|
42
|
-
|
43
|
-
def self.#{tag_type.singularize}_counts(options={})
|
44
|
-
tag_counts_on('#{tag_type}',options)
|
45
|
-
end
|
46
|
-
|
47
|
-
def #{tag_type.singularize}_list
|
48
|
-
tag_list_on('#{tag_type}')
|
49
|
-
end
|
50
|
-
|
51
|
-
def #{tag_type.singularize}_list=(new_tags)
|
52
|
-
set_tag_list_on('#{tag_type}',new_tags)
|
53
|
-
end
|
54
|
-
|
55
|
-
def #{tag_type.singularize}_counts(options = {})
|
56
|
-
tag_counts_on('#{tag_type}',options)
|
57
|
-
end
|
58
|
-
|
59
|
-
def #{tag_type}_from(owner)
|
60
|
-
tag_list_on('#{tag_type}', owner)
|
61
|
-
end
|
62
|
-
|
63
|
-
def find_related_#{tag_type}(options = {})
|
64
|
-
related_tags_for('#{tag_type}', self.class, options)
|
65
|
-
end
|
66
|
-
alias_method :find_related_on_#{tag_type}, :find_related_#{tag_type}
|
67
|
-
|
68
|
-
def find_related_#{tag_type}_for(klass, options = {})
|
69
|
-
related_tags_for('#{tag_type}', klass, options)
|
70
|
-
end
|
71
|
-
|
72
|
-
def find_matching_contexts(search_context, result_context, options = {})
|
73
|
-
matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options)
|
74
|
-
end
|
75
|
-
|
76
|
-
def find_matching_contexts_for(klass, search_context, result_context, options = {})
|
77
|
-
matching_contexts_for(search_context.to_s, result_context.to_s, klass, options)
|
78
|
-
end
|
79
|
-
|
80
|
-
def top_#{tag_type}(limit = 10)
|
81
|
-
tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
|
82
|
-
end
|
83
|
-
|
84
|
-
def self.top_#{tag_type}(limit = 10)
|
85
|
-
tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
|
86
|
-
end
|
87
|
-
|
88
|
-
RUBY
|
89
|
-
end
|
90
|
-
|
91
|
-
if respond_to?(:tag_types)
|
92
|
-
write_inheritable_attribute( :tag_types, (tag_types + args).uniq )
|
93
|
-
else
|
94
|
-
class_eval do
|
95
|
-
write_inheritable_attribute(:tag_types, args.uniq)
|
96
|
-
class_inheritable_reader :tag_types
|
97
|
-
|
98
|
-
has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag
|
99
|
-
has_many :base_tags, :class_name => "Tag", :through => :taggings, :source => :tag
|
100
|
-
|
101
|
-
attr_writer :custom_contexts
|
102
|
-
|
103
|
-
before_save :save_cached_tag_list
|
104
|
-
|
105
|
-
after_save:save_tags
|
106
|
-
|
107
|
-
scope :tagged_with, lambda{ |*args|
|
108
|
-
find_options_for_find_tagged_with(*args)
|
109
|
-
}
|
110
|
-
end
|
111
|
-
|
112
|
-
include ActiveRecord::Acts::TaggableOn::InstanceMethods
|
113
|
-
extend ActiveRecord::Acts::TaggableOn::SingletonMethods
|
114
|
-
alias_method_chain :reload, :tag_list
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
end
|
119
|
-
|
120
|
-
module SingletonMethods
|
121
|
-
|
122
|
-
include ActiveRecord::Acts::TaggableOn::GroupHelper
|
123
|
-
|
124
|
-
# Pass either a tag string, or an array of strings or tags
|
125
|
-
#
|
126
|
-
# Options:
|
127
|
-
# :any - find models that match any of the given tags
|
128
|
-
# :exclude - Find models that are not tagged with the given tags
|
129
|
-
# :match_all - Find models that match all of the given tags, not just one
|
130
|
-
# :conditions - A piece of SQL conditions to add to the query
|
131
|
-
# :on - scopes the find to a context
|
132
|
-
# def find_tagged_with(*args)
|
133
|
-
# find_options_for_find_tagged_with(*args)
|
134
|
-
# end
|
135
|
-
|
136
|
-
def caching_tag_list_on?(context)
|
137
|
-
column_names.include?("cached_#{context.to_s.singularize}_list")
|
138
|
-
end
|
139
|
-
|
140
|
-
def tag_counts_on(context, options = {})
|
141
|
-
find_for_tag_counts(options.merge({:on => context.to_s}))
|
142
|
-
end
|
143
|
-
|
144
|
-
def all_tag_counts(options = {})
|
145
|
-
find_for_tag_counts(options)
|
146
|
-
end
|
147
|
-
|
148
|
-
def find_options_for_find_tagged_with(tags, options = {})
|
149
|
-
tag_list = TagList.from(tags)
|
150
|
-
|
151
|
-
return {} if tag_list.empty?
|
152
|
-
|
153
|
-
joins = []
|
154
|
-
conditions = []
|
155
|
-
|
156
|
-
context = options.delete(:on)
|
157
|
-
|
158
|
-
if options.delete(:exclude)
|
159
|
-
tags_conditions = tag_list.map { |t| sanitize_sql(["#{Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
|
160
|
-
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)})"
|
161
|
-
|
162
|
-
elsif options.delete(:any)
|
163
|
-
tags_conditions = tag_list.map { |t| sanitize_sql(["#{Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
|
164
|
-
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)})"
|
165
|
-
|
166
|
-
else
|
167
|
-
tags = Tag.named_any(tag_list)
|
168
|
-
return { :conditions => "1 = 0" } unless tags.length == tag_list.length
|
169
|
-
|
170
|
-
tags.each do |tag|
|
171
|
-
safe_tag = tag.name.gsub(/[^a-zA-Z0-9]/, '')
|
172
|
-
prefix = "#{safe_tag}_#{rand(1024)}"
|
173
|
-
|
174
|
-
taggings_alias = "#{table_name}_taggings_#{prefix}"
|
175
|
-
|
176
|
-
tagging_join = "JOIN #{Tagging.table_name} #{taggings_alias}" +
|
177
|
-
" ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
|
178
|
-
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}" +
|
179
|
-
" AND #{taggings_alias}.tag_id = #{tag.id}"
|
180
|
-
tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
|
181
|
-
|
182
|
-
joins << tagging_join
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
taggings_alias, tags_alias = "#{table_name}_taggings_group", "#{table_name}_tags_group"
|
187
|
-
|
188
|
-
if options.delete(:match_all)
|
189
|
-
joins << "LEFT OUTER JOIN #{Tagging.table_name} #{taggings_alias}" +
|
190
|
-
" ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
|
191
|
-
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}"
|
192
|
-
|
193
|
-
group = "#{grouped_column_names_for(self)} HAVING COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
|
194
|
-
end
|
195
|
-
|
196
|
-
Tag.joins(joins.join(" ")).group(group).where(conditions.join(" AND ")).readonly(false)
|
197
|
-
|
198
|
-
# { :joins => joins.join(" "),
|
199
|
-
# :group => group,
|
200
|
-
# :conditions => conditions.join(" AND "),
|
201
|
-
# :readonly => false }.update(options)
|
202
|
-
end
|
203
|
-
|
204
|
-
# Calculate the tag counts for all tags.
|
205
|
-
#
|
206
|
-
# Options:
|
207
|
-
# :start_at - Restrict the tags to those created after a certain time
|
208
|
-
# :end_at - Restrict the tags to those created before a certain time
|
209
|
-
# :conditions - A piece of SQL conditions to add to the query
|
210
|
-
# :limit - The maximum number of tags to return
|
211
|
-
# :order - A piece of SQL to order by. Eg 'tags.count desc' or 'taggings.created_at desc'
|
212
|
-
# :at_least - Exclude tags with a frequency less than the given value
|
213
|
-
# :at_most - Exclude tags with a frequency greater than the given value
|
214
|
-
# :on - Scope the find to only include a certain context
|
215
|
-
def find_for_tag_counts(options = {})
|
216
|
-
options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id
|
217
|
-
|
218
|
-
start_at = sanitize_sql(["#{Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
219
|
-
end_at = sanitize_sql(["#{Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
220
|
-
|
221
|
-
taggable_type = sanitize_sql(["#{Tagging.table_name}.taggable_type = ?", base_class.name])
|
222
|
-
taggable_id = sanitize_sql(["#{Tagging.table_name}.taggable_id = ?", options.delete(:id)]) if options[:id]
|
223
|
-
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
224
|
-
|
225
|
-
conditions = [
|
226
|
-
taggable_type,
|
227
|
-
taggable_id,
|
228
|
-
options[:conditions],
|
229
|
-
start_at,
|
230
|
-
end_at
|
231
|
-
]
|
232
|
-
|
233
|
-
conditions = conditions.compact.join(' AND ')
|
234
|
-
|
235
|
-
joins = ["LEFT OUTER JOIN #{Tagging.table_name} ON #{Tag.table_name}.id = #{Tagging.table_name}.tag_id"]
|
236
|
-
joins << sanitize_sql(["AND #{Tagging.table_name}.context = ?",options.delete(:on).to_s]) unless options[:on].nil?
|
237
|
-
joins << " INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id"
|
238
|
-
|
239
|
-
unless descends_from_active_record?
|
240
|
-
# Current model is STI descendant, so add type checking to the join condition
|
241
|
-
joins << " AND #{table_name}.#{inheritance_column} = '#{name}'"
|
242
|
-
end
|
243
|
-
|
244
|
-
at_least = sanitize_sql(['COUNT(*) >= ?', options.delete(:at_least)]) if options[:at_least]
|
245
|
-
at_most = sanitize_sql(['COUNT(*) <= ?', options.delete(:at_most)]) if options[:at_most]
|
246
|
-
having = [at_least, at_most].compact.join(' AND ')
|
247
|
-
group_by = "#{grouped_column_names_for(Tag)} HAVING COUNT(*) > 0"
|
248
|
-
group_by << " AND #{having}" unless having.blank?
|
249
|
-
|
250
|
-
Tag.select("#{Tag.table_name}.*, COUNT(*) AS count").joins(joins.join(" ")).where(conditions).group(group_by).limit(options[:limit]).order(options[:order])
|
251
|
-
|
252
|
-
end
|
253
|
-
|
254
|
-
def is_taggable?
|
255
|
-
true
|
256
|
-
end
|
257
|
-
|
258
|
-
end
|
259
|
-
|
260
|
-
module InstanceMethods
|
261
|
-
|
262
|
-
include ActiveRecord::Acts::TaggableOn::GroupHelper
|
263
|
-
|
264
|
-
def custom_contexts
|
265
|
-
@custom_contexts ||= []
|
266
|
-
end
|
267
|
-
|
268
|
-
def is_taggable?
|
269
|
-
self.class.is_taggable?
|
270
|
-
end
|
271
|
-
|
272
|
-
def add_custom_context(value)
|
273
|
-
custom_contexts << value.to_s unless custom_contexts.include?(value.to_s) or self.class.tag_types.map(&:to_s).include?(value.to_s)
|
274
|
-
end
|
275
|
-
|
276
|
-
def tag_list_on(context, owner = nil)
|
277
|
-
add_custom_context(context)
|
278
|
-
cache = tag_list_cache_on(context)
|
279
|
-
return owner ? cache[owner] : cache[owner] if cache[owner]
|
280
|
-
|
281
|
-
if !owner && self.class.caching_tag_list_on?(context) and !(cached_value = cached_tag_list_on(context)).nil?
|
282
|
-
cache[owner] = TagList.from(cached_tag_list_on(context))
|
283
|
-
else
|
284
|
-
cache[owner] = TagList.new(*tags_on(context, owner).map(&:name))
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
def all_tags_list_on(context)
|
289
|
-
variable_name = "@all_#{context.to_s.singularize}_list"
|
290
|
-
return instance_variable_get(variable_name) if instance_variable_get(variable_name)
|
291
|
-
instance_variable_set(variable_name, TagList.new(all_tags_on(context).map(&:name)).freeze)
|
292
|
-
end
|
293
|
-
|
294
|
-
def all_tags_on(context)
|
295
|
-
opts = ["#{Tagging.table_name}.context = ?", context.to_s]
|
296
|
-
base_tags.where(opts).order("#{Tagging.table_name}.created_at")
|
297
|
-
end
|
298
|
-
|
299
|
-
def tags_on(context, owner = nil)
|
300
|
-
if owner
|
301
|
-
opts = ["#{Tagging.table_name}.context = ? AND #{Tagging.table_name}.tagger_id = ? AND #{Tagging.table_name}.tagger_type = ?", context.to_s, owner.id, owner.class.to_s]
|
302
|
-
else
|
303
|
-
opts = ["#{Tagging.table_name}.context = ? AND #{Tagging.table_name}.tagger_id IS NULL", context.to_s]
|
304
|
-
end
|
305
|
-
base_tags.where(opts)
|
306
|
-
end
|
307
|
-
|
308
|
-
def cached_tag_list_on(context)
|
309
|
-
self["cached_#{context.to_s.singularize}_list"]
|
310
|
-
end
|
311
|
-
|
312
|
-
def tag_list_cache_on(context)
|
313
|
-
variable_name = "@#{context.to_s.singularize}_list"
|
314
|
-
cache = instance_variable_get(variable_name)
|
315
|
-
instance_variable_set(variable_name, cache = {}) unless cache
|
316
|
-
cache
|
317
|
-
end
|
318
|
-
|
319
|
-
def set_tag_list_on(context, new_list, tagger = nil)
|
320
|
-
tag_list_cache_on(context)[tagger] = TagList.from(new_list)
|
321
|
-
add_custom_context(context)
|
322
|
-
end
|
323
|
-
|
324
|
-
def tag_counts_on(context, options={})
|
325
|
-
self.class.tag_counts_on(context, options.merge(:id => id))
|
326
|
-
end
|
327
|
-
|
328
|
-
def related_tags_for(context, klass, options = {})
|
329
|
-
search_conditions = related_search_options(context, klass, options)
|
330
|
-
|
331
|
-
klass.select(search_conditions[:select]).from(search_conditions[:from]).where(search_conditions[:conditions]).group(search_conditions[:group]).order(search_conditions[:order])
|
332
|
-
end
|
333
|
-
|
334
|
-
def related_search_options(context, klass, options = {})
|
335
|
-
tags_to_find = tags_on(context).collect { |t| t.name }
|
336
|
-
|
337
|
-
exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
|
338
|
-
|
339
|
-
{ :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
|
340
|
-
:from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
|
341
|
-
: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],
|
342
|
-
:group => grouped_column_names_for(klass),
|
343
|
-
:order => "count DESC"
|
344
|
-
}.update(options)
|
345
|
-
end
|
346
|
-
|
347
|
-
def matching_contexts_for(search_context, result_context, klass, options = {})
|
348
|
-
search_conditions = matching_context_search_options(search_context, result_context, klass, options)
|
349
|
-
|
350
|
-
klass.select(search_conditions[:select]).from(search_conditions[:from]).where(search_conditions[:conditions]).group(search_conditions[:group]).order(search_conditions[:order])
|
351
|
-
end
|
352
|
-
|
353
|
-
def matching_context_search_options(search_context, result_context, klass, options = {})
|
354
|
-
tags_to_find = tags_on(search_context).collect { |t| t.name }
|
355
|
-
|
356
|
-
exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
|
357
|
-
|
358
|
-
{ :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
|
359
|
-
:from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
|
360
|
-
: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],
|
361
|
-
:group => grouped_column_names_for(klass),
|
362
|
-
:order => "count DESC"
|
363
|
-
}.update(options)
|
364
|
-
end
|
365
|
-
|
366
|
-
def save_cached_tag_list
|
367
|
-
self.class.tag_types.map(&:to_s).each do |tag_type|
|
368
|
-
if self.class.send("caching_#{tag_type.singularize}_list?")
|
369
|
-
self["cached_#{tag_type.singularize}_list"] = tag_list_cache_on(tag_type.singularize).to_a.flatten.compact.join(', ')
|
370
|
-
end
|
371
|
-
end
|
372
|
-
end
|
373
|
-
|
374
|
-
def save_tags
|
375
|
-
contexts = custom_contexts + self.class.tag_types.map(&:to_s)
|
376
|
-
|
377
|
-
transaction do
|
378
|
-
contexts.each do |context|
|
379
|
-
cache = tag_list_cache_on(context)
|
380
|
-
|
381
|
-
cache.each do |owner, list|
|
382
|
-
new_tags = Tag.find_or_create_all_with_like_by_name(list.uniq)
|
383
|
-
taggings = Tagging.where({ :taggable_id => self.id, :taggable_type => self.class.base_class.to_s })
|
384
|
-
|
385
|
-
# Destroy old taggings:
|
386
|
-
if owner
|
387
|
-
old_tags = tags_on(context, owner) - new_tags
|
388
|
-
old_taggings = Tagging.where({ :taggable_id => self.id, :taggable_type => self.class.base_class.to_s, :tag_id => old_tags, :tagger_id => owner.id, :tagger_type => owner.class.to_s, :context => context })
|
389
|
-
|
390
|
-
Tagging.destroy_all :id => old_taggings.map(&:id)
|
391
|
-
else
|
392
|
-
old_tags = tags_on(context) - new_tags
|
393
|
-
base_tags.delete(*old_tags)
|
394
|
-
end
|
1
|
+
module ActsAsTaggableOn
|
2
|
+
module Taggable
|
3
|
+
def taggable?
|
4
|
+
false
|
5
|
+
end
|
395
6
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
tagging.tagger_type == (owner ? owner.class.to_s : nil) &&
|
400
|
-
tagging.context == context
|
401
|
-
}
|
402
|
-
}
|
7
|
+
def acts_as_taggable
|
8
|
+
acts_as_taggable_on :tags
|
9
|
+
end
|
403
10
|
|
404
|
-
|
405
|
-
|
406
|
-
Tagging.create!(:tag_id => tag.id, :context => context, :tagger => owner, :taggable => self)
|
407
|
-
end
|
408
|
-
end
|
409
|
-
end
|
410
|
-
end
|
11
|
+
def acts_as_taggable_on(*tag_types)
|
12
|
+
tag_types = tag_types.to_a.flatten.compact.map(&:to_sym)
|
411
13
|
|
412
|
-
|
14
|
+
if taggable?
|
15
|
+
write_inheritable_attribute(:tag_types, (self.tag_types + tag_types).uniq)
|
16
|
+
else
|
17
|
+
if ::ActiveRecord::VERSION::MAJOR < 3
|
18
|
+
include ActsAsTaggableOn::ActiveRecord::Backports
|
413
19
|
end
|
20
|
+
|
21
|
+
write_inheritable_attribute(:tag_types, tag_types)
|
22
|
+
class_inheritable_reader(:tag_types)
|
23
|
+
|
24
|
+
class_eval do
|
25
|
+
has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag
|
26
|
+
has_many :base_tags, :class_name => "Tag", :through => :taggings, :source => :tag
|
414
27
|
|
415
|
-
|
416
|
-
|
417
|
-
instance_variable_set("@#{tag_type.to_s.singularize}_list", nil)
|
28
|
+
def self.taggable?
|
29
|
+
true
|
418
30
|
end
|
419
|
-
|
420
|
-
reload_without_tag_list(*args)
|
421
31
|
end
|
422
|
-
|
423
32
|
end
|
33
|
+
|
34
|
+
include Core
|
35
|
+
include Aggregate
|
36
|
+
include Cache
|
37
|
+
include Ownership
|
38
|
+
include Related
|
424
39
|
end
|
425
40
|
end
|
426
41
|
end
|