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.
@@ -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.12
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)
@@ -2,5 +2,7 @@ class Tagging < ActiveRecord::Base #:nodoc:
2
2
  belongs_to :tag
3
3
  belongs_to :taggable, :polymorphic => true
4
4
  belongs_to :tagger, :polymorphic => true
5
+
5
6
  validates_presence_of :context
7
+ validates_presence_of :tag_id
6
8
  end
@@ -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
@@ -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
- acts_as_taggable_on :tags, :languages
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.12
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-07 00:00:00 -05:00
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