acts-as-taggable-on 1.0.19 → 2.0.0

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.
Files changed (38) hide show
  1. data/CHANGELOG +5 -2
  2. data/Gemfile +6 -0
  3. data/README.rdoc +52 -30
  4. data/Rakefile +45 -15
  5. data/VERSION +1 -1
  6. data/lib/acts-as-taggable-on.rb +30 -7
  7. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +53 -0
  8. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +98 -0
  9. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +237 -0
  10. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +101 -0
  11. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +64 -0
  12. data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +43 -395
  13. data/lib/acts_as_taggable_on/acts_as_tagger.rb +58 -43
  14. data/lib/acts_as_taggable_on/compatibility/Gemfile +6 -0
  15. data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +17 -0
  16. data/lib/acts_as_taggable_on/tag.rb +43 -16
  17. data/lib/acts_as_taggable_on/tag_list.rb +42 -43
  18. data/lib/acts_as_taggable_on/tagging.rb +16 -7
  19. data/lib/acts_as_taggable_on/tags_helper.rb +6 -2
  20. data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +31 -0
  21. data/lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb +28 -0
  22. data/rails/init.rb +1 -5
  23. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +100 -41
  24. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +46 -4
  25. data/spec/acts_as_taggable_on/tag_list_spec.rb +2 -2
  26. data/spec/acts_as_taggable_on/tag_spec.rb +57 -15
  27. data/spec/acts_as_taggable_on/taggable_spec.rb +133 -79
  28. data/spec/acts_as_taggable_on/tagger_spec.rb +73 -5
  29. data/spec/acts_as_taggable_on/tagging_spec.rb +11 -5
  30. data/spec/acts_as_taggable_on/tags_helper_spec.rb +1 -3
  31. data/spec/bm.rb +52 -0
  32. data/spec/models.rb +30 -0
  33. data/spec/schema.rb +13 -2
  34. data/spec/spec.opts +1 -2
  35. data/spec/spec_helper.rb +29 -35
  36. metadata +26 -8
  37. data/lib/acts_as_taggable_on/group_helper.rb +0 -12
  38. data/spec/acts_as_taggable_on/group_helper_spec.rb +0 -18
data/CHANGELOG CHANGED
@@ -1,3 +1,6 @@
1
+ == 2010-02-17
2
+ * Converted the plugin to be compatible with Rails3
3
+
1
4
  == 2009-12-02
2
5
 
3
6
  * PostgreSQL is now supported (via morgoth)
@@ -12,10 +15,10 @@
12
15
  * Removed extraneous down migration cruft (azabaj)
13
16
 
14
17
  == 2008-06-09
15
-
18
+
16
19
  * Added support for Single Table Inheritance
17
20
  * Adding gemspec and rails/init.rb for gemified plugin
18
-
21
+
19
22
  == 2007-12-12
20
23
 
21
24
  * Added ability to use dynamic tag contexts
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source :gemcutter
2
+
3
+ # Rails 3.0
4
+ gem 'rails', '3.0.0.beta'
5
+ gem 'rspec', '2.0.0.beta.1'
6
+ gem 'sqlite3-ruby', '1.2.5', :require => 'sqlite3'
data/README.rdoc CHANGED
@@ -15,71 +15,85 @@ was used.
15
15
 
16
16
  == Installation
17
17
 
18
- === Plugin
18
+ === Rails 2.3.x
19
+
20
+ Acts As Taggable On is tested to work in Rails 2.3.5.
21
+
22
+ ==== Plugin
19
23
 
20
24
  Acts As Taggable On is available both as a gem and as a traditional plugin. For the
21
- traditional plugin you can install like so (Rails 2.1 or later):
25
+ traditional plugin you can install like so:
22
26
 
23
27
  script/plugin install git://github.com/mbleigh/acts-as-taggable-on.git
24
-
25
- === GemPlugin
26
28
 
27
29
  Acts As Taggable On is also available as a gem plugin using Rails 2.1's gem dependencies.
28
30
  To install the gem, add this to your config/environment.rb:
29
31
 
30
- config.gem "acts-as-taggable-on", :source => "http://gemcutter.org"
32
+ config.gem "acts-as-taggable-on", :source => "http://gemcutter.org", :version => '2.0.0.rc1'
31
33
 
32
34
  After that, you can run "rake gems:install" to install the gem if you don't already have it.
