acts-as-taggable-on 1.0.10 → 1.0.11

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ == 2009-12-02
2
+
3
+ * PostgreSQL is now supported (via morgoth)
4
+
1
5
  == 2008-07-17
2
6
 
3
7
  * Can now use a named_scope to find tags!
@@ -0,0 +1,158 @@
1
+ = ActsAsTaggableOn
2
+
3
+ This plugin was originally based on Acts as Taggable on Steroids by Jonathan Viney.
4
+ It has evolved substantially since that point, but all credit goes to him for the
5
+ initial tagging functionality that so many people have used.
6
+
7
+ For instance, in a social network, a user might have tags that are called skills,
8
+ interests, sports, and more. There is no real way to differentiate between tags and
9
+ so an implementation of this type is not possible with acts as taggable on steroids.
10
+
11
+ Enter Acts as Taggable On. Rather than tying functionality to a specific keyword
12
+ (namely "tags"), acts as taggable on allows you to specify an arbitrary number of
13
+ tag "contexts" that can be used locally or in combination in the same way steroids
14
+ was used.
15
+
16
+ == Installation
17
+
18
+ === Plugin
19
+
20
+ Acts As Taggable On is available both as a gem and as a traditional plugin. For the
21
+ traditional plugin you can install like so (Rails 2.1 or later):
22
+
23
+ script/plugin install git://github.com/mbleigh/acts-as-taggable-on.git
24
+
25
+ === GemPlugin
26
+
27
+ Acts As Taggable On is also available as a gem plugin using Rails 2.1's gem dependencies.
28
+ To install the gem, add this to your config/environment.rb:
29
+
30
+ config.gem "acts-as-taggable-on", :source => "http://gemcutter.org"
31
+
32
+ After that, you can run "rake gems:install" to install the gem if you don't already have it.
33
+
34
+ === Post Installation (Rails)
35
+
36
+ 1. script/generate acts_as_taggable_on_migration
37
+ 2. rake db:migrate
38
+
39
+ === Testing
40
+
41
+ Acts As Taggable On uses RSpec for its test coverage. Inside the plugin
42
+ directory, you can run the specs with:
43
+
44
+ rake spec
45
+
46
+ If you already have RSpec on your application, the specs will run while using:
47
+
48
+ rake spec:plugins
49
+
50
+
51
+ == Usage
52
+
53
+ class User < ActiveRecord::Base
54
+ acts_as_taggable_on :tags, :skills, :interests
55
+ end
56
+
57
+ @user = User.new(:name => "Bobby")
58
+ @user.tag_list = "awesome, slick, hefty" # this should be familiar
59
+ @user.skill_list = "joking, clowning, boxing" # but you can do it for any context!
60
+ @user.skill_list # => ["joking","clowning","boxing"] as TagList
61
+ @user.save
62
+
63
+ @user.tags # => [<Tag name:"awesome">,<Tag name:"slick">,<Tag name:"hefty">]
64
+ @user.skills # => [<Tag name:"joking">,<Tag name:"clowning">,<Tag name:"boxing">]
65
+
66
+ # The old way
67
+ User.find_tagged_with("awesome", :on => :tags) # => [@user]
68
+ User.find_tagged_with("awesome", :on => :skills) # => []
69
+
70
+ # The better way (utilizes named_scope)
71
+ User.tagged_with("awesome", :on => :tags) # => [@user]
72
+ User.tagged_with("awesome", :on => :skills) # => []
73
+
74
+ @frankie = User.create(:name => "Frankie", :skill_list => "joking, flying, eating")
75
+ User.skill_counts # => [<Tag name="joking" count=2>,<Tag name="clowning" count=1>...]
76
+ @frankie.skill_counts
77
+
78
+ === Finding Tagged Objects
79
+
80
+ Acts As Taggable On utilizes Rails 2.1's named_scope to create an association
81
+ for tags. This way you can mix and match to filter down your results, and it
82
+ also improves compatibility with the will_paginate gem:
83
+
84
+ class User < ActiveRecord::Base
85
+ acts_as_taggable_on :tags
86
+ named_scope :by_join_date, :order => "created_at DESC"
87
+ end
88
+
89
+ User.tagged_with("awesome").by_date
90
+ User.tagged_with("awesome").by_date.paginate(:page => params[:page], :per_page => 20)
91
+
92
+ === Relationships
93
+
94
+ You can find objects of the same type based on similar tags on certain contexts.
95
+ Also, objects will be returned in descending order based on the total number of
96
+ matched tags.
97
+
98
+ @bobby = User.find_by_name("Bobby")
99
+ @bobby.skill_list # => ["jogging", "diving"]
100
+
101
+ @frankie = User.find_by_name("Frankie")
102
+ @frankie.skill_list # => ["hacking"]
103
+
104
+ @tom = User.find_by_name("Tom")
105
+ @tom.skill_list # => ["hacking", "jogging", "diving"]
106
+
107
+ @tom.find_related_skills # => [<User name="Bobby">,<User name="Frankie">]
108
+ @bobby.find_related_skills # => [<User name="Tom">]
109
+ @frankie.find_related_skills # => [<User name="Tom">]
110
+
111
+ === Dynamic Tag Contexts
112
+
113
+ In addition to the generated tag contexts in the definition, it is also possible
114
+ to allow for dynamic tag contexts (this could be user generated tag contexts!)
115
+
116
+ @user = User.new(:name => "Bobby")
117
+ @user.set_tag_list_on(:customs, "same, as, tag, list")
118
+ @user.tag_list_on(:customs) # => ["same","as","tag","list"]
119
+ @user.save
120
+ @user.tags_on(:customs) # => [<Tag name='same'>,...]
121
+ @user.tag_counts_on(:customs)
122
+ User.find_tagged_with("same", :on => :customs) # => [@user]
123
+
124
+ === Tag Ownership
125
+
126
+ Tags can have owners:
127
+
128
+ class User < ActiveRecord::Base
129
+ acts_as_tagger
130
+ end
131
+
132
+ class Photo < ActiveRecord::Base
133
+ acts_as_taggable_on :locations
134
+ end
135
+
136
+ @some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations)
137
+ @some_user.owned_taggings
138
+ @some_user.owned_tags
139
+ @some_photo.locations_from(@some_user)
140
+
141
+ == Contributors
142
+
143
+ * TomEric (i76) - Maintainer
144
+ * Michael Bleigh - Original Author
145
+ * Brendan Lim - Related Objects
146
+ * Pradeep Elankumaran - Taggers
147
+ * Sinclair Bain - Patch King
148
+
149
+ == Patch Contributors
150
+
151
+ * tristanzdunn - Related objects of other classes
152
+ * azabaj - Fixed migrate down
153
+ * Peter Cooper - named_scope fix
154
+ * slainer68 - STI fix
155
+ * harrylove - migration instructions and fix-ups
156
+ * lawrencepit - cached tag work
157
+
158
+ Copyright (c) 2007-2009 Michael Bleigh (http://mbleigh.com/) and Intridea Inc. (http://intridea.com/), released under the MIT license
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.10
1
+ 1.0.11
@@ -4,16 +4,16 @@ module ActiveRecord
4
4
  def self.included(base)
5
5
  base.extend(ClassMethods)
6
6
  end
7
-
7
+
8
8
  module ClassMethods
9
9
  def taggable?
10
10
  false
11
11
  end
12
-
12
+
13
13
  def acts_as_taggable
14
14
  acts_as_taggable_on :tags
15
15
  end
16
-
16
+
17
17
  def acts_as_taggable_on(*args)
18
18
  args.flatten! if args
19
19
  args.compact! if args
@@ -22,40 +22,40 @@ module ActiveRecord
22
22
  # use aliased_join_table_name for context condition so that sphix can join multiple
23
23
  # tag references from same model without getting an ambiguous column error
24
24
  self.class_eval do
25
- has_many "#{tag_type.singularize}_taggings".to_sym, :as => :taggable, :dependent => :destroy,
25
+ has_many "#{tag_type.singularize}_taggings".to_sym, :as => :taggable, :dependent => :destroy,
26
26
  :include => :tag, :conditions => ['#{aliased_join_table_name rescue "taggings"}.context = ?',tag_type], :class_name => "Tagging"
27
27
  has_many "#{tag_type}".to_sym, :through => "#{tag_type.singularize}_taggings".to_sym, :source => :tag
28
28
  end
29
-
29
+
30
30
  self.class_eval <<-RUBY
31
31
  def self.taggable?
32
32
  true
33
33
  end
34
-
34
+
35
35
  def self.caching_#{tag_type.singularize}_list?
36
36
  caching_tag_list_on?("#{tag_type}")
37
37
  end
38
-
38
+
39
39
  def self.#{tag_type.singularize}_counts(options={})
40
40
  tag_counts_on('#{tag_type}',options)
41
41
  end
42
-
42
+
43
43
  def #{tag_type.singularize}_list
44
44
  tag_list_on('#{tag_type}')
45
45
  end
46
-
46
+
47
47
  def #{tag_type.singularize}_list=(new_tags)
48
48
  set_tag_list_on('#{tag_type}',new_tags)
49
49
  end
50
-
50
+
51
51
  def #{tag_type.singularize}_counts(options = {})
52
52
  tag_counts_on('#{tag_type}',options)
53
53
  end
54
-
54
+
55
55
  def #{tag_type}_from(owner)
56
56
  tag_list_on('#{tag_type}', owner)
57
57
  end
58
-
58
+
59
59
  def find_related_#{tag_type}(options = {})
60
60
  related_tags_for('#{tag_type}', self.class, options)
61
61
  end
@@ -64,53 +64,53 @@ module ActiveRecord
64
64
  def find_related_#{tag_type}_for(klass, options = {})
65
65
  related_tags_for('#{tag_type}', klass, options)
66
66
  end
67
-
67
+
68
68
  def top_#{tag_type}(limit = 10)
69
69
  tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
70
70
  end
71
-
71
+
72
72
  def self.top_#{tag_type}(limit = 10)
73
73
  tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
74
74
  end
75
75
  RUBY
76
- end
77
-
76
+ end
77
+
78
78
  if respond_to?(:tag_types)
79
79
  write_inheritable_attribute( :tag_types, (tag_types + args).uniq )
80
80
  else
81
81
  self.class_eval do
82
82
  write_inheritable_attribute(:tag_types, args.uniq)
83
83
  class_inheritable_reader :tag_types
84
-
84
+
85
85
  has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag
86
86
  has_many :base_tags, :class_name => "Tag", :through => :taggings, :source => :tag
87
-
87
+
88
88
  attr_writer :custom_contexts
89
-
89
+
90
90
  before_save :save_cached_tag_list
91
91
  after_save :save_tags
92
-
92
+
93
93
  if respond_to?(:named_scope)
94
94
  named_scope :tagged_with, lambda{ |*args|
95
95
  find_options_for_find_tagged_with(*args)
96
96
  }
97
97
  end
98
98
  end
99
-
99
+
100
100
  include ActiveRecord::Acts::TaggableOn::InstanceMethods
101
- extend ActiveRecord::Acts::TaggableOn::SingletonMethods
101
+ extend ActiveRecord::Acts::TaggableOn::SingletonMethods
102
102
  alias_method_chain :reload, :tag_list
103
103
  end
104
104
  end
105
-
105
+
106
106
  def is_taggable?
107
107
  false
108
108
  end
109
109
  end
110
-
110
+
111
111
  module SingletonMethods
112
112
  # Pass either a tag string, or an array of strings or tags
113
- #
113
+ #
114
114
  # Options:
115
115
  # :exclude - Find models that are not tagged with the given tags
116
116
  # :match_all - Find models that match all of the given tags, not just one
@@ -120,73 +120,73 @@ module ActiveRecord
120
120
  options = find_options_for_find_tagged_with(*args)
121
121
  options.blank? ? [] : find(:all,options)
122
122
  end
123
-
123
+
124
124
  def caching_tag_list_on?(context)
125
125
  column_names.include?("cached_#{context.to_s.singularize}_list")
126
- end
127
-
126
+ end
127
+
128
128
  def tag_counts_on(context, options = {})
129
129
  Tag.find(:all, find_options_for_tag_counts(options.merge({:on => context.to_s})))
130
- end
131
-
130
+ end
131
+
132
132
  def all_tag_counts(options = {})
133
133
  Tag.find(:all, find_options_for_tag_counts(options))
134
134
  end
135
-
135
+
136
136
  def find_options_for_find_tagged_with(tags, options = {})
137
137
  tags = TagList.from(tags)
138
-
138
+
139
139
  return {} if tags.empty?
140
-
140
+
141
141
  joins = []
142
142
  conditions = []
143
-
143
+
144
144
  context = options.delete(:on)
145
145
 
146
-
146
+
147
147
  if options.delete(:exclude)
148
148
  tags_conditions = tags.map { |t| sanitize_sql(["#{Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
149
149
  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)})"
150
-
151
- else
150
+
151
+ else
152
152
  tags.each do |tag|
153
153
  safe_tag = tag.gsub(/[^a-zA-Z0-9]/, '')
154
154
  prefix = "#{safe_tag}_#{rand(1024)}"
155
-
155
+
156
156
  taggings_alias = "#{table_name}_taggings_#{prefix}"
157
157
  tags_alias = "#{table_name}_tags_#{prefix}"
158
-
158
+
159
159
  tagging_join = "JOIN #{Tagging.table_name} #{taggings_alias}" +
160
160
  " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
161
161
  " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}"
162
162
  tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
163
-
163
+
164
164
  tag_join = "JOIN #{Tag.table_name} #{tags_alias}" +
165
165
  " ON #{tags_alias}.id = #{taggings_alias}.tag_id" +
166
166
  " AND " + sanitize_sql(["#{tags_alias}.name like ?", tag])
167
-
167
+
168
168
  joins << tagging_join
169
169
  joins << tag_join
170
- end
170
+ end
171
171
  end
172
-
172
+
173
173
  taggings_alias, tags_alias = "#{table_name}_taggings_group", "#{table_name}_tags_group"
174
174
 
175
175
  if options.delete(:match_all)
176
176
  joins << "LEFT OUTER JOIN #{Tagging.table_name} #{taggings_alias}" +
177
177
  " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
178
178
  " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}"
179
-
180
- group = "#{table_name}.#{primary_key} HAVING COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
179
+
180
+ group = "#{column_names_for_tagging_group} HAVING COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
181
181
  end
182
-
182
+
183
183
  { :joins => joins.join(" "),
184
184
  :group => group,
185
185
  :conditions => conditions.join(" AND ") }.update(options)
186
- end
187
-
186
+ end
187
+
188
188
  # Calculate the tag counts for all tags.
189
- #
189
+ #
190
190
  # Options:
191
191
  # :start_at - Restrict the tags to those created after a certain time
192
192
  # :end_at - Restrict the tags to those created before a certain time
@@ -198,7 +198,7 @@ module ActiveRecord
198
198
  # :on - Scope the find to only include a certain context
199
199
  def find_options_for_tag_counts(options = {})
200
200
  options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id
201
-
201
+
202
202
  scope = scope(:find)
203
203
  start_at = sanitize_sql(["#{Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
204
204
  end_at = sanitize_sql(["#{Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
@@ -206,7 +206,7 @@ module ActiveRecord
206
206
  taggable_type = sanitize_sql(["#{Tagging.table_name}.taggable_type = ?", base_class.name])
207
207
  taggable_id = sanitize_sql(["#{Tagging.table_name}.taggable_id = ?", options.delete(:id)]) if options[:id]
208
208
  options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
209
-
209
+
210
210
  conditions = [
211
211
  taggable_type,
212
212
  taggable_id,
@@ -220,65 +220,73 @@ module ActiveRecord
220
220
 
221
221
  joins = ["LEFT OUTER JOIN #{Tagging.table_name} ON #{Tag.table_name}.id = #{Tagging.table_name}.tag_id"]
222
222
  joins << sanitize_sql(["AND #{Tagging.table_name}.context = ?",options.delete(:on).to_s]) unless options[:on].nil?
223
-
223
+
224
224
  joins << " INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id"
225
225
  unless self.descends_from_active_record?
226
226
  # Current model is STI descendant, so add type checking to the join condition
227
227
  joins << " AND #{table_name}.#{self.inheritance_column} = '#{self.name}'"
228
228
  end
229
-
229
+
230
230
  joins << scope[:joins] if scope && scope[:joins]
231
231
 
232
232
  at_least = sanitize_sql(['COUNT(*) >= ?', options.delete(:at_least)]) if options[:at_least]
233
233
  at_most = sanitize_sql(['COUNT(*) <= ?', options.delete(:at_most)]) if options[:at_most]
234
234
  having = [at_least, at_most].compact.join(' AND ')
235
- group_by = "#{Tag.table_name}.id HAVING COUNT(*) > 0"
235
+ group_by = "#{column_names_for_tag_group} HAVING COUNT(*) > 0"
236
236
  group_by << " AND #{having}" unless having.blank?
237
237
 
238
- { :select => "#{Tag.table_name}.*, COUNT(*) AS count",
238
+ { :select => "#{Tag.table_name}.*, COUNT(*) AS count",
239
239
  :joins => joins.join(" "),
240
240
  :conditions => conditions,
241
241
  :group => group_by,
242
242
  :limit => options[:limit],
243
243
  :order => options[:order]
244
244
  }
245
- end
246
-
245
+ end
246
+
247
247
  def is_taggable?
248
248
  true
249
- end
249
+ end
250
+
251
+ def column_names_for_tag_group
252
+ Tag.column_names.map { |column| "#{Tag.table_name}.#{column}" }.join(", ")
253
+ end
254
+
255
+ def column_names_for_tagging_group
256
+ column_names.map { |column| "#{table_name}.#{column}" }.join(", ")
257
+ end
250
258
  end
251
-
259
+
252
260
  module InstanceMethods
253
-
261
+
254
262
  def tag_types
255
263
  self.class.tag_types
256
264
  end
257
-
265
+
258
266
  def custom_contexts
259
267
  @custom_contexts ||= []
260
268
  end
261
-
269
+
262
270
  def is_taggable?
263
271
  self.class.is_taggable?
264
272
  end
265
-
273
+
266
274
  def add_custom_context(value)
267
275
  custom_contexts << value.to_s unless custom_contexts.include?(value.to_s) or self.class.tag_types.map(&:to_s).include?(value.to_s)
268
276
  end
269
-
277
+
270
278
  def tag_list_on(context, owner=nil)
271
279
  var_name = context.to_s.singularize + "_list"
272
280
  add_custom_context(context)
273
281
  return instance_variable_get("@#{var_name}") unless instance_variable_get("@#{var_name}").nil?
274
-
282
+
275
283
  if !owner && self.class.caching_tag_list_on?(context) and !(cached_value = cached_tag_list_on(context)).nil?
276
284
  instance_variable_set("@#{var_name}", TagList.from(self["cached_#{var_name}"]))
277
285
  else
278
286
  instance_variable_set("@#{var_name}", TagList.new(*tags_on(context, owner).map(&:name)))
279
287
  end
280
288
  end
281
-
289
+
282
290
  def tags_on(context, owner=nil)
283
291
  if owner
284
292
  opts = {:conditions => ["#{Tagging.table_name}.context = ? AND #{Tagging.table_name}.tagger_id = ? AND #{Tagging.table_name}.tagger_type = ?",
@@ -288,16 +296,16 @@ module ActiveRecord
288
296
  end
289
297
  base_tags.find(:all, opts)
290
298
  end
291
-
299
+
292
300
  def cached_tag_list_on(context)
293
301
  self["cached_#{context.to_s.singularize}_list"]
294
302
  end
295
-
303
+
296
304
  def set_tag_list_on(context,new_list, tagger=nil)
297
305
  instance_variable_set("@#{context.to_s.singularize}_list", TagList.from_owner(tagger, new_list))
298
306
  add_custom_context(context)
299
307
  end
300
-
308
+
301
309
  def tag_counts_on(context, options={})
302
310
  self.class.tag_counts_on(context, options.merge(:id => self.id))
303
311
  end
@@ -310,17 +318,17 @@ module ActiveRecord
310
318
 
311
319
  def related_search_options(context, klass, options = {})
312
320
  tags_to_find = self.tags_on(context).collect { |t| t.name }
313
-
321
+
314
322
  exclude_self = "#{klass.table_name}.id != #{self.id} AND" if self.class == klass
315
323
 
316
- { :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
324
+ { :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
317
325
  :from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
318
326
  :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],
319
327
  :group => "#{klass.table_name}.id",
320
328
  :order => "count DESC"
321
329
  }.update(options)
322
330
  end
323
-
331
+
324
332
  def save_cached_tag_list
325
333
  self.class.tag_types.map(&:to_s).each do |tag_type|
326
334
  if self.class.send("caching_#{tag_type.singularize}_list?")
@@ -328,32 +336,32 @@ module ActiveRecord
328
336
  end
329
337
  end
330
338
  end
331
-
339
+
332
340
  def save_tags
333
341
  (custom_contexts + self.class.tag_types.map(&:to_s)).each do |tag_type|
334
342
  next unless instance_variable_get("@#{tag_type.singularize}_list")
335
343
  owner = instance_variable_get("@#{tag_type.singularize}_list").owner
336
344
  new_tag_names = instance_variable_get("@#{tag_type.singularize}_list") - tags_on(tag_type).map(&:name)
337
345
  old_tags = tags_on(tag_type, owner).reject { |tag| instance_variable_get("@#{tag_type.singularize}_list").include?(tag.name) }
338
-
346
+
339
347
  self.class.transaction do
340
348
  base_tags.delete(*old_tags) if old_tags.any?
341
349
  new_tag_names.each do |new_tag_name|
342
350
  new_tag = Tag.find_or_create_with_like_by_name(new_tag_name)
343
- Tagging.create(:tag_id => new_tag.id, :context => tag_type,
351
+ Tagging.create(:tag_id => new_tag.id, :context => tag_type,
344
352
  :taggable => self, :tagger => owner)
345
353
  end
346
354
  end
347
355
  end
348
-
356
+
349
357
  true
350
358
  end
351
-
359
+
352
360
  def reload_with_tag_list(*args)
353
361
  self.class.tag_types.each do |tag_type|
354
362
  self.instance_variable_set("@#{tag_type.to_s.singularize}_list", nil)
355
363
  end
356
-
364
+
357
365
  reload_without_tag_list(*args)
358
366
  end
359
367
  end
@@ -4,61 +4,69 @@ describe "Acts As Taggable On" do
4
4
  it "should provide a class method 'taggable?' that is false for untaggable models" do
5
5
  UntaggableModel.should_not be_taggable
6
6
  end
7
-
7
+
8
8
  describe "Taggable Method Generation" do
9
9
  before(:each) do
10
10
  [TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
11
11
  @taggable = TaggableModel.new(:name => "Bob Jones")
12
12
  end
13
-
13
+
14
14
  it "should respond 'true' to taggable?" do
15
15
  @taggable.class.should be_taggable
16
16
  end
17
-
17
+
18
18
  it "should create a class attribute for tag types" do
19
19
  @taggable.class.should respond_to(:tag_types)
20
20
  end
21
-
21
+
22
22
  it "should generate an association for each tag type" do
23
23
  @taggable.should respond_to(:tags, :skills, :languages)
24
24
  end
25
-
25
+
26
26
  it "should generate a cached column checker for each tag type" do
27
27
  TaggableModel.should respond_to(:caching_tag_list?, :caching_skill_list?, :caching_language_list?)
28
28
  end
29
-
29
+
30
30
  it "should add tagged_with and tag_counts to singleton" do
31
31
  TaggableModel.should respond_to(:find_tagged_with, :tag_counts)
32
32
  end
33
-
33
+
34
34
  it "should add saving of tag lists and cached tag lists to the instance" do
35
35
  @taggable.should respond_to(:save_cached_tag_list)
36
36
  @taggable.should respond_to(:save_tags)
37
37
  end
38
-
38
+
39
39
  it "should generate a tag_list accessor/setter for each tag type" do
40
40
  @taggable.should respond_to(:tag_list, :skill_list, :language_list)
41
41
  @taggable.should respond_to(:tag_list=, :skill_list=, :language_list=)
42
42
  end
43
+
44
+ it "should return all column names joined for Tag GROUP clause" do
45
+ TaggableModel.column_names_for_tag_group.should == "tags.id, tags.name"
46
+ end
47
+
48
+ it "should return all column names joined for TaggableModel GROUP clause" do
49
+ TaggableModel.column_names_for_tagging_group.should == "taggable_models.id, taggable_models.name, taggable_models.type"
50
+ end
43
51
  end
44
-
52
+
45
53
  describe "Single Table Inheritance" do
46
54
  before do
47
55
  @taggable = TaggableModel.new(:name => "taggable")
48
56
  @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
49
57
  @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
50
58
  end
51
-
59
+
52
60
  it "should pass on tag contexts to STI-inherited models" do
53
61
  @inherited_same.should respond_to(:tag_list, :skill_list, :language_list)
54
62
  @inherited_different.should respond_to(:tag_list, :skill_list, :language_list)
55
63
  end
56
-
64
+
57
65
  it "should have tag contexts added in altered STI models" do
58
66
  @inherited_different.should respond_to(:part_list)
59
67
  end
60
68
  end
61
-
69
+
62
70
  describe "Reloading" do
63
71
  it "should save a model instantiated by Model.find" do
64
72
  taggable = TaggableModel.create!(:name => "Taggable")
@@ -66,7 +74,7 @@ describe "Acts As Taggable On" do
66
74
  found_taggable.save
67
75
  end
68
76
  end
69
-
77
+
70
78
  describe "Related Objects" do
71
79
  it "should find related objects based on tag names on context" do
72
80
  taggable1 = TaggableModel.create!(:name => "Taggable 1")
@@ -75,13 +83,13 @@ describe "Acts As Taggable On" do
75
83
 
76
84
  taggable1.tag_list = "one, two"
77
85
  taggable1.save
78
-
86
+
79
87
  taggable2.tag_list = "three, four"
80
88
  taggable2.save
81
-
89
+
82
90
  taggable3.tag_list = "one, four"
83
91
  taggable3.save
84
-
92
+
85
93
  taggable1.find_related_tags.should include(taggable3)
86
94
  taggable1.find_related_tags.should_not include(taggable2)
87
95
  end
@@ -93,32 +101,32 @@ describe "Acts As Taggable On" do
93
101
 
94
102
  taggable1.tag_list = "one, two"
95
103
  taggable1.save
96
-
104
+
97
105
  taggable2.tag_list = "three, four"
98
106
  taggable2.save
99
-
107
+
100
108
  taggable3.tag_list = "one, four"
101
109
  taggable3.save
102
110
 
103
111
  taggable1.find_related_tags_for(OtherTaggableModel).should include(taggable3)
104
112
  taggable1.find_related_tags_for(OtherTaggableModel).should_not include(taggable2)
105
113
  end
106
-
114
+
107
115
  it "should not include the object itself in the list of related objects" do
108
116
  taggable1 = TaggableModel.create!(:name => "Taggable 1")
109
117
  taggable2 = TaggableModel.create!(:name => "Taggable 2")
110
118
 
111
119
  taggable1.tag_list = "one"
112
120
  taggable1.save
113
-
121
+
114
122
  taggable2.tag_list = "one, two"
115
123
  taggable2.save
116
-
124
+
117
125
  taggable1.find_related_tags.should include(taggable2)
118
126
  taggable1.find_related_tags.should_not include(taggable1)
119
127
  end
120
128
  end
121
-
129
+
122
130
  describe 'Tagging Contexts' do
123
131
  before(:all) do
124
132
  class Array
@@ -129,7 +137,7 @@ describe "Acts As Taggable On" do
129
137
  end
130
138
  end
131
139
  end
132
-
140
+
133
141
  it 'should eliminate duplicate tagging contexts ' do
134
142
  TaggableModel.acts_as_taggable_on(:skills, :skills)
135
143
  TaggableModel.tag_types.freq[:skills].should_not == 3
@@ -161,5 +169,5 @@ describe "Acts As Taggable On" do
161
169
  class Array; remove_method :freq; end
162
170
  end
163
171
  end
164
-
172
+
165
173
  end
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: 1.0.10
4
+ version: 1.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-17 00:00:00 +01:00
12
+ date: 2009-12-02 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -20,11 +20,11 @@ executables: []
20
20
  extensions: []
21
21
 
22
22
  extra_rdoc_files:
23
- - README
23
+ - README.rdoc
24
24
  files:
25
25
  - CHANGELOG
26
26
  - MIT-LICENSE
27
- - README
27
+ - README.rdoc
28
28
  - Rakefile
29
29
  - VERSION
30
30
  - lib/acts-as-taggable-on.rb
data/README DELETED
@@ -1,194 +0,0 @@
1
- ActsAsTaggableOn
2
- ================
3
-
4
- This plugin was originally based on Acts as Taggable on Steroids by Jonathan Viney.
5
- It has evolved substantially since that point, but all credit goes to him for the
6
- initial tagging functionality that so many people have used.
7
-
8
- For instance, in a social network, a user might have tags that are called skills,
9
- interests, sports, and more. There is no real way to differentiate between tags and
10
- so an implementation of this type is not possible with acts as taggable on steroids.
11
-
12
- Enter Acts as Taggable On. Rather than tying functionality to a specific keyword
13
- (namely "tags"), acts as taggable on allows you to specify an arbitrary number of
14
- tag "contexts" that can be used locally or in combination in the same way steroids
15
- was used.
16
-
17
- Installation
18
- ============
19
-
20
- Plugin
21
- ------
22
-
23
- Acts As Taggable On is available both as a gem and as a traditional plugin. For the
24
- traditional plugin you can install like so (Rails 2.1 or later):
25
-
26
- script/plugin install git://github.com/mbleigh/acts-as-taggable-on.git
27
-
28
- For earlier versions:
29
-
30
- git clone git://github.com/mbleigh/acts-as-taggable-on.git vendor/plugins/acts-as-taggable-on
31
-
32
- GemPlugin
33
- ---------
34
-
35
- Acts As Taggable On is also available as a gem plugin using Rails 2.1's gem dependencies.
36
- To install the gem, add this to your config/environment.rb:
37
-
38
- config.gem "acts-as-taggable-on", :source => "http://gemcutter.org"
39
-
40
- After that, you can run "rake gems:install" to install the gem if you don't already have it.
41
-
42
- ** NOTE **
43
- Some issues have been experienced with "rake gems:install". If that doesn't work to install the gem,
44
- try just installing it as a normal gem:
45
-
46
- gem install acts-as-taggable-on --source http://gemcutter.org
47
-
48
- Post Installation (Rails)
49
- -------------------------
50
- 1. script/generate acts_as_taggable_on_migration
51
- 2. rake db:migrate
52
-
53
- Testing
54
- =======
55
-
56
- Acts As Taggable On uses RSpec for its test coverage. Inside the plugin
57
- directory, you can run the specs with:
58
-
59
- rake spec
60
-
61
-
62
- If you already have RSpec on your application, the specs will run while using:
63
-
64
- rake spec:plugins
65
-
66
-
67
- Example
68
- =======
69
-
70
- class User < ActiveRecord::Base
71
- acts_as_taggable_on :tags, :skills, :interests
72
- end
73
-
74
- @user = User.new(:name => "Bobby")
75
- @user.tag_list = "awesome, slick, hefty" # this should be familiar
76
- @user.skill_list = "joking, clowning, boxing" # but you can do it for any context!
77
- @user.skill_list # => ["joking","clowning","boxing"] as TagList
78
- @user.save
79
-
80
- @user.tags # => [<Tag name:"awesome">,<Tag name:"slick">,<Tag name:"hefty">]
81
- @user.skills # => [<Tag name:"joking">,<Tag name:"clowning">,<Tag name:"boxing">]
82
-
83
- # The old way
84
- User.find_tagged_with("awesome", :on => :tags) # => [@user]
85
- User.find_tagged_with("awesome", :on => :skills) # => []
86
-
87
- # The better way (utilizes named_scope)
88
- User.tagged_with("awesome", :on => :tags) # => [@user]
89
- User.tagged_with("awesome", :on => :skills) # => []
90
-
91
- @frankie = User.create(:name => "Frankie", :skill_list => "joking, flying, eating")
92
- User.skill_counts # => [<Tag name="joking" count=2>,<Tag name="clowning" count=1>...]
93
- @frankie.skill_counts
94
-
95
- Finding Tagged Objects
96
- ======================
97
-
98
- Acts As Taggable On utilizes Rails 2.1's named_scope to create an association
99
- for tags. This way you can mix and match to filter down your results, and it
100
- also improves compatibility with the will_paginate gem:
101
-
102
- class User < ActiveRecord::Base
103
- acts_as_taggable_on :tags
104
- named_scope :by_join_date, :order => "created_at DESC"
105
- end
106
-
107
- User.tagged_with("awesome").by_date
108
- User.tagged_with("awesome").by_date.paginate(:page => params[:page], :per_page => 20)
109
-
110
- Relationships
111
- =============
112
-
113
- You can find objects of the same type based on similar tags on certain contexts.
114
- Also, objects will be returned in descending order based on the total number of
115
- matched tags.
116
-
117
- @bobby = User.find_by_name("Bobby")
118
- @bobby.skill_list # => ["jogging", "diving"]
119
-
120
- @frankie = User.find_by_name("Frankie")
121
- @frankie.skill_list # => ["hacking"]
122
-
123
- @tom = User.find_by_name("Tom")
124
- @tom.skill_list # => ["hacking", "jogging", "diving"]
125
-
126
- @tom.find_related_skills # => [<User name="Bobby">,<User name="Frankie">]
127
- @bobby.find_related_skills # => [<User name="Tom">]
128
- @frankie.find_related_skills # => [<User name="Tom">]
129
-
130
-
131
- Dynamic Tag Contexts
132
- ====================
133
-
134
- In addition to the generated tag contexts in the definition, it is also possible
135
- to allow for dynamic tag contexts (this could be user generated tag contexts!)
136
-
137
- @user = User.new(:name => "Bobby")
138
- @user.set_tag_list_on(:customs, "same, as, tag, list")
139
- @user.tag_list_on(:customs) # => ["same","as","tag","list"]
140
- @user.save
141
- @user.tags_on(:customs) # => [<Tag name='same'>,...]
142
- @user.tag_counts_on(:customs)
143
- User.find_tagged_with("same", :on => :customs) # => [@user]
144
-
145
- Tag Ownership
146
- =============
147
-
148
- Tags can have owners:
149
-
150
- class User < ActiveRecord::Base
151
- acts_as_tagger
152
- end
153
-
154
- class Photo < ActiveRecord::Base
155
- acts_as_taggable_on :locations
156
- end
157
-
158
- @some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations)
159
- @some_user.owned_taggings
160
- @some_user.owned_tags
161
- @some_photo.locations_from(@some_user)
162
-
163
- Caveats, Uncharted Waters
164
- =========================
165
-
166
- This plugin is still under active development. Tag caching has not
167
- been thoroughly (or even casually) tested and may not work as expected.
168
-
169
- Contributors
170
- ============
171
-
172
- * Michael Bleigh - Original Author
173
- * Brendan Lim - Related Objects
174
- * Pradeep Elankumaran - Taggers
175
- * Sinclair Bain - Patch King
176
-
177
- Patch Contributors
178
- ------------------
179
-
180
- * tristanzdunn - Related objects of other classes
181
- * azabaj - Fixed migrate down
182
- * Peter Cooper - named_scope fix
183
- * slainer68 - STI fix
184
- * harrylove - migration instructions and fix-ups
185
- * lawrencepit - cached tag work
186
-
187
- Resources
188
- =========
189
-
190
- * Acts As Community - http://www.actsascommunity.com/projects/acts-as-taggable-on
191
- * GitHub - http://github.com/mbleigh/acts-as-taggable-on
192
- * Lighthouse - http://mbleigh.lighthouseapp.com/projects/10116-acts-as-taggable-on
193
-
194
- Copyright (c) 2007 Michael Bleigh (http://mbleigh.com/) and Intridea Inc. (http://intridea.com/), released under the MIT license