acts-as-taggable-on 1.0.12 → 1.1.1
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/README.rdoc +41 -0
- data/Rakefile +7 -1
- data/VERSION +1 -1
- data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +7 -0
- data/generators/acts_as_taggable_on_migration/templates/migration.rb +29 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +132 -59
- data/lib/acts_as_taggable_on/tag.rb +30 -3
- data/lib/acts_as_taggable_on/tag_list.rb +6 -11
- data/lib/acts_as_taggable_on/tagging.rb +8 -0
- data/lib/acts_as_taggable_on/tags_helper.rb +2 -0
- data/rails/init.rb +1 -2
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +55 -13
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +41 -5
- data/spec/acts_as_taggable_on/tag_list_spec.rb +18 -0
- data/spec/acts_as_taggable_on/tag_spec.rb +73 -0
- data/spec/acts_as_taggable_on/taggable_spec.rb +83 -36
- data/spec/acts_as_taggable_on/tagger_spec.rb +17 -2
- data/spec/acts_as_taggable_on/tagging_spec.rb +18 -0
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +30 -0
- data/spec/spec_helper.rb +17 -3
- metadata +6 -2
data/README.rdoc
CHANGED
|
@@ -137,6 +137,46 @@ Tags can have owners:
|
|
|
137
137
|
@some_user.owned_taggings
|
|
138
138
|
@some_user.owned_tags
|
|
139
139
|
@some_photo.locations_from(@some_user)
|
|
140
|
+
|
|
141
|
+
=== Tag cloud calculations
|
|
142
|
+
|
|
143
|
+
To construct tag clouds, the frequency of each tag needs to be calculated.
|
|
144
|
+
Because we specified +acts_as_taggable_on+ on the <tt>User</tt> class, we can
|
|
145
|
+
get a calculation of all the tag counts by using <tt>User.tag_counts_on(:customs)</tt>. But what if we wanted a tag count for
|
|
146
|
+
an single user's posts? To achieve this we call tag_counts on the association:
|
|
147
|
+
|
|
148
|
+
User.find(:first).posts.tag_counts_on(:tags)
|
|
149
|
+
|
|
150
|
+
A helper is included to assist with generating tag clouds.
|
|
151
|
+
|
|
152
|
+
Here is an example that generates a tag cloud.
|
|
153
|
+
|
|
154
|
+
Helper:
|
|
155
|
+
|
|
156
|
+
module PostsHelper
|
|
157
|
+
include TagsHelper
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
Controller:
|
|
161
|
+
|
|
162
|
+
class PostController < ApplicationController
|
|
163
|
+
def tag_cloud
|
|
164
|
+
@tags = Post.tag_counts_on(:tags)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
View:
|
|
169
|
+
|
|
170
|
+
<% tag_cloud(@tags, %w(css1 css2 css3 css4)) do |tag, css_class| %>
|
|
171
|
+
<%= link_to tag.name, { :action => :tag, :id => tag.name }, :class => css_class %>
|
|
172
|
+
<% end %>
|
|
173
|
+
|
|
174
|
+
CSS:
|
|
175
|
+
|
|
176
|
+
.css1 { font-size: 1.0em; }
|
|
177
|
+
.css2 { font-size: 1.2em; }
|
|
178
|
+
.css3 { font-size: 1.4em; }
|
|
179
|
+
.css4 { font-size: 1.6em; }
|
|
140
180
|
|
|
141
181
|
== Contributors
|
|
142
182
|
|
|
@@ -154,5 +194,6 @@ Tags can have owners:
|
|
|
154
194
|
* slainer68 - STI fix
|
|
155
195
|
* harrylove - migration instructions and fix-ups
|
|
156
196
|
* lawrencepit - cached tag work
|
|
197
|
+
* sobrinho - fixed tag_cloud helper
|
|
157
198
|
|
|
158
199
|
Copyright (c) 2007-2009 Michael Bleigh (http://mbleigh.com/) and Intridea Inc. (http://intridea.com/), released under the MIT license
|
data/Rakefile
CHANGED
|
@@ -9,7 +9,7 @@ begin
|
|
|
9
9
|
gemspec.email = "michael@intridea.com"
|
|
10
10
|
gemspec.homepage = "http://github.com/mbleigh/acts-as-taggable-on"
|
|
11
11
|
gemspec.authors = ["Michael Bleigh"]
|
|
12
|
-
gemspec.files = FileList["[A-Z]*", "{lib,spec,rails}/**/*"] - FileList["**/*.log"]
|
|
12
|
+
gemspec.files = FileList["[A-Z]*", "{generators,lib,spec,rails}/**/*"] - FileList["**/*.log"]
|
|
13
13
|
end
|
|
14
14
|
Jeweler::GemcutterTasks.new
|
|
15
15
|
rescue LoadError
|
|
@@ -21,3 +21,9 @@ task :default => :spec
|
|
|
21
21
|
Spec::Rake::SpecTask.new do |t|
|
|
22
22
|
t.spec_files = FileList["spec/**/*_spec.rb"]
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
Spec::Rake::SpecTask.new('rcov') do |t|
|
|
26
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
|
27
|
+
t.rcov = true
|
|
28
|
+
t.rcov_opts = ['--exclude', 'spec']
|
|
29
|
+
end
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.1.1
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class ActsAsTaggableOnMigration < ActiveRecord::Migration
|
|
2
|
+
def self.up
|
|
3
|
+
create_table :tags do |t|
|
|
4
|
+
t.column :name, :string
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
create_table :taggings do |t|
|
|
8
|
+
t.column :tag_id, :integer
|
|
9
|
+
t.column :taggable_id, :integer
|
|
10
|
+
t.column :tagger_id, :integer
|
|
11
|
+
t.column :tagger_type, :string
|
|
12
|
+
|
|
13
|
+
# You should make sure that the column created is
|
|
14
|
+
# long enough to store the required class names.
|
|
15
|
+
t.column :taggable_type, :string
|
|
16
|
+
t.column :context, :string
|
|
17
|
+
|
|
18
|
+
t.column :created_at, :datetime
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
add_index :taggings, :tag_id
|
|
22
|
+
add_index :taggings, [:taggable_id, :taggable_type, :context]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.down
|
|
26
|
+
drop_table :taggings
|
|
27
|
+
drop_table :tags
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -19,15 +19,15 @@ module ActiveRecord
|
|
|
19
19
|
args.compact! if args
|
|
20
20
|
for tag_type in args
|
|
21
21
|
tag_type = tag_type.to_s
|
|
22
|
-
# use aliased_join_table_name for context condition so that
|
|
22
|
+
# use aliased_join_table_name for context condition so that sphinx can join multiple
|
|
23
23
|
# tag references from same model without getting an ambiguous column error
|
|
24
|
-
|
|
24
|
+
class_eval do
|
|
25
25
|
has_many "#{tag_type.singularize}_taggings".to_sym, :as => :taggable, :dependent => :destroy,
|
|
26
|
-
:include => :tag, :conditions => ['#{aliased_join_table_name rescue
|
|
26
|
+
:include => :tag, :conditions => ['#{aliased_join_table_name || Tagging.table_name rescue Tagging.table_name}.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
|
+
class_eval <<-RUBY
|
|
31
31
|
def self.taggable?
|
|
32
32
|
true
|
|
33
33
|
end
|
|
@@ -65,6 +65,14 @@ module ActiveRecord
|
|
|
65
65
|
related_tags_for('#{tag_type}', klass, options)
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
def find_matching_contexts(search_context, result_context, options = {})
|
|
69
|
+
matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def find_matching_contexts_for(klass, search_context, result_context, options = {})
|
|
73
|
+
matching_contexts_for(search_context.to_s, result_context.to_s, klass, options)
|
|
74
|
+
end
|
|
75
|
+
|
|
68
76
|
def top_#{tag_type}(limit = 10)
|
|
69
77
|
tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
|
|
70
78
|
end
|
|
@@ -74,11 +82,10 @@ module ActiveRecord
|
|
|
74
82
|
end
|
|
75
83
|
RUBY
|
|
76
84
|
end
|
|
77
|
-
|
|
78
85
|
if respond_to?(:tag_types)
|
|
79
86
|
write_inheritable_attribute( :tag_types, (tag_types + args).uniq )
|
|
80
87
|
else
|
|
81
|
-
|
|
88
|
+
class_eval do
|
|
82
89
|
write_inheritable_attribute(:tag_types, args.uniq)
|
|
83
90
|
class_inheritable_reader :tag_types
|
|
84
91
|
|
|
@@ -102,10 +109,6 @@ module ActiveRecord
|
|
|
102
109
|
alias_method_chain :reload, :tag_list
|
|
103
110
|
end
|
|
104
111
|
end
|
|
105
|
-
|
|
106
|
-
def is_taggable?
|
|
107
|
-
false
|
|
108
|
-
end
|
|
109
112
|
end
|
|
110
113
|
|
|
111
114
|
module SingletonMethods
|
|
@@ -113,6 +116,7 @@ module ActiveRecord
|
|
|
113
116
|
# Pass either a tag string, or an array of strings or tags
|
|
114
117
|
#
|
|
115
118
|
# Options:
|
|
119
|
+
# :any - find models that match any of the given tags
|
|
116
120
|
# :exclude - Find models that are not tagged with the given tags
|
|
117
121
|
# :match_all - Find models that match all of the given tags, not just one
|
|
118
122
|
# :conditions - A piece of SQL conditions to add to the query
|
|
@@ -135,9 +139,9 @@ module ActiveRecord
|
|
|
135
139
|
end
|
|
136
140
|
|
|
137
141
|
def find_options_for_find_tagged_with(tags, options = {})
|
|
138
|
-
|
|
142
|
+
tag_list = TagList.from(tags)
|
|
139
143
|
|
|
140
|
-
return {} if
|
|
144
|
+
return {} if tag_list.empty?
|
|
141
145
|
|
|
142
146
|
joins = []
|
|
143
147
|
conditions = []
|
|
@@ -146,28 +150,30 @@ module ActiveRecord
|
|
|
146
150
|
|
|
147
151
|
|
|
148
152
|
if options.delete(:exclude)
|
|
149
|
-
tags_conditions =
|
|
153
|
+
tags_conditions = tag_list.map { |t| sanitize_sql(["#{Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
|
|
150
154
|
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)})"
|
|
151
155
|
|
|
156
|
+
elsif options.delete(:any)
|
|
157
|
+
tags_conditions = tag_list.map { |t| sanitize_sql(["#{Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
|
|
158
|
+
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)})"
|
|
159
|
+
|
|
152
160
|
else
|
|
161
|
+
tags = Tag.named_like_any(tag_list)
|
|
162
|
+
return { :conditions => "1 = 0" } unless tags.length == tag_list.length
|
|
163
|
+
|
|
153
164
|
tags.each do |tag|
|
|
154
|
-
safe_tag = tag.gsub(/[^a-zA-Z0-9]/, '')
|
|
165
|
+
safe_tag = tag.name.gsub(/[^a-zA-Z0-9]/, '')
|
|
155
166
|
prefix = "#{safe_tag}_#{rand(1024)}"
|
|
156
167
|
|
|
157
168
|
taggings_alias = "#{table_name}_taggings_#{prefix}"
|
|
158
|
-
tags_alias = "#{table_name}_tags_#{prefix}"
|
|
159
169
|
|
|
160
170
|
tagging_join = "JOIN #{Tagging.table_name} #{taggings_alias}" +
|
|
161
171
|
" ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
|
|
162
|
-
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}"
|
|
172
|
+
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}" +
|
|
173
|
+
" AND #{taggings_alias}.tag_id = #{tag.id}"
|
|
163
174
|
tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
|
|
164
175
|
|
|
165
|
-
tag_join = "JOIN #{Tag.table_name} #{tags_alias}" +
|
|
166
|
-
" ON #{tags_alias}.id = #{taggings_alias}.tag_id" +
|
|
167
|
-
" AND " + sanitize_sql(["#{tags_alias}.name like ?", tag])
|
|
168
|
-
|
|
169
176
|
joins << tagging_join
|
|
170
|
-
joins << tag_join
|
|
171
177
|
end
|
|
172
178
|
end
|
|
173
179
|
|
|
@@ -183,7 +189,8 @@ module ActiveRecord
|
|
|
183
189
|
|
|
184
190
|
{ :joins => joins.join(" "),
|
|
185
191
|
:group => group,
|
|
186
|
-
:conditions => conditions.join(" AND ")
|
|
192
|
+
:conditions => conditions.join(" AND "),
|
|
193
|
+
:readonly => false }.update(options)
|
|
187
194
|
end
|
|
188
195
|
|
|
189
196
|
# Calculate the tag counts for all tags.
|
|
@@ -221,14 +228,32 @@ module ActiveRecord
|
|
|
221
228
|
|
|
222
229
|
joins = ["LEFT OUTER JOIN #{Tagging.table_name} ON #{Tag.table_name}.id = #{Tagging.table_name}.tag_id"]
|
|
223
230
|
joins << sanitize_sql(["AND #{Tagging.table_name}.context = ?",options.delete(:on).to_s]) unless options[:on].nil?
|
|
224
|
-
|
|
225
231
|
joins << " INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id"
|
|
226
|
-
|
|
232
|
+
|
|
233
|
+
unless descends_from_active_record?
|
|
227
234
|
# Current model is STI descendant, so add type checking to the join condition
|
|
228
|
-
joins << " AND #{table_name}.#{
|
|
235
|
+
joins << " AND #{table_name}.#{inheritance_column} = '#{name}'"
|
|
229
236
|
end
|
|
230
237
|
|
|
231
|
-
|
|
238
|
+
# Based on a proposed patch by donV to ActiveRecord Base
|
|
239
|
+
# This is needed because merge_joins and construct_join are private in ActiveRecord Base
|
|
240
|
+
if scope && scope[:joins]
|
|
241
|
+
case scope[:joins]
|
|
242
|
+
when Array
|
|
243
|
+
scope_joins = scope[:joins].flatten
|
|
244
|
+
strings = scope_joins.select{|j| j.is_a? String}
|
|
245
|
+
joins << strings.join(' ') + " "
|
|
246
|
+
symbols = scope_joins - strings
|
|
247
|
+
join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, symbols, nil)
|
|
248
|
+
joins << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
|
|
249
|
+
joins.flatten!
|
|
250
|
+
when Symbol, Hash
|
|
251
|
+
join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, scope[:joins], nil)
|
|
252
|
+
joins << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
|
|
253
|
+
when String
|
|
254
|
+
joins << scope[:joins]
|
|
255
|
+
end
|
|
256
|
+
end
|
|
232
257
|
|
|
233
258
|
at_least = sanitize_sql(['COUNT(*) >= ?', options.delete(:at_least)]) if options[:at_least]
|
|
234
259
|
at_most = sanitize_sql(['COUNT(*) <= ?', options.delete(:at_most)]) if options[:at_most]
|
|
@@ -253,10 +278,6 @@ module ActiveRecord
|
|
|
253
278
|
module InstanceMethods
|
|
254
279
|
include ActiveRecord::Acts::TaggableOn::GroupHelper
|
|
255
280
|
|
|
256
|
-
def tag_types
|
|
257
|
-
self.class.tag_types
|
|
258
|
-
end
|
|
259
|
-
|
|
260
281
|
def custom_contexts
|
|
261
282
|
@custom_contexts ||= []
|
|
262
283
|
end
|
|
@@ -264,29 +285,40 @@ module ActiveRecord
|
|
|
264
285
|
def is_taggable?
|
|
265
286
|
self.class.is_taggable?
|
|
266
287
|
end
|
|
267
|
-
|
|
288
|
+
|
|
268
289
|
def add_custom_context(value)
|
|
269
290
|
custom_contexts << value.to_s unless custom_contexts.include?(value.to_s) or self.class.tag_types.map(&:to_s).include?(value.to_s)
|
|
270
291
|
end
|
|
271
292
|
|
|
272
|
-
def tag_list_on(context, owner=nil)
|
|
273
|
-
var_name = context.to_s.singularize + "_list"
|
|
293
|
+
def tag_list_on(context, owner = nil)
|
|
274
294
|
add_custom_context(context)
|
|
275
|
-
|
|
276
|
-
|
|
295
|
+
cache = tag_list_cache_on(context)
|
|
296
|
+
return owner ? cache[owner] : cache[owner] if cache[owner]
|
|
297
|
+
|
|
277
298
|
if !owner && self.class.caching_tag_list_on?(context) and !(cached_value = cached_tag_list_on(context)).nil?
|
|
278
|
-
|
|
299
|
+
cache[owner] = TagList.from(cached_tag_list_on(context))
|
|
279
300
|
else
|
|
280
|
-
|
|
301
|
+
cache[owner] = TagList.new(*tags_on(context, owner).map(&:name))
|
|
281
302
|
end
|
|
282
303
|
end
|
|
304
|
+
|
|
305
|
+
def all_tags_list_on(context)
|
|
306
|
+
variable_name = "@all_#{context.to_s.singularize}_list"
|
|
307
|
+
return instance_variable_get(variable_name) if instance_variable_get(variable_name)
|
|
308
|
+
instance_variable_set(variable_name, TagList.new(all_tags_on(context).map(&:name)).freeze)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def all_tags_on(context)
|
|
312
|
+
opts = {:conditions => ["#{Tagging.table_name}.context = ?", context.to_s]}
|
|
313
|
+
base_tags.find(:all, opts.merge(:order => "#{Tagging.table_name}.created_at"))
|
|
314
|
+
end
|
|
283
315
|
|
|
284
|
-
def tags_on(context, owner=nil)
|
|
316
|
+
def tags_on(context, owner = nil)
|
|
285
317
|
if owner
|
|
286
318
|
opts = {:conditions => ["#{Tagging.table_name}.context = ? AND #{Tagging.table_name}.tagger_id = ? AND #{Tagging.table_name}.tagger_type = ?",
|
|
287
319
|
context.to_s, owner.id, owner.class.to_s]}
|
|
288
320
|
else
|
|
289
|
-
opts = {:conditions => ["#{Tagging.table_name}.context = ?", context.to_s]}
|
|
321
|
+
opts = {:conditions => ["#{Tagging.table_name}.context = ? AND #{Tagging.table_name}.tagger_id IS NULL", context.to_s]}
|
|
290
322
|
end
|
|
291
323
|
base_tags.find(:all, opts)
|
|
292
324
|
end
|
|
@@ -294,14 +326,21 @@ module ActiveRecord
|
|
|
294
326
|
def cached_tag_list_on(context)
|
|
295
327
|
self["cached_#{context.to_s.singularize}_list"]
|
|
296
328
|
end
|
|
329
|
+
|
|
330
|
+
def tag_list_cache_on(context)
|
|
331
|
+
variable_name = "@#{context.to_s.singularize}_list"
|
|
332
|
+
cache = instance_variable_get(variable_name)
|
|
333
|
+
instance_variable_set(variable_name, cache = {}) unless cache
|
|
334
|
+
cache
|
|
335
|
+
end
|
|
297
336
|
|
|
298
|
-
def set_tag_list_on(context,new_list, tagger=nil)
|
|
299
|
-
|
|
337
|
+
def set_tag_list_on(context, new_list, tagger = nil)
|
|
338
|
+
tag_list_cache_on(context)[tagger] = TagList.from(new_list)
|
|
300
339
|
add_custom_context(context)
|
|
301
340
|
end
|
|
302
341
|
|
|
303
342
|
def tag_counts_on(context, options={})
|
|
304
|
-
self.class.tag_counts_on(context, options.merge(:id =>
|
|
343
|
+
self.class.tag_counts_on(context, options.merge(:id => id))
|
|
305
344
|
end
|
|
306
345
|
|
|
307
346
|
def related_tags_for(context, klass, options = {})
|
|
@@ -311,9 +350,9 @@ module ActiveRecord
|
|
|
311
350
|
end
|
|
312
351
|
|
|
313
352
|
def related_search_options(context, klass, options = {})
|
|
314
|
-
tags_to_find =
|
|
353
|
+
tags_to_find = tags_on(context).collect { |t| t.name }
|
|
315
354
|
|
|
316
|
-
exclude_self = "#{klass.table_name}.id != #{
|
|
355
|
+
exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
|
|
317
356
|
|
|
318
357
|
{ :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
|
|
319
358
|
:from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
|
|
@@ -322,38 +361,72 @@ module ActiveRecord
|
|
|
322
361
|
:order => "count DESC"
|
|
323
362
|
}.update(options)
|
|
324
363
|
end
|
|
364
|
+
|
|
365
|
+
def matching_contexts_for(search_context, result_context, klass, options = {})
|
|
366
|
+
search_conditions = matching_context_search_options(search_context, result_context, klass, options)
|
|
367
|
+
|
|
368
|
+
klass.find(:all, search_conditions)
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def matching_context_search_options(search_context, result_context, klass, options = {})
|
|
372
|
+
tags_to_find = tags_on(search_context).collect { |t| t.name }
|
|
373
|
+
|
|
374
|
+
exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
|
|
325
375
|
|
|
376
|
+
{ :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
|
|
377
|
+
:from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
|
|
378
|
+
: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],
|
|
379
|
+
:group => grouped_column_names_for(klass),
|
|
380
|
+
:order => "count DESC"
|
|
381
|
+
}.update(options)
|
|
382
|
+
end
|
|
383
|
+
|
|
326
384
|
def save_cached_tag_list
|
|
327
385
|
self.class.tag_types.map(&:to_s).each do |tag_type|
|
|
328
386
|
if self.class.send("caching_#{tag_type.singularize}_list?")
|
|
329
|
-
self["cached_#{tag_type.singularize}_list"] =
|
|
387
|
+
self["cached_#{tag_type.singularize}_list"] = tag_list_cache_on(tag_type.singularize).tags.join(', ')
|
|
330
388
|
end
|
|
331
389
|
end
|
|
332
390
|
end
|
|
333
391
|
|
|
334
392
|
def save_tags
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
393
|
+
contexts = custom_contexts + self.class.tag_types.map(&:to_s)
|
|
394
|
+
|
|
395
|
+
transaction do
|
|
396
|
+
contexts.each do |context|
|
|
397
|
+
cache = tag_list_cache_on(context)
|
|
398
|
+
|
|
399
|
+
cache.each do |owner, list|
|
|
400
|
+
new_tags = Tag.find_or_create_all_with_like_by_name(list.uniq)
|
|
401
|
+
|
|
402
|
+
# Destroy old taggings:
|
|
403
|
+
if owner
|
|
404
|
+
old_tags = tags_on(context, owner) - new_tags
|
|
405
|
+
old_taggings = taggings.find(:all, :conditions => { :tag_id => old_tags, :tagger_id => owner, :tagger_type => owner.class.to_s, :context => context })
|
|
406
|
+
old_taggings.each(&:destroy)
|
|
407
|
+
else
|
|
408
|
+
old_tags = tags_on(context) - new_tags
|
|
409
|
+
base_tags.delete(*old_tags)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
new_tags.reject! { |tag| taggings.any? { |tagging| tagging.tag == tag &&
|
|
413
|
+
tagging.tagger == owner &&
|
|
414
|
+
tagging.context == context } }
|
|
415
|
+
|
|
416
|
+
# create new taggings:
|
|
417
|
+
new_tags.each do |tag|
|
|
418
|
+
taggings.create!(:tag_id => tag.id, :context => context, :tagger => owner)
|
|
419
|
+
end
|
|
347
420
|
end
|
|
348
421
|
end
|
|
349
|
-
end
|
|
422
|
+
end
|
|
350
423
|
|
|
351
424
|
true
|
|
352
425
|
end
|
|
353
426
|
|
|
354
427
|
def reload_with_tag_list(*args)
|
|
355
428
|
self.class.tag_types.each do |tag_type|
|
|
356
|
-
|
|
429
|
+
instance_variable_set("@#{tag_type.to_s.singularize}_list", nil)
|
|
357
430
|
end
|
|
358
431
|
|
|
359
432
|
reload_without_tag_list(*args)
|
|
@@ -1,14 +1,41 @@
|
|
|
1
1
|
class Tag < ActiveRecord::Base
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
attr_accessible :name
|
|
4
|
+
|
|
5
|
+
### ASSOCIATIONS:
|
|
6
|
+
|
|
7
|
+
has_many :taggings, :dependent => :destroy
|
|
8
|
+
|
|
9
|
+
### VALIDATIONS:
|
|
3
10
|
|
|
4
11
|
validates_presence_of :name
|
|
5
12
|
validates_uniqueness_of :name
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
### NAMED SCOPES:
|
|
15
|
+
|
|
16
|
+
named_scope :named, lambda { |name| { :conditions => ["name LIKE ?", name] } }
|
|
17
|
+
named_scope :named_any, lambda { |list| { :conditions => list.map { |tag| sanitize_sql(["name LIKE ?", tag.to_s]) }.join(" OR ") } }
|
|
18
|
+
named_scope :named_like, lambda { |name| { :conditions => ["name LIKE ?", "%#{name}%"] } }
|
|
19
|
+
named_scope :named_like_any, lambda { |list| { :conditions => list.map { |tag| sanitize_sql(["name LIKE ?", "%#{tag.to_s}%"]) }.join(" OR ") } }
|
|
20
|
+
|
|
21
|
+
### CLASS METHODS:
|
|
22
|
+
|
|
8
23
|
def self.find_or_create_with_like_by_name(name)
|
|
9
|
-
|
|
24
|
+
named_like(name).first || create(:name => name)
|
|
10
25
|
end
|
|
11
26
|
|
|
27
|
+
def self.find_or_create_all_with_like_by_name(*list)
|
|
28
|
+
list = [list].flatten
|
|
29
|
+
|
|
30
|
+
existing_tags = Tag.named_any(list).all
|
|
31
|
+
new_tag_names = list.reject { |name| existing_tags.any? { |tag| tag.name.downcase == name.downcase } }
|
|
32
|
+
created_tags = new_tag_names.map { |name| Tag.create(:name => name) }
|
|
33
|
+
|
|
34
|
+
existing_tags + created_tags
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
### INSTANCE METHODS:
|
|
38
|
+
|
|
12
39
|
def ==(object)
|
|
13
40
|
super || (object.is_a?(Tag) && name == object.name)
|
|
14
41
|
end
|
|
@@ -41,9 +41,10 @@ class TagList < Array
|
|
|
41
41
|
# tag_list = TagList.new("Round", "Square,Cube")
|
|
42
42
|
# tag_list.to_s # 'Round, "Square,Cube"'
|
|
43
43
|
def to_s
|
|
44
|
-
|
|
44
|
+
tags = frozen? ? self.dup : self
|
|
45
|
+
tags.send(:clean!)
|
|
45
46
|
|
|
46
|
-
map do |name|
|
|
47
|
+
tags.map do |name|
|
|
47
48
|
name.include?(delimiter) ? "\"#{name}\"" : name
|
|
48
49
|
end.join(delimiter.ends_with?(" ") ? delimiter : "#{delimiter} ")
|
|
49
50
|
end
|
|
@@ -55,7 +56,7 @@ class TagList < Array
|
|
|
55
56
|
map!(&:strip)
|
|
56
57
|
uniq!
|
|
57
58
|
end
|
|
58
|
-
|
|
59
|
+
|
|
59
60
|
def extract_and_apply_options!(args)
|
|
60
61
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
61
62
|
options.assert_valid_keys :parse
|
|
@@ -79,17 +80,11 @@ class TagList < Array
|
|
|
79
80
|
string = string.to_s.dup
|
|
80
81
|
|
|
81
82
|
# Parse the quoted tags
|
|
82
|
-
string.gsub!(/"(.*?)"\s
|
|
83
|
-
string.gsub!(/'(.*?)'\s
|
|
83
|
+
string.gsub!(/(\A|#{delimiter})\s*"(.*?)"\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
|
|
84
|
+
string.gsub!(/(\A|#{delimiter})\s*'(.*?)'\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
|
|
84
85
|
|
|
85
86
|
tag_list.add(string.split(delimiter))
|
|
86
87
|
end
|
|
87
88
|
end
|
|
88
|
-
|
|
89
|
-
def from_owner(owner, *tags)
|
|
90
|
-
returning from(*tags) do |taglist|
|
|
91
|
-
taglist.owner = owner
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
89
|
end
|
|
95
90
|
end
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
class Tagging < ActiveRecord::Base #:nodoc:
|
|
2
|
+
attr_accessible :tag, :tag_id, :context,
|
|
3
|
+
:taggable, :taggable_type, :taggable_id,
|
|
4
|
+
:tagger, :tagger_type, :tagger_id
|
|
5
|
+
|
|
2
6
|
belongs_to :tag
|
|
3
7
|
belongs_to :taggable, :polymorphic => true
|
|
4
8
|
belongs_to :tagger, :polymorphic => true
|
|
9
|
+
|
|
5
10
|
validates_presence_of :context
|
|
11
|
+
validates_presence_of :tag_id
|
|
12
|
+
|
|
13
|
+
validates_uniqueness_of :tag_id, :scope => [:taggable_type, :taggable_id, :context, :tagger_id, :tagger_type]
|
|
6
14
|
end
|
data/rails/init.rb
CHANGED
|
@@ -2,5 +2,4 @@ require 'acts-as-taggable-on'
|
|
|
2
2
|
|
|
3
3
|
ActiveRecord::Base.send :include, ActiveRecord::Acts::TaggableOn
|
|
4
4
|
ActiveRecord::Base.send :include, ActiveRecord::Acts::Tagger
|
|
5
|
-
|
|
6
|
-
RAILS_DEFAULT_LOGGER.info "** acts_as_taggable_on: initialized properly."
|
|
5
|
+
ActionView::Base.send :include, TagsHelper if defined?(ActionView::Base)
|
|
@@ -18,6 +18,10 @@ describe "Acts As Taggable On" do
|
|
|
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
|
+
|
|
22
|
+
it "should create an instance attribute for tag types" do
|
|
23
|
+
@taggable.should respond_to(:tag_types)
|
|
24
|
+
end
|
|
21
25
|
|
|
22
26
|
it "should generate an association for each tag type" do
|
|
23
27
|
@taggable.should respond_to(:tags, :skills, :languages)
|
|
@@ -119,17 +123,59 @@ describe "Acts As Taggable On" do
|
|
|
119
123
|
end
|
|
120
124
|
end
|
|
121
125
|
|
|
122
|
-
describe
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
describe "Matching Contexts" do
|
|
127
|
+
it "should find objects with tags of matching contexts" do
|
|
128
|
+
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
|
129
|
+
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
|
130
|
+
taggable3 = TaggableModel.create!(:name => "Taggable 3")
|
|
131
|
+
|
|
132
|
+
taggable1.offering_list = "one, two"
|
|
133
|
+
taggable1.save!
|
|
134
|
+
|
|
135
|
+
taggable2.need_list = "one, two"
|
|
136
|
+
taggable2.save!
|
|
137
|
+
|
|
138
|
+
taggable3.offering_list = "one, two"
|
|
139
|
+
taggable3.save!
|
|
140
|
+
|
|
141
|
+
taggable1.find_matching_contexts(:offerings, :needs).should include(taggable2)
|
|
142
|
+
taggable1.find_matching_contexts(:offerings, :needs).should_not include(taggable3)
|
|
131
143
|
end
|
|
132
144
|
|
|
145
|
+
it "should find other related objects with tags of matching contexts" do
|
|
146
|
+
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
|
147
|
+
taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
|
|
148
|
+
taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
|
|
149
|
+
|
|
150
|
+
taggable1.offering_list = "one, two"
|
|
151
|
+
taggable1.save
|
|
152
|
+
|
|
153
|
+
taggable2.need_list = "one, two"
|
|
154
|
+
taggable2.save
|
|
155
|
+
|
|
156
|
+
taggable3.offering_list = "one, two"
|
|
157
|
+
taggable3.save
|
|
158
|
+
|
|
159
|
+
taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should include(taggable2)
|
|
160
|
+
taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should_not include(taggable3)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it "should not include the object itself in the list of related objects" do
|
|
164
|
+
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
|
165
|
+
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
|
166
|
+
|
|
167
|
+
taggable1.tag_list = "one"
|
|
168
|
+
taggable1.save
|
|
169
|
+
|
|
170
|
+
taggable2.tag_list = "one, two"
|
|
171
|
+
taggable2.save
|
|
172
|
+
|
|
173
|
+
taggable1.find_related_tags.should include(taggable2)
|
|
174
|
+
taggable1.find_related_tags.should_not include(taggable1)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
describe 'Tagging Contexts' do
|
|
133
179
|
it 'should eliminate duplicate tagging contexts ' do
|
|
134
180
|
TaggableModel.acts_as_taggable_on(:skills, :skills)
|
|
135
181
|
TaggableModel.tag_types.freq[:skills].should_not == 3
|
|
@@ -156,10 +202,6 @@ describe "Acts As Taggable On" do
|
|
|
156
202
|
TaggableModel.acts_as_taggable_on([nil])
|
|
157
203
|
}.should_not raise_error
|
|
158
204
|
end
|
|
159
|
-
|
|
160
|
-
after(:all) do
|
|
161
|
-
class Array; remove_method :freq; end
|
|
162
|
-
end
|
|
163
205
|
end
|
|
164
206
|
|
|
165
207
|
end
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
require File.dirname(__FILE__) + '/../spec_helper'
|
|
2
2
|
|
|
3
3
|
describe "acts_as_tagger" do
|
|
4
|
+
before(:each) do
|
|
5
|
+
[TaggableUser, TaggableModel, Tagging, Tag].each(&:destroy_all)
|
|
6
|
+
end
|
|
7
|
+
|
|
4
8
|
context "Tagger Method Generation" do
|
|
5
|
-
|
|
6
9
|
before(:each) do
|
|
7
10
|
@tagger = TaggableUser.new()
|
|
8
11
|
end
|
|
@@ -48,8 +51,22 @@ describe "acts_as_tagger" do
|
|
|
48
51
|
|
|
49
52
|
it 'should by default create the tag context on-the-fly' do
|
|
50
53
|
@taggable.tag_list_on(:here_ond_now).should be_empty
|
|
51
|
-
@tagger.tag(@taggable, :with=>'that', :on
|
|
52
|
-
@taggable.tag_list_on(:here_ond_now).
|
|
54
|
+
@tagger.tag(@taggable, :with=>'that', :on => :here_ond_now)
|
|
55
|
+
@taggable.tag_list_on(:here_ond_now).should_not include('that')
|
|
56
|
+
@taggable.all_tags_list_on(:here_ond_now).should include('that')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "should show all the tag list when both public and owned tags exist" do
|
|
60
|
+
@taggable.tag_list = 'ruby, python'
|
|
61
|
+
@tagger.tag(@taggable, :with => 'java, lisp', :on => :tags)
|
|
62
|
+
@taggable.all_tags_on(:tags).map(&:name).sort.should == %w(ruby python java lisp).sort
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "should not add owned tags to the common list" do
|
|
66
|
+
@taggable.tag_list = 'ruby, python'
|
|
67
|
+
@tagger.tag(@taggable, :with => 'java, lisp', :on => :foo)
|
|
68
|
+
@tagger.tag(@taggable, :with => '', :on => :foo)
|
|
69
|
+
@taggable.tag_list.should == %w(ruby python)
|
|
53
70
|
end
|
|
54
71
|
|
|
55
72
|
it "should throw an exception when the default is over-ridden" do
|
|
@@ -64,9 +81,28 @@ describe "acts_as_tagger" do
|
|
|
64
81
|
@tagger.tag(@taggable, :with=>'this, and, that', :on=>:foo_boo, :force=>false) rescue
|
|
65
82
|
@taggable.tag_list_on(:foo_boo).should be_empty
|
|
66
83
|
end
|
|
67
|
-
|
|
68
84
|
end
|
|
69
|
-
|
|
85
|
+
|
|
86
|
+
context "when called by multiple tagger's" do
|
|
87
|
+
before(:each) do
|
|
88
|
+
@user_x = TaggableUser.new
|
|
89
|
+
@user_y = TaggableUser.new
|
|
90
|
+
@taggable = TaggableModel.new(:name => 'acts_as_taggable_on', :tag_list => 'plugin')
|
|
91
|
+
|
|
92
|
+
@user_x.tag(@taggable, :with => 'ruby, rails', :on => :tags)
|
|
93
|
+
@user_y.tag(@taggable, :with => 'ruby, plugin', :on => :tags)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "should not delete other taggers tags" do
|
|
97
|
+
@user_y.tag(@taggable, :with => '', :on => :tags)
|
|
98
|
+
@taggable.all_tags_list_on(:tags).should include('ruby')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "should not delete original tags" do
|
|
102
|
+
@user_y.tag(@taggable, :with => '', :on => :tags)
|
|
103
|
+
@taggable.all_tags_list_on(:tags).should include('plugin')
|
|
104
|
+
end
|
|
105
|
+
end
|
|
70
106
|
end
|
|
71
107
|
|
|
72
108
|
end
|
|
@@ -20,6 +20,18 @@ describe TagList do
|
|
|
20
20
|
@tag_list.include?("wicked").should be_true
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
it "should be able to add delimited list of words with quoted delimiters" do
|
|
24
|
+
@tag_list.add("'cool, wicked', \"really cool, really wicked\"", :parse => true)
|
|
25
|
+
@tag_list.include?("cool, wicked").should be_true
|
|
26
|
+
@tag_list.include?("really cool, really wicked").should be_true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "should be able to handle other uses of quotation marks correctly" do
|
|
30
|
+
@tag_list.add("john's cool car, mary's wicked toy", :parse => true)
|
|
31
|
+
@tag_list.include?("john's cool car").should be_true
|
|
32
|
+
@tag_list.include?("mary's wicked toy").should be_true
|
|
33
|
+
end
|
|
34
|
+
|
|
23
35
|
it "should be able to add an array of words" do
|
|
24
36
|
@tag_list.add(["cool", "wicked"], :parse => true)
|
|
25
37
|
@tag_list.include?("cool").should be_true
|
|
@@ -49,4 +61,10 @@ describe TagList do
|
|
|
49
61
|
@tag_list.add("cool","rad,bodacious")
|
|
50
62
|
@tag_list.to_s.should == "awesome, radical, cool, \"rad,bodacious\""
|
|
51
63
|
end
|
|
64
|
+
|
|
65
|
+
it "should be able to call to_s on a frozen tag list" do
|
|
66
|
+
@tag_list.freeze
|
|
67
|
+
lambda { @tag_list.add("cool","rad,bodacious") }.should raise_error
|
|
68
|
+
lambda { @tag_list.to_s }.should_not raise_error
|
|
69
|
+
end
|
|
52
70
|
end
|
|
@@ -4,8 +4,68 @@ describe Tag do
|
|
|
4
4
|
before(:each) do
|
|
5
5
|
@tag = Tag.new
|
|
6
6
|
@user = TaggableModel.create(:name => "Pablo")
|
|
7
|
+
Tag.delete_all
|
|
7
8
|
end
|
|
8
9
|
|
|
10
|
+
describe "named like any" do
|
|
11
|
+
before(:each) do
|
|
12
|
+
Tag.create(:name => "awesome")
|
|
13
|
+
Tag.create(:name => "epic")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "should find both tags" do
|
|
17
|
+
Tag.named_like_any(["awesome", "epic"]).should have(2).items
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "find or create by name" do
|
|
22
|
+
before(:each) do
|
|
23
|
+
@tag.name = "awesome"
|
|
24
|
+
@tag.save
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "should find by name" do
|
|
28
|
+
Tag.find_or_create_with_like_by_name("awesome").should == @tag
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "should find by name case insensitive" do
|
|
32
|
+
Tag.find_or_create_with_like_by_name("AWESOME").should == @tag
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "should create by name" do
|
|
36
|
+
lambda {
|
|
37
|
+
Tag.find_or_create_with_like_by_name("epic")
|
|
38
|
+
}.should change(Tag, :count).by(1)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "find or create all by any name" do
|
|
43
|
+
before(:each) do
|
|
44
|
+
@tag.name = "awesome"
|
|
45
|
+
@tag.save
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "should find by name" do
|
|
49
|
+
Tag.find_or_create_all_with_like_by_name("awesome").should == [@tag]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "should find by name case insensitive" do
|
|
53
|
+
Tag.find_or_create_all_with_like_by_name("AWESOME").should == [@tag]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "should create by name" do
|
|
57
|
+
lambda {
|
|
58
|
+
Tag.find_or_create_all_with_like_by_name("epic")
|
|
59
|
+
}.should change(Tag, :count).by(1)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "should find or create by name" do
|
|
63
|
+
lambda {
|
|
64
|
+
Tag.find_or_create_all_with_like_by_name("awesome", "epic").map(&:name).should == ["awesome", "epic"]
|
|
65
|
+
}.should change(Tag, :count).by(1)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
9
69
|
it "should require a name" do
|
|
10
70
|
@tag.valid?
|
|
11
71
|
@tag.errors.on(:name).should == "can't be blank"
|
|
@@ -24,4 +84,17 @@ describe Tag do
|
|
|
24
84
|
@tag.name = "cool"
|
|
25
85
|
@tag.to_s.should == "cool"
|
|
26
86
|
end
|
|
87
|
+
|
|
88
|
+
it "have named_scope named(something)" do
|
|
89
|
+
@tag.name = "cool"
|
|
90
|
+
@tag.save!
|
|
91
|
+
Tag.named('cool').should include(@tag)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "have named_scope named_like(something)" do
|
|
95
|
+
@tag.name = "cool"
|
|
96
|
+
@tag.save!
|
|
97
|
+
@another_tag = Tag.create!(:name => "coolip")
|
|
98
|
+
Tag.named_like('cool').should include(@tag, @another_tag)
|
|
99
|
+
end
|
|
27
100
|
end
|
|
@@ -5,31 +5,51 @@ describe "Taggable" do
|
|
|
5
5
|
[TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
|
|
6
6
|
@taggable = TaggableModel.new(:name => "Bob Jones")
|
|
7
7
|
end
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
it "should have tag types" do
|
|
10
|
+
for type in [:tags, :languages, :skills, :needs, :offerings]
|
|
11
|
+
TaggableModel.tag_types.should include type
|
|
12
|
+
end
|
|
13
|
+
@taggable.tag_types.should == TaggableModel.tag_types
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "should have tag_counts_on" do
|
|
17
|
+
TaggableModel.tag_counts_on(:tags).should be_empty
|
|
18
|
+
|
|
19
|
+
@taggable.tag_list = ["awesome", "epic"]
|
|
20
|
+
@taggable.save
|
|
21
|
+
|
|
22
|
+
TaggableModel.tag_counts_on(:tags).count.should == 2
|
|
23
|
+
@taggable.tag_counts_on(:tags).count.should == 2
|
|
24
|
+
end
|
|
25
|
+
|
|
9
26
|
it "should be able to create tags" do
|
|
10
27
|
@taggable.skill_list = "ruby, rails, css"
|
|
11
|
-
@taggable.instance_variable_get("@skill_list").instance_of?(
|
|
28
|
+
@taggable.instance_variable_get("@skill_list").instance_of?(Hash).should be_true
|
|
29
|
+
@taggable.instance_variable_get("@skill_list")[nil].instance_of?(TagList).should be_true
|
|
12
30
|
@taggable.save
|
|
13
|
-
|
|
31
|
+
|
|
14
32
|
Tag.find(:all).size.should == 3
|
|
15
33
|
end
|
|
16
|
-
|
|
34
|
+
|
|
17
35
|
it "should be able to create tags through the tag list directly" do
|
|
18
36
|
@taggable.tag_list_on(:test).add("hello")
|
|
19
|
-
@taggable.
|
|
37
|
+
@taggable.tag_list_cache_on(:test).should_not be_empty
|
|
38
|
+
@taggable.save
|
|
39
|
+
@taggable.save_tags
|
|
20
40
|
@taggable.reload
|
|
21
41
|
@taggable.tag_list_on(:test).should == ["hello"]
|
|
22
42
|
end
|
|
23
|
-
|
|
43
|
+
|
|
24
44
|
it "should differentiate between contexts" do
|
|
25
45
|
@taggable.skill_list = "ruby, rails, css"
|
|
26
46
|
@taggable.tag_list = "ruby, bob, charlie"
|
|
27
47
|
@taggable.save
|
|
28
48
|
@taggable.reload
|
|
29
|
-
@taggable.skill_list.include
|
|
30
|
-
@taggable.skill_list.include
|
|
49
|
+
@taggable.skill_list.should include("ruby")
|
|
50
|
+
@taggable.skill_list.should_not include("bob")
|
|
31
51
|
end
|
|
32
|
-
|
|
52
|
+
|
|
33
53
|
it "should be able to remove tags through list alone" do
|
|
34
54
|
@taggable.skill_list = "ruby, rails, css"
|
|
35
55
|
@taggable.save
|
|
@@ -40,13 +60,13 @@ describe "Taggable" do
|
|
|
40
60
|
@taggable.reload
|
|
41
61
|
@taggable.should have(2).skills
|
|
42
62
|
end
|
|
43
|
-
|
|
63
|
+
|
|
44
64
|
it "should be able to find by tag" do
|
|
45
65
|
@taggable.skill_list = "ruby, rails, css"
|
|
46
66
|
@taggable.save
|
|
47
67
|
TaggableModel.find_tagged_with("ruby").first.should == @taggable
|
|
48
68
|
end
|
|
49
|
-
|
|
69
|
+
|
|
50
70
|
it "should be able to find by tag with context" do
|
|
51
71
|
@taggable.skill_list = "ruby, rails, css"
|
|
52
72
|
@taggable.tag_list = "bob, charlie"
|
|
@@ -55,25 +75,27 @@ describe "Taggable" do
|
|
|
55
75
|
TaggableModel.find_tagged_with("bob", :on => :skills).first.should_not == @taggable
|
|
56
76
|
TaggableModel.find_tagged_with("bob", :on => :tags).first.should == @taggable
|
|
57
77
|
end
|
|
58
|
-
|
|
78
|
+
|
|
59
79
|
it "should be able to use the tagged_with named scope" do
|
|
60
80
|
@taggable.skill_list = "ruby, rails, css"
|
|
61
81
|
@taggable.tag_list = "bob, charlie"
|
|
62
82
|
@taggable.save
|
|
63
|
-
|
|
83
|
+
|
|
64
84
|
TaggableModel.tagged_with("ruby").first.should == @taggable
|
|
85
|
+
TaggableModel.tagged_with("ruby, css").first.should == @taggable
|
|
86
|
+
TaggableModel.tagged_with("ruby, nonexistingtag").should be_empty
|
|
65
87
|
TaggableModel.tagged_with("bob", :on => :skills).first.should_not == @taggable
|
|
66
88
|
TaggableModel.tagged_with("bob", :on => :tags).first.should == @taggable
|
|
67
89
|
end
|
|
68
|
-
|
|
90
|
+
|
|
69
91
|
it "should not care about case" do
|
|
70
92
|
bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby")
|
|
71
93
|
frank = TaggableModel.create(:name => "Frank", :tag_list => "Ruby")
|
|
72
|
-
|
|
94
|
+
|
|
73
95
|
Tag.find(:all).size.should == 1
|
|
74
96
|
TaggableModel.find_tagged_with("ruby").should == TaggableModel.find_tagged_with("Ruby")
|
|
75
97
|
end
|
|
76
|
-
|
|
98
|
+
|
|
77
99
|
it "should be able to get tag counts on model as a whole" do
|
|
78
100
|
bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
|
|
79
101
|
frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
|
|
@@ -81,33 +103,39 @@ describe "Taggable" do
|
|
|
81
103
|
TaggableModel.tag_counts.should_not be_empty
|
|
82
104
|
TaggableModel.skill_counts.should_not be_empty
|
|
83
105
|
end
|
|
84
|
-
|
|
106
|
+
|
|
85
107
|
it "should be able to get all tag counts on model as whole" do
|
|
86
108
|
bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
|
|
87
109
|
frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
|
|
88
110
|
charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
|
|
89
|
-
|
|
111
|
+
|
|
90
112
|
TaggableModel.all_tag_counts.should_not be_empty
|
|
91
113
|
TaggableModel.all_tag_counts.first.count.should == 3 # ruby
|
|
92
114
|
end
|
|
93
|
-
|
|
115
|
+
|
|
116
|
+
it "should not return read-only records" do
|
|
117
|
+
TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
|
|
118
|
+
|
|
119
|
+
TaggableModel.tagged_with("ruby").first.should_not be_readonly
|
|
120
|
+
end
|
|
121
|
+
|
|
94
122
|
it "should be able to get scoped tag counts" do
|
|
95
123
|
bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
|
|
96
124
|
frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
|
|
97
125
|
charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
|
|
98
|
-
|
|
126
|
+
|
|
99
127
|
TaggableModel.tagged_with("ruby").tag_counts.first.count.should == 2 # ruby
|
|
100
128
|
TaggableModel.tagged_with("ruby").skill_counts.first.count.should == 1 # ruby
|
|
101
129
|
end
|
|
102
|
-
|
|
130
|
+
|
|
103
131
|
it "should be able to get all scoped tag counts" do
|
|
104
132
|
bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
|
|
105
133
|
frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
|
|
106
134
|
charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
|
|
107
|
-
|
|
135
|
+
|
|
108
136
|
TaggableModel.tagged_with("ruby").all_tag_counts.first.count.should == 3 # ruby
|
|
109
137
|
end
|
|
110
|
-
|
|
138
|
+
|
|
111
139
|
it "should be able to set a custom tag context list" do
|
|
112
140
|
bob = TaggableModel.create(:name => "Bob")
|
|
113
141
|
bob.set_tag_list_on(:rotors, "spinning, jumping")
|
|
@@ -116,17 +144,27 @@ describe "Taggable" do
|
|
|
116
144
|
bob.reload
|
|
117
145
|
bob.tags_on(:rotors).should_not be_empty
|
|
118
146
|
end
|
|
119
|
-
|
|
147
|
+
|
|
120
148
|
it "should be able to find tagged" do
|
|
121
149
|
bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
|
|
122
150
|
frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
|
|
123
151
|
steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
|
|
124
|
-
|
|
152
|
+
|
|
125
153
|
TaggableModel.find_tagged_with("ruby", :order => 'taggable_models.name').should == [bob, frank, steve]
|
|
126
154
|
TaggableModel.find_tagged_with("ruby, rails", :order => 'taggable_models.name').should == [bob, frank]
|
|
127
155
|
TaggableModel.find_tagged_with(["ruby", "rails"], :order => 'taggable_models.name').should == [bob, frank]
|
|
128
156
|
end
|
|
129
|
-
|
|
157
|
+
|
|
158
|
+
it "should be able to find tagged with any tag" do
|
|
159
|
+
bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
|
|
160
|
+
frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
|
|
161
|
+
steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
|
|
162
|
+
|
|
163
|
+
TaggableModel.find_tagged_with(["ruby", "java"], :order => 'taggable_models.name', :any => true).should == [bob, frank, steve]
|
|
164
|
+
TaggableModel.find_tagged_with(["c++", "fitter"], :order => 'taggable_models.name', :any => true).should == [bob, steve]
|
|
165
|
+
TaggableModel.find_tagged_with(["depressed", "css"], :order => 'taggable_models.name', :any => true).should == [bob, frank]
|
|
166
|
+
end
|
|
167
|
+
|
|
130
168
|
it "should be able to find tagged on a custom tag context" do
|
|
131
169
|
bob = TaggableModel.create(:name => "Bob")
|
|
132
170
|
bob.set_tag_list_on(:rotors, "spinning, jumping")
|
|
@@ -139,29 +177,38 @@ describe "Taggable" do
|
|
|
139
177
|
bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
|
|
140
178
|
frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
|
|
141
179
|
steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, python')
|
|
142
|
-
|
|
180
|
+
|
|
143
181
|
# Let's only find those productive Rails developers
|
|
144
182
|
TaggableModel.tagged_with('rails', :on => :skills, :order => 'taggable_models.name').should == [bob, frank]
|
|
145
183
|
TaggableModel.tagged_with('happier', :on => :tags, :order => 'taggable_models.name').should == [bob, steve]
|
|
146
184
|
TaggableModel.tagged_with('rails', :on => :skills).tagged_with('happier', :on => :tags).should == [bob]
|
|
147
185
|
TaggableModel.tagged_with('rails').tagged_with('happier', :on => :tags).should == [bob]
|
|
148
186
|
end
|
|
149
|
-
|
|
187
|
+
|
|
150
188
|
it "should be able to find tagged with only the matching tags" do
|
|
151
189
|
bob = TaggableModel.create(:name => "Bob", :tag_list => "lazy, happier")
|
|
152
190
|
frank = TaggableModel.create(:name => "Frank", :tag_list => "fitter, happier, inefficient")
|
|
153
191
|
steve = TaggableModel.create(:name => 'Steve', :tag_list => "fitter, happier")
|
|
154
|
-
|
|
192
|
+
|
|
155
193
|
TaggableModel.find_tagged_with("fitter, happier", :match_all => true).should == [steve]
|
|
156
194
|
end
|
|
157
|
-
|
|
195
|
+
|
|
158
196
|
it "should be able to find tagged with some excluded tags" do
|
|
159
197
|
bob = TaggableModel.create(:name => "Bob", :tag_list => "happier, lazy")
|
|
160
198
|
frank = TaggableModel.create(:name => "Frank", :tag_list => "happier")
|
|
161
199
|
steve = TaggableModel.create(:name => 'Steve', :tag_list => "happier")
|
|
162
|
-
|
|
200
|
+
|
|
163
201
|
TaggableModel.find_tagged_with("lazy", :exclude => true).should == [frank, steve]
|
|
164
202
|
end
|
|
203
|
+
|
|
204
|
+
it "should not create duplicate taggings" do
|
|
205
|
+
bob = TaggableModel.create(:name => "Bob")
|
|
206
|
+
lambda {
|
|
207
|
+
bob.tag_list << "happier"
|
|
208
|
+
bob.tag_list << "happier"
|
|
209
|
+
bob.save
|
|
210
|
+
}.should change(Tagging, :count).by(1)
|
|
211
|
+
end
|
|
165
212
|
|
|
166
213
|
describe "Single Table Inheritance" do
|
|
167
214
|
before do
|
|
@@ -170,32 +217,32 @@ describe "Taggable" do
|
|
|
170
217
|
@inherited_same = InheritingTaggableModel.new(:name => "inherited same")
|
|
171
218
|
@inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
|
|
172
219
|
end
|
|
173
|
-
|
|
220
|
+
|
|
174
221
|
it "should be able to save tags for inherited models" do
|
|
175
222
|
@inherited_same.tag_list = "bob, kelso"
|
|
176
223
|
@inherited_same.save
|
|
177
224
|
InheritingTaggableModel.find_tagged_with("bob").first.should == @inherited_same
|
|
178
225
|
end
|
|
179
|
-
|
|
226
|
+
|
|
180
227
|
it "should find STI tagged models on the superclass" do
|
|
181
228
|
@inherited_same.tag_list = "bob, kelso"
|
|
182
229
|
@inherited_same.save
|
|
183
230
|
TaggableModel.find_tagged_with("bob").first.should == @inherited_same
|
|
184
231
|
end
|
|
185
|
-
|
|
232
|
+
|
|
186
233
|
it "should be able to add on contexts only to some subclasses" do
|
|
187
234
|
@inherited_different.part_list = "fork, spoon"
|
|
188
235
|
@inherited_different.save
|
|
189
236
|
InheritingTaggableModel.find_tagged_with("fork", :on => :parts).should be_empty
|
|
190
237
|
AlteredInheritingTaggableModel.find_tagged_with("fork", :on => :parts).first.should == @inherited_different
|
|
191
238
|
end
|
|
192
|
-
|
|
239
|
+
|
|
193
240
|
it "should have different tag_counts_on for inherited models" do
|
|
194
241
|
@inherited_same.tag_list = "bob, kelso"
|
|
195
242
|
@inherited_same.save!
|
|
196
243
|
@inherited_different.tag_list = "fork, spoon"
|
|
197
244
|
@inherited_different.save!
|
|
198
|
-
|
|
245
|
+
|
|
199
246
|
InheritingTaggableModel.tag_counts_on(:tags).map(&:name).should == %w(bob kelso)
|
|
200
247
|
AlteredInheritingTaggableModel.tag_counts_on(:tags).map(&:name).should == %w(fork spoon)
|
|
201
248
|
TaggableModel.tag_counts_on(:tags).map(&:name).should == %w(bob kelso fork spoon)
|
|
@@ -6,17 +6,32 @@ describe "Tagger" do
|
|
|
6
6
|
@user = TaggableUser.new
|
|
7
7
|
@taggable = TaggableModel.new(:name => "Bob Jones")
|
|
8
8
|
end
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
it "should have taggings" do
|
|
11
11
|
@user.tag(@taggable, :with=>'ruby,scheme', :on=>:tags)
|
|
12
12
|
@user.owned_taggings.size == 2
|
|
13
13
|
end
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
it "should have tags" do
|
|
16
16
|
@user.tag(@taggable, :with=>'ruby,scheme', :on=>:tags)
|
|
17
17
|
@user.owned_tags.size == 2
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
it "should not overlap or lose tags from different users" do
|
|
21
|
+
@user2 = TaggableUser.new
|
|
22
|
+
lambda{
|
|
23
|
+
@user.tag(@taggable, :with => 'ruby, scheme', :on => :tags)
|
|
24
|
+
@user2.tag(@taggable, :with => 'java, python, lisp, ruby', :on => :tags)
|
|
25
|
+
}.should change(Tagging, :count).by(6)
|
|
26
|
+
|
|
27
|
+
@user.owned_tags.map(&:name).sort.should == %w(ruby scheme)
|
|
28
|
+
@user2.owned_tags.map(&:name).sort.should == %w(java python lisp ruby).sort
|
|
29
|
+
@taggable.tags_from(@user).sort.should == %w(ruby scheme)
|
|
30
|
+
@taggable.tags_from(@user2).sort.should == %w(java lisp python ruby)
|
|
31
|
+
@taggable.all_tags_list_on(:tags).sort.should == %w(ruby scheme java python lisp).sort
|
|
32
|
+
@taggable.all_tags_on(:tags).size.should == 6
|
|
33
|
+
end
|
|
34
|
+
|
|
20
35
|
it "is tagger" do
|
|
21
36
|
@user.is_tagger?.should(be_true)
|
|
22
37
|
end
|
|
@@ -4,4 +4,22 @@ describe Tagging do
|
|
|
4
4
|
before(:each) do
|
|
5
5
|
@tagging = Tagging.new
|
|
6
6
|
end
|
|
7
|
+
|
|
8
|
+
it "should not be valid with a invalid tag" do
|
|
9
|
+
@tagging.taggable = TaggableModel.create(:name => "Bob Jones")
|
|
10
|
+
@tagging.tag = Tag.new(:name => "")
|
|
11
|
+
@tagging.context = "tags"
|
|
12
|
+
|
|
13
|
+
@tagging.should_not be_valid
|
|
14
|
+
@tagging.errors.on(:tag_id).should == "can't be blank"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "should not create duplicate taggings" do
|
|
18
|
+
@taggable = TaggableModel.create(:name => "Bob Jones")
|
|
19
|
+
@tag = Tag.create(:name => "awesome")
|
|
20
|
+
|
|
21
|
+
lambda {
|
|
22
|
+
2.times { Tagging.create(:taggable => @taggable, :tag => @tag, :context => 'tags') }
|
|
23
|
+
}.should change(Tagging, :count).by(1)
|
|
24
|
+
end
|
|
7
25
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe TagsHelper do
|
|
4
|
+
before(:each) do
|
|
5
|
+
[TaggableModel, Tag, Tagging].each(&:delete_all)
|
|
6
|
+
|
|
7
|
+
@bob = TaggableModel.create(:name => "Bob Jones", :language_list => "ruby, php")
|
|
8
|
+
@tom = TaggableModel.create(:name => "Tom Marley", :language_list => "ruby, java")
|
|
9
|
+
@eve = TaggableModel.create(:name => "Eve Nodd", :language_list => "ruby, c++")
|
|
10
|
+
|
|
11
|
+
@helper = class Helper
|
|
12
|
+
include TagsHelper
|
|
13
|
+
end.new
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "should yield the proper css classes" do
|
|
19
|
+
tags = { }
|
|
20
|
+
|
|
21
|
+
@helper.tag_cloud(TaggableModel.tag_counts_on(:languages), ["sucky", "awesome"]) do |tag, css_class|
|
|
22
|
+
tags[tag.name] = css_class
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
tags["ruby"].should == "awesome"
|
|
26
|
+
tags["java"].should == "sucky"
|
|
27
|
+
tags["c++"].should == "sucky"
|
|
28
|
+
tags["php"].should == "sucky"
|
|
29
|
+
end
|
|
30
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
# require File.dirname(__FILE__) + '/../../../../spec/spec_helper'
|
|
2
2
|
require 'rubygems'
|
|
3
|
-
require '
|
|
3
|
+
require 'active_record'
|
|
4
4
|
require 'spec'
|
|
5
5
|
|
|
6
6
|
module Spec::Example::ExampleGroupMethods
|
|
7
7
|
alias :context :describe
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
class Array
|
|
11
|
+
def freq
|
|
12
|
+
k=Hash.new(0)
|
|
13
|
+
each {|e| k[e]+=1}
|
|
14
|
+
k
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
10
18
|
TEST_DATABASE_FILE = File.join(File.dirname(__FILE__), '..', 'test.sqlite3')
|
|
11
19
|
|
|
12
20
|
File.unlink(TEST_DATABASE_FILE) if File.exist?(TEST_DATABASE_FILE)
|
|
@@ -16,18 +24,24 @@ ActiveRecord::Base.establish_connection(
|
|
|
16
24
|
|
|
17
25
|
RAILS_DEFAULT_LOGGER = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
|
|
18
26
|
|
|
19
|
-
|
|
27
|
+
ActiveRecord::Base.silence do
|
|
28
|
+
ActiveRecord::Migration.verbose = false
|
|
29
|
+
load(File.dirname(__FILE__) + '/schema.rb')
|
|
30
|
+
end
|
|
20
31
|
|
|
21
32
|
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
|
22
33
|
require File.join(File.dirname(__FILE__), '..', 'init')
|
|
23
34
|
|
|
24
35
|
class TaggableModel < ActiveRecord::Base
|
|
25
|
-
|
|
36
|
+
acts_as_taggable
|
|
37
|
+
acts_as_taggable_on :languages
|
|
26
38
|
acts_as_taggable_on :skills
|
|
39
|
+
acts_as_taggable_on :needs, :offerings
|
|
27
40
|
end
|
|
28
41
|
|
|
29
42
|
class OtherTaggableModel < ActiveRecord::Base
|
|
30
43
|
acts_as_taggable_on :tags, :languages
|
|
44
|
+
acts_as_taggable_on :needs, :offerings
|
|
31
45
|
end
|
|
32
46
|
|
|
33
47
|
class InheritingTaggableModel < TaggableModel
|
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.
|
|
4
|
+
version: 1.1.1
|
|
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:
|
|
12
|
+
date: 2010-02-06 00:00:00 +01:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies: []
|
|
15
15
|
|
|
@@ -27,6 +27,8 @@ files:
|
|
|
27
27
|
- README.rdoc
|
|
28
28
|
- Rakefile
|
|
29
29
|
- VERSION
|
|
30
|
+
- generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb
|
|
31
|
+
- generators/acts_as_taggable_on_migration/templates/migration.rb
|
|
30
32
|
- lib/acts-as-taggable-on.rb
|
|
31
33
|
- lib/acts_as_taggable_on/acts_as_taggable_on.rb
|
|
32
34
|
- lib/acts_as_taggable_on/acts_as_tagger.rb
|
|
@@ -44,6 +46,7 @@ files:
|
|
|
44
46
|
- spec/acts_as_taggable_on/taggable_spec.rb
|
|
45
47
|
- spec/acts_as_taggable_on/tagger_spec.rb
|
|
46
48
|
- spec/acts_as_taggable_on/tagging_spec.rb
|
|
49
|
+
- spec/acts_as_taggable_on/tags_helper_spec.rb
|
|
47
50
|
- spec/schema.rb
|
|
48
51
|
- spec/spec.opts
|
|
49
52
|
- spec/spec_helper.rb
|
|
@@ -84,5 +87,6 @@ test_files:
|
|
|
84
87
|
- spec/acts_as_taggable_on/taggable_spec.rb
|
|
85
88
|
- spec/acts_as_taggable_on/tagger_spec.rb
|
|
86
89
|
- spec/acts_as_taggable_on/tagging_spec.rb
|
|
90
|
+
- spec/acts_as_taggable_on/tags_helper_spec.rb
|
|
87
91
|
- spec/schema.rb
|
|
88
92
|
- spec/spec_helper.rb
|