33
35
 
34
- === Post Installation (Rails)
36
+ ==== Post Installation
35
37
 
36
38
  1. script/generate acts_as_taggable_on_migration
37
39
  2. rake db:migrate
38
40
 
39
- === Testing
41
+ == Rails 3.0
42
+
43
+ Acts As Taggable On is now useable in Rails 3.0, thanks to the excellent work of Szymon Nowak
44
+ and Jelle Vandebeeck.
45
+
46
+ To use it, add it to your Gemfile:
47
+
48
+ gem 'acts-as-taggable-on', '2.0.0.rc1'
49
+
50
+ === Post Installation
51
+
52
+ 1. rails generate acts_as_taggable_on:migration
53
+ 2. rake db:migrate
54
+
55
+ = Testing
40
56
 
41
57
  Acts As Taggable On uses RSpec for its test coverage. Inside the plugin
42
- directory, you can run the specs with:
58
+ directory, you can run the specs for RoR 3.0.0 with:
59
+
60
+ rake spec
61
+
62
+ If you want to test the plugin for Rails 2.3.x, use:
43
63
 
44
- rake spec
64
+ rake rails2.3:spec
45
65
 
46
66
  If you already have RSpec on your application, the specs will run while using:
47
67
 
48
68
  rake spec:plugins
49
69
 
50
70
 
51
- == Usage
71
+ = Usage
52
72
 
53
73
  class User < ActiveRecord::Base
54
- acts_as_taggable_on :tags, :skills, :interests
74
+ # Alias for <tt>acts_as_taggable_on :tags</tt>:
75
+ acts_as_taggable
76
+ acts_as_taggable_on :skills, :interests
55
77
  end
56
78
 
57
79
  @user = User.new(:name => "Bobby")
58
80
  @user.tag_list = "awesome, slick, hefty" # this should be familiar
59
81
  @user.skill_list = "joking, clowning, boxing" # but you can do it for any context!
60
- @user.skill_list # => ["joking","clowning","boxing"] as TagList
82
+ @user.skill_list # => ["joking","clowning","boxing"] as TagList
61
83
  @user.save
62
84
 
63
85
  @user.tags # => [<Tag name:"awesome">,<Tag name:"slick">,<Tag name:"hefty">]
64
86
  @user.skills # => [<Tag name:"joking">,<Tag name:"clowning">,<Tag name:"boxing">]
65
87
 
66
- # The old way
67
- User.find_tagged_with("awesome", :on => :tags) # => [@user]
68
- User.find_tagged_with("awesome", :on => :skills) # => []
69
-
70
- # The better way (utilizes named_scope)
71
- User.tagged_with("awesome", :on => :tags) # => [@user]
72
- User.tagged_with("awesome", :on => :skills) # => []
73
-
74
88
  @frankie = User.create(:name => "Frankie", :skill_list => "joking, flying, eating")
75
89
  User.skill_counts # => [<Tag name="joking" count=2>,<Tag name="clowning" count=1>...]
76
90
  @frankie.skill_counts
77
91
 
78
- === Finding Tagged Objects
92
+ == Finding Tagged Objects
79
93
 
80
- Acts As Taggable On utilizes Rails 2.1's named_scope to create an association
81
- for tags. This way you can mix and match to filter down your results, and it
82
- also improves compatibility with the will_paginate gem:
94
+ Acts As Taggable On utilizes named_scopes to create an association for tags.
95
+ This way you can mix and match to filter down your results, and it also improves
96
+ compatibility with the will_paginate gem:
83
97
 
84
98
  class User < ActiveRecord::Base
85
99
  acts_as_taggable_on :tags
@@ -89,7 +103,13 @@ also improves compatibility with the will_paginate gem:
89
103
  User.tagged_with("awesome").by_date
90
104
  User.tagged_with("awesome").by_date.paginate(:page => params[:page], :per_page => 20)
91
105
 
92
- === Relationships
106
+ # Find a user with matching all tags, not just one
107
+ User.tagged_with(["awesome", "cool"], :match_all => :true)
108
+
109
+ # Find a user with any of the tags:
110
+ User.tagged_with(["awesome", "cool"], :any => true)
111
+
112
+ == Relationships
93
113
 
94
114
  You can find objects of the same type based on similar tags on certain contexts.
95
115
  Also, objects will be returned in descending order based on the total number of
