acts-as-taggable-on 1.1.7 → 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 (37) hide show
  1. data/CHANGELOG +5 -2
  2. data/Gemfile +6 -0
  3. data/README.rdoc +45 -36
  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 +42 -423
  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 +32 -19
  17. data/lib/acts_as_taggable_on/tag_list.rb +42 -37
  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 +98 -43
  24. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +3 -2
  25. data/spec/acts_as_taggable_on/tag_spec.rb +31 -20
  26. data/spec/acts_as_taggable_on/taggable_spec.rb +75 -56
  27. data/spec/acts_as_taggable_on/tagger_spec.rb +56 -5
  28. data/spec/acts_as_taggable_on/tagging_spec.rb +10 -5
  29. data/spec/acts_as_taggable_on/tags_helper_spec.rb +0 -2
  30. data/spec/bm.rb +52 -0
  31. data/spec/models.rb +30 -0
  32. data/spec/schema.rb +10 -1
  33. data/spec/spec.opts +1 -2
  34. data/spec/spec_helper.rb +25 -41
  35. metadata +19 -8
  36. data/lib/acts_as_taggable_on/group_helper.rb +0 -12
  37. data/spec/acts_as_taggable_on/group_helper_spec.rb +0 -21
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,81 +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
 
36
+ ==== Post Installation
37
+
38
+ 1. script/generate acts_as_taggable_on_migration
39
+ 2. rake db:migrate
40
+
34
41
  == Rails 3.0
35
42
 
36
43
  Acts As Taggable On is now useable in Rails 3.0, thanks to the excellent work of Szymon Nowak
37
- and Jelle Vandebeeck. Because backwards compatibility is hard to maintain, their work is available
38
- in the feature/rails3_compatibility branch.
44
+ and Jelle Vandebeeck.
39
45
 
40
- A Rails 3.0 compatible version of the gem is also available:
46
+ To use it, add it to your Gemfile:
41
47
 
42
- gem install acts-as-taggable-on -v=2.0.0.pre1
48
+ gem 'acts-as-taggable-on', '2.0.0.rc1'
43
49
 
44
- === Post Installation (Rails)
50
+ === Post Installation
45
51
 
46
- 1. script/generate acts_as_taggable_on_migration
52
+ 1. rails generate acts_as_taggable_on:migration
47
53
  2. rake db:migrate
48
54
 
49
- === Testing
55
+ = Testing
50
56
 
51
57
  Acts As Taggable On uses RSpec for its test coverage. Inside the plugin
52
- 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:
53
63
 
54
- rake spec
64
+ rake rails2.3:spec
55
65
 
56
66
  If you already have RSpec on your application, the specs will run while using:
57
67
 
58
68
  rake spec:plugins
59
69
 
60
70
 
61
- == Usage
71
+ = Usage
62
72
 
63
73
  class User < ActiveRecord::Base
64
- 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
65
77
  end
66
78
 
67
79
  @user = User.new(:name => "Bobby")
68
80
  @user.tag_list = "awesome, slick, hefty" # this should be familiar
69
81
  @user.skill_list = "joking, clowning, boxing" # but you can do it for any context!
70
- @user.skill_list # => ["joking","clowning","boxing"] as TagList
82
+ @user.skill_list # => ["joking","clowning","boxing"] as TagList
71
83
  @user.save
72
84
 
73
85
  @user.tags # => [<Tag name:"awesome">,<Tag name:"slick">,<Tag name:"hefty">]
74
86
  @user.skills # => [<Tag name:"joking">,<Tag name:"clowning">,<Tag name:"boxing">]
75
87
 
76
- # The old way
77
- User.find_tagged_with("awesome", :on => :tags) # => [@user]
78
- User.find_tagged_with("awesome", :on => :skills) # => []
79
-
80
- # The better way (utilizes named_scope)
81
- User.tagged_with("awesome", :on => :tags) # => [@user]
82
- User.tagged_with("awesome", :on => :skills) # => []
83
-
84
88
  @frankie = User.create(:name => "Frankie", :skill_list => "joking, flying, eating")
85
89
  User.skill_counts # => [<Tag name="joking" count=2>,<Tag name="clowning" count=1>...]
86
90
  @frankie.skill_counts
87
91
 
88
- === Finding Tagged Objects
92
+ == Finding Tagged Objects
89
93
 
90
- Acts As Taggable On utilizes Rails 2.1's named_scope to create an association
91
- for tags. This way you can mix and match to filter down your results, and it
92
- 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:
93
97
 
94
98
  class User < ActiveRecord::Base
95
99
  acts_as_taggable_on :tags
@@ -99,10 +103,13 @@ also improves compatibility with the will_paginate gem:
99
103
  User.tagged_with("awesome").by_date
100
104
  User.tagged_with("awesome").by_date.paginate(:page => params[:page], :per_page => 20)
101
105
 
102
- #Find a user with matching all tags, not just one
106
+ # Find a user with matching all tags, not just one
103
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)
104
111
 
105
- === Relationships
112
+ == Relationships
106
113
 
107
114
  You can find objects of the same type based on similar tags on certain contexts.
108
115
  Also, objects will be returned in descending order based on the total number of
@@ -121,7 +128,7 @@ matched tags.
121
128
  @bobby.find_related_skills # => [<User name="Tom">]
122
129
  @frankie.find_related_skills # => [<User name="Tom">]
123
130
 
124
- === Dynamic Tag Contexts
131
+ == Dynamic Tag Contexts
125
132
 
126
133
  In addition to the generated tag contexts in the definition, it is also possible
127
134
  to allow for dynamic tag contexts (this could be user generated tag contexts!)
@@ -134,7 +141,7 @@ to allow for dynamic tag contexts (this could be user generated tag contexts!)
134
141
  @user.tag_counts_on(:customs)
135
142
  User.find_tagged_with("same", :on => :customs) # => [@user]
136
143
 
137
- === Tag Ownership
144
+ == Tag Ownership
138
145
 
139
146
  Tags can have owners:
140
147
 
@@ -151,7 +158,7 @@ Tags can have owners:
151
158
  @some_user.owned_tags
152
159
  @some_photo.locations_from(@some_user)
153
160
 
154
- === Tag cloud calculations
161
+ == Tag cloud calculations
155
162
 
156
163
  To construct tag clouds, the frequency of each tag needs to be calculated.
157
164
  Because we specified +acts_as_taggable_on+ on the <tt>User</tt> class, we can
@@ -191,10 +198,12 @@ CSS:
191
198
  .css3 { font-size: 1.4em; }
192
199
  .css4 { font-size: 1.6em; }
193
200
 
194
- == Contributors
201
+ = Contributors
195
202
 
196
203
  * TomEric (i76) - Maintainer
197
204
  * Michael Bleigh - Original Author
205
+ * Szymon Nowak - Rails 3.0 compatibility
206
+ * Jelle Vandebeeck - Rails 3.0 compatibility
198
207
  * Brendan Lim - Related Objects
199
208
  * Pradeep Elankumaran - Taggers
200
209
  * Sinclair Bain - Patch King
@@ -209,4 +218,4 @@ CSS:
209
218
  * lawrencepit - cached tag work
210
219
  * sobrinho - fixed tag_cloud helper
211
220
 
212
- 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.1.7
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