acts-as-taggable-on 1.0.10 → 1.0.11

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/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