@@ -108,7 +128,7 @@ matched tags.
108
128
  @bobby.find_related_skills # => [<User name="Tom">]
109
129
  @frankie.find_related_skills # => [<User name="Tom">]
110
130
 
111
- === Dynamic Tag Contexts
131
+ == Dynamic Tag Contexts
112
132
 
113
133
  In addition to the generated tag contexts in the definition, it is also possible
114
134
  to allow for dynamic tag contexts (this could be user generated tag contexts!)
@@ -121,7 +141,7 @@ to allow for dynamic tag contexts (this could be user generated tag contexts!)
121
141
  @user.tag_counts_on(:customs)
122
142
  User.find_tagged_with("same", :on => :customs) # => [@user]
123
143
 
124
- === Tag Ownership
144
+ == Tag Ownership
125
145
 
126
146
  Tags can have owners:
127
147
 
@@ -138,7 +158,7 @@ Tags can have owners:
138
158
  @some_user.owned_tags
139
159
  @some_photo.locations_from(@some_user)
140
160
 
141
- === Tag cloud calculations
161
+ == Tag cloud calculations
142
162
 
143
163
  To construct tag clouds, the frequency of each tag needs to be calculated.
144
164
  Because we specified +acts_as_taggable_on+ on the <tt>User</tt> class, we can
@@ -178,10 +198,12 @@ CSS:
178
198
  .css3 { font-size: 1.4em; }
179
199
  .css4 { font-size: 1.6em; }
180
200
 
181
- == Contributors
201
+ = Contributors
182
202
 
183
203
  * TomEric (i76) - Maintainer
184
204
  * Michael Bleigh - Original Author
205
+ * Szymon Nowak - Rails 3.0 compatibility
206
+ * Jelle Vandebeeck - Rails 3.0 compatibility
185
207
  * Brendan Lim - Related Objects
186
208
  * Pradeep Elankumaran - Taggers
187
209
  * Sinclair Bain - Patch King
@@ -196,4 +218,4 @@ CSS:
196
218
  * lawrencepit - cached tag work
197
219
  * sobrinho - fixed tag_cloud helper
198
220
 
