acts-as-taggable-on 1.0.12 → 1.0.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -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