acts-as-taggable-on 2.0.0.pre1 → 2.0.0.pre3
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 +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
|