199
- Copyright (c) 2007-2009 Michael Bleigh (http://mbleigh.com/) and Intridea Inc. (http://intridea.com/), released under the MIT license
221
+ Copyright (c) 2007-2010 Michael Bleigh (http://mbleigh.com/) and Intridea Inc. (http://intridea.com/), released under the MIT license
data/Rakefile CHANGED
@@ -1,4 +1,46 @@
1
- require 'spec/rake/spectask'
1
+ begin
2
+ # Rspec 1.3.0
3
+ require 'spec/rake/spectask'
4
+
5
+ desc 'Default: run specs'
6
+ task :default => :spec
7
+ Spec::Rake::SpecTask.new do |t|
8
+ t.spec_files = FileList["spec/**/*_spec.rb"]
9
+ end
10
+
11
+ Spec::Rake::SpecTask.new('rcov') do |t|
12
+ t.spec_files = FileList["spec/**/*_spec.rb"]
13
+ t.rcov = true
14
+ t.rcov_opts = ['--exclude', 'spec']
15
+ end
16
+
17
+ rescue LoadError
18
+ # Rspec 2.0
19
+ require 'rspec/core/rake_task'
20
+
21
+ desc 'Default: run specs'
22
+ task :default => :spec
23
+ Rspec::Core::RakeTask.new do |t|
24
+ t.pattern = "spec/**/*_spec.rb"
25
+ end
26
+
27
+ Rspec::Core::RakeTask.new('rcov') do |t|
28
+ t.pattern = "spec/**/*_spec.rb"
29
+ t.rcov = true
30
+ t.rcov_opts = ['--exclude', 'spec']
31
+ end
32
+
33
+ rescue LoadError
34
+ puts "Rspec not available. Install it with: gem install rspec"
35
+ end
36
+
37
+ namespace 'rails2.3' do
38
+ task :spec do
39
+ gemfile = File.join(File.dirname(__FILE__), 'lib', 'acts_as_taggable_on', 'compatibility', 'Gemfile')
40
+ ENV['BUNDLE_GEMFILE'] = gemfile
41
+ Rake::Task['spec'].invoke
42
+ end
43
+ end
2
44
 
3
45
  begin
4
46
  require 'jeweler'
@@ -13,17 +55,5 @@ begin
13
55
  end
14
56
  Jeweler::GemcutterTasks.new
15
57
  rescue LoadError
16
- puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
17
- end
18
-
19
- desc 'Default: run specs'
20
- task :default => :spec
21
- Spec::Rake::SpecTask.new do |t|
22
- t.spec_files = FileList["spec/**/*_spec.rb"]
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
58
+ puts "Jeweler not available. Install it with: gem install jeweler"
59
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.19
1
+ 2.0.0
@@ -1,7 +1,30 @@
1
- require 'acts_as_taggable_on/group_helper'
2
- require 'acts_as_taggable_on/acts_as_taggable_on'
3
- require 'acts_as_taggable_on/acts_as_tagger'
4
- require 'acts_as_taggable_on/tag'
5
- require 'acts_as_taggable_on/tag_list'
6
- require 'acts_as_taggable_on/tags_helper'
7
- require 'acts_as_taggable_on/tagging'
1
+ require "active_record"
2
+ require "action_view"
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+
6
+ require "acts_as_taggable_on/compatibility/active_record_backports" if ActiveRecord::VERSION::MAJOR < 3
7
+
8
+ require "acts_as_taggable_on/acts_as_taggable_on"
9
+ require "acts_as_taggable_on/acts_as_taggable_on/core"
10
+ require "acts_as_taggable_on/acts_as_taggable_on/collection"
11
+ require "acts_as_taggable_on/acts_as_taggable_on/cache"
12
+ require "acts_as_taggable_on/acts_as_taggable_on/ownership"
13
+ require "acts_as_taggable_on/acts_as_taggable_on/related"
14
+
15
+ require "acts_as_taggable_on/acts_as_tagger"
16
+ require "acts_as_taggable_on/tag"
17
+ require "acts_as_taggable_on/tag_list"
18
+ require "acts_as_taggable_on/tags_helper"
19
+ require "acts_as_taggable_on/tagging"
20
+
21
+ $LOAD_PATH.shift
22
+
23
+ if defined?(ActiveRecord::Base)
24
+ ActiveRecord::Base.extend ActsAsTaggableOn::Taggable
25
+ ActiveRecord::Base.send :include, ActsAsTaggableOn::Tagger
26
+ end
27
+
28
+ if defined?(ActionView::Base)
29
+ ActionView::Base.send :include, TagsHelper
30
+ end
@@ -0,0 +1,53 @@
1
+ module ActsAsTaggableOn::Taggable
2
+ module Cache
3
+ def self.included(base)
4
+ # Skip adding caching capabilities if no cache columns exist
5
+ return unless base.tag_types.any? { |context| base.column_names.include?("cached_#{context.to_s.singularize}_list") }
6
+
7
+ base.send :include, ActsAsTaggableOn::Taggable::Cache::InstanceMethods
8
+ base.extend ActsAsTaggableOn::Taggable::Cache::ClassMethods
9
+
10
+ base.class_eval do
11
+ before_save :save_cached_tag_list
12
+ end
13
+
14
+ base.intialize_acts_as_taggable_on_cache
15
+ end
16
+
17
+ module ClassMethods
18
+ def intialize_acts_as_taggable_on_cache
19
+ tag_types.map(&:to_s).each do |tag_type|
20
+ class_eval %(
21
+ def self.caching_#{tag_type.singularize}_list?
22
+ caching_tag_list_on?("#{tag_type}")
23
+ end
24
+ )
25
+ end
26
+ end
27
+
28
+ def acts_as_taggable_on(*args)
29
+ super(*args)
30
+ intialize_acts_as_taggable_on_cache
31
+ end
32
+
33
+ def caching_tag_list_on?(context)
34
+ column_names.include?("cached_#{context.to_s.singularize}_list")
35
+ end
36
+ end
37
+
38
+ module InstanceMethods
39
+ def save_cached_tag_list
40
+ tag_types.map(&:to_s).each do |tag_type|
41
+ if self.class.send("caching_#{tag_type.singularize}_list?")
42
+ if tag_list_cache_set_on(tag_type)
43
+ list = tag_list_cache_on(tag_type.singularize).to_a.flatten.compact.join(', ')
44
+ self["cached_#{tag_type.singularize}_list"] = list
45
+ end
46
+ end
47
+ end
48
+
49
+ true
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,98 @@
1
+ module ActsAsTaggableOn::Taggable
2
+ module Collection
3
+ def self.included(base)
4
+ base.send :include, ActsAsTaggableOn::Taggable::Collection::InstanceMethods
5
+ base.extend ActsAsTaggableOn::Taggable::Collection::ClassMethods
6
+ base.initialize_acts_as_taggable_on_collection
7
+ end
8
+
9
+ module ClassMethods
10
+ def initialize_acts_as_taggable_on_collection
11
+ tag_types.map(&:to_s).each do |tag_type|
12
+ class_eval %(
13
+ def self.#{tag_type.singularize}_counts(options={})
14
+ tag_counts_on('#{tag_type}', options)
15
+ end
16
+
17
+ def #{tag_type.singularize}_counts(options = {})
18
+ tag_counts_on('#{tag_type}', options)
19
+ end
20
+
21
+ def top_#{tag_type}(limit = 10)
22
+ tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
23
+ end
24
+
25
+ def self.top_#{tag_type}(limit = 10)
26
+ tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
27
+ end
28
+ )
29
+ end
30
+ end
31
+
32
+ def acts_as_taggable_on(*args)
33
+ super(*args)
34
+ initialize_acts_as_taggable_on_collection
35
+ end
36
+
37
+ def tag_counts_on(context, options = {})
38
+ all_tag_counts(options.merge({:on => context.to_s}))
39
+ end
40
+
41
+ ##
42
+ # Calculate the tag counts for all tags.
43
+ #
44
+ # @param [Hash] options Options:
45
+ # * :start_at - Restrict the tags to those created after a certain time
46
+ # * :end_at - Restrict the tags to those created before a certain time
47
+ # * :conditions - A piece of SQL conditions to add to the query
48
+ # * :limit - The maximum number of tags to return
49
+ # * :order - A piece of SQL to order by. Eg 'tags.count desc' or 'taggings.created_at desc'
50
+ # * :at_least - Exclude tags with a frequency less than the given value
51
+ # * :at_most - Exclude tags with a frequency greater than the given value
52
+ # * :on - Scope the find to only include a certain context
53
+ def all_tag_counts(options = {})
54
+ options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id
55
+
56
+ start_at = sanitize_sql(["#{Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
57
+ end_at = sanitize_sql(["#{Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
58
+
59
+ taggable_type = sanitize_sql(["#{Tagging.table_name}.taggable_type = ?", base_class.name])
60
+ taggable_id = sanitize_sql(["#{Tagging.table_name}.taggable_id = ?", options.delete(:id)]) if options[:id]
61
+ options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
62
+
63
+ conditions = [
64
+ taggable_type,
65
+ taggable_id,
66
+ options[:conditions],
67
+ start_at,
68
+ end_at
69
+ ]
70
+
71
+ conditions = conditions.compact.join(' AND ')
72
+
73
+ joins = ["LEFT OUTER JOIN #{Tagging.table_name} ON #{Tag.table_name}.id = #{Tagging.table_name}.tag_id"]
74
+ joins << sanitize_sql(["AND #{Tagging.table_name}.context = ?",options.delete(:on).to_s]) unless options[:on].nil?
75
+ joins << " INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id"
76
+
77
+ unless descends_from_active_record?
78
+ # Current model is STI descendant, so add type checking to the join condition
79
+ joins << " AND #{table_name}.#{inheritance_column} = '#{name}'"
80
+ end
81
+
82
+ at_least = sanitize_sql(['COUNT(*) >= ?', options.delete(:at_least)]) if options[:at_least]
83
+ at_most = sanitize_sql(['COUNT(*) <= ?', options.delete(:at_most)]) if options[:at_most]
84
+ having = [at_least, at_most].compact.join(' AND ')
85
+ group_by = "#{grouped_column_names_for(Tag)} HAVING COUNT(*) > 0"
86
+ group_by << " AND #{having}" unless having.blank?
87
+
88
+ Tag.select("#{Tag.table_name}.*, COUNT(*) AS count").joins(joins.join(" ")).where(conditions).group(group_by).limit(options[:limit]).order(options[:order])
89
+ end
90
+ end
91
+
92
+ module InstanceMethods
93
+ def tag_counts_on(context, options={})
94
+ self.class.tag_counts_on(context, options.merge(:id => id))
95
+ end
96
+ end
97
+ end
98
+ end