acts-as-taggable-on 1.0.12 → 1.0.13
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 +33 -0
- data/Rakefile +6 -0
- data/VERSION +1 -1
- data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +27 -8
- data/lib/acts_as_taggable_on/tag.rb +4 -1
- data/lib/acts_as_taggable_on/tagging.rb +2 -0
- data/rails/init.rb +1 -0
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +56 -0
- data/spec/acts_as_taggable_on/tag_spec.rb +35 -0
- data/spec/acts_as_taggable_on/taggable_spec.rb +15 -0
- data/spec/acts_as_taggable_on/tagging_spec.rb +9 -0
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +30 -0
- data/spec/spec_helper.rb +4 -1
- metadata +4 -2
data/README.rdoc
CHANGED
@@ -137,6 +137,39 @@ 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
|
+
Controller:
|
155
|
+
|
156
|
+
class PostController < ApplicationController
|
157
|
+
def tag_cloud
|
158
|
+
@tags = Post.tag_counts_on(:tags)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
View:
|
163
|
+
<% tag_cloud @tags, %w(css1 css2 css3 css4) do |tag, css_class| %>
|
164
|
+
<%= link_to tag.name, { :action => :tag, :id => tag.name }, :class => css_class %>
|
165
|
+
<% end %>
|
166
|
+
|
167
|
+
CSS:
|
168
|
+
|
169
|
+
.css1 { font-size: 1.0em; }
|
170
|
+
.css2 { font-size: 1.2em; }
|
171
|
+
.css3 { font-size: 1.4em; }
|
172
|
+
.css4 { font-size: 1.6em; }
|
140
173
|
|
141
174
|
== Contributors
|
142
175
|
|
data/Rakefile
CHANGED
@@ -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.0.
|
1
|
+
1.0.13
|
@@ -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
|
@@ -102,10 +110,6 @@ module ActiveRecord
|
|
102
110
|
alias_method_chain :reload, :tag_list
|
103
111
|
end
|
104
112
|
end
|
105
|
-
|
106
|
-
def is_taggable?
|
107
|
-
false
|
108
|
-
end
|
109
113
|
end
|
110
114
|
|
111
115
|
module SingletonMethods
|
@@ -253,10 +257,6 @@ module ActiveRecord
|
|
253
257
|
module InstanceMethods
|
254
258
|
include ActiveRecord::Acts::TaggableOn::GroupHelper
|
255
259
|
|
256
|
-
def tag_types
|
257
|
-
self.class.tag_types
|
258
|
-
end
|
259
|
-
|
260
260
|
def custom_contexts
|
261
261
|
@custom_contexts ||= []
|
262
262
|
end
|
@@ -322,7 +322,26 @@ module ActiveRecord
|
|
322
322
|
:order => "count DESC"
|
323
323
|
}.update(options)
|
324
324
|
end
|
325
|
+
|
326
|
+
def matching_contexts_for(search_context, result_context, klass, options = {})
|
327
|
+
search_conditions = matching_context_search_options(search_context, result_context, klass, options)
|
328
|
+
|
329
|
+
klass.find(:all, search_conditions)
|
330
|
+
end
|
331
|
+
|
332
|
+
def matching_context_search_options(search_context, result_context, klass, options = {})
|
333
|
+
tags_to_find = self.tags_on(search_context).collect { |t| t.name }
|
325
334
|
|
335
|
+
exclude_self = "#{klass.table_name}.id != #{self.id} AND" if self.class == klass
|
336
|
+
|
337
|
+
{ :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
|
338
|
+
:from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
|
339
|
+
: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],
|
340
|
+
:group => grouped_column_names_for(klass),
|
341
|
+
:order => "count DESC"
|
342
|
+
}.update(options)
|
343
|
+
end
|
344
|
+
|
326
345
|
def save_cached_tag_list
|
327
346
|
self.class.tag_types.map(&:to_s).each do |tag_type|
|
328
347
|
if self.class.send("caching_#{tag_type.singularize}_list?")
|
@@ -1,9 +1,12 @@
|
|
1
1
|
class Tag < ActiveRecord::Base
|
2
|
-
has_many :taggings
|
2
|
+
has_many :taggings, :dependent => :destroy
|
3
3
|
|
4
4
|
validates_presence_of :name
|
5
5
|
validates_uniqueness_of :name
|
6
6
|
|
7
|
+
named_scope :named, lambda { |name| { :conditions => ["name = ?", name] } }
|
8
|
+
named_scope :named_like, lambda { |name| { :conditions => ["name LIKE ?", "%#{name}%"] } }
|
9
|
+
|
7
10
|
# LIKE is used for cross-database case-insensitivity
|
8
11
|
def self.find_or_create_with_like_by_name(name)
|
9
12
|
find(:first, :conditions => ["name LIKE ?", name]) || create(:name => name)
|
data/rails/init.rb
CHANGED
@@ -2,5 +2,6 @@ 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
|
+
ActionView::Base.send :include, TagsHelper if defined?(ActionView::Base)
|
5
6
|
|
6
7
|
RAILS_DEFAULT_LOGGER.info "** acts_as_taggable_on: initialized properly."
|
@@ -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,6 +123,58 @@ describe "Acts As Taggable On" do
|
|
119
123
|
end
|
120
124
|
end
|
121
125
|
|
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)
|
143
|
+
end
|
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
|
+
|
122
178
|
describe 'Tagging Contexts' do
|
123
179
|
before(:all) do
|
124
180
|
class Array
|
@@ -4,8 +4,30 @@ 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 "find or create by name" do
|
11
|
+
before(:each) do
|
12
|
+
@tag.name = "awesome"
|
13
|
+
@tag.save
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should find by name" do
|
17
|
+
Tag.find_or_create_with_like_by_name("awesome").should == @tag
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should find by name case insensitive" do
|
21
|
+
Tag.find_or_create_with_like_by_name("AWESOME").should == @tag
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should create by name" do
|
25
|
+
lambda {
|
26
|
+
Tag.find_or_create_with_like_by_name("epic")
|
27
|
+
}.should change(Tag, :count).by(1)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
9
31
|
it "should require a name" do
|
10
32
|
@tag.valid?
|
11
33
|
@tag.errors.on(:name).should == "can't be blank"
|
@@ -24,4 +46,17 @@ describe Tag do
|
|
24
46
|
@tag.name = "cool"
|
25
47
|
@tag.to_s.should == "cool"
|
26
48
|
end
|
49
|
+
|
50
|
+
it "have named_scope named(something)" do
|
51
|
+
@tag.name = "cool"
|
52
|
+
@tag.save!
|
53
|
+
Tag.named('cool').should include(@tag)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "have named_scope named_like(something)" do
|
57
|
+
@tag.name = "cool"
|
58
|
+
@tag.save!
|
59
|
+
@another_tag = Tag.create!(:name => "coolip")
|
60
|
+
Tag.named_like('cool').should include(@tag, @another_tag)
|
61
|
+
end
|
27
62
|
end
|
@@ -5,6 +5,21 @@ describe "Taggable" do
|
|
5
5
|
[TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
|
6
6
|
@taggable = TaggableModel.new(:name => "Bob Jones")
|
7
7
|
end
|
8
|
+
|
9
|
+
it "should have tag types" do
|
10
|
+
TaggableModel.tag_types.should == [:tags, :languages, :skills, :needs, :offerings]
|
11
|
+
@taggable.tag_types.should == TaggableModel.tag_types
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should have tag_counts_on" do
|
15
|
+
TaggableModel.tag_counts_on(:tags).should be_empty
|
16
|
+
|
17
|
+
@taggable.tag_list = ["awesome", "epic"]
|
18
|
+
@taggable.save
|
19
|
+
|
20
|
+
TaggableModel.tag_counts_on(:tags).count.should == 2
|
21
|
+
@taggable.tag_counts_on(:tags).count.should == 2
|
22
|
+
end
|
8
23
|
|
9
24
|
it "should be able to create tags" do
|
10
25
|
@taggable.skill_list = "ruby, rails, css"
|
@@ -4,4 +4,13 @@ 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
|
7
16
|
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
@@ -22,12 +22,15 @@ $: << File.join(File.dirname(__FILE__), '..', 'lib')
|
|
22
22
|
require File.join(File.dirname(__FILE__), '..', 'init')
|
23
23
|
|
24
24
|
class TaggableModel < ActiveRecord::Base
|
25
|
-
|
25
|
+
acts_as_taggable
|
26
|
+
acts_as_taggable_on :languages
|
26
27
|
acts_as_taggable_on :skills
|
28
|
+
acts_as_taggable_on :needs, :offerings
|
27
29
|
end
|
28
30
|
|
29
31
|
class OtherTaggableModel < ActiveRecord::Base
|
30
32
|
acts_as_taggable_on :tags, :languages
|
33
|
+
acts_as_taggable_on :needs, :offerings
|
31
34
|
end
|
32
35
|
|
33
36
|
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.0.
|
4
|
+
version: 1.0.13
|
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-12-
|
12
|
+
date: 2009-12-11 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -44,6 +44,7 @@ files:
|
|
44
44
|
- spec/acts_as_taggable_on/taggable_spec.rb
|
45
45
|
- spec/acts_as_taggable_on/tagger_spec.rb
|
46
46
|
- spec/acts_as_taggable_on/tagging_spec.rb
|
47
|
+
- spec/acts_as_taggable_on/tags_helper_spec.rb
|
47
48
|
- spec/schema.rb
|
48
49
|
- spec/spec.opts
|
49
50
|
- spec/spec_helper.rb
|
@@ -84,5 +85,6 @@ test_files:
|
|
84
85
|
- spec/acts_as_taggable_on/taggable_spec.rb
|
85
86
|
- spec/acts_as_taggable_on/tagger_spec.rb
|
86
87
|
- spec/acts_as_taggable_on/tagging_spec.rb
|
88
|
+
- spec/acts_as_taggable_on/tags_helper_spec.rb
|
87
89
|
- spec/schema.rb
|
88
90
|
- spec/spec_helper.rb
|