mbleigh-acts-as-taggable-on 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,8 @@
1
+ == 2008-06-23
2
+
3
+ * Can now find related objects of another class (tristanzdunn)
4
+ * Removed extraneous down migration cruft (azabaj)
5
+
1
6
  == 2008-06-09
2
7
 
3
8
  * Added support for Single Table Inheritance
data/README CHANGED
@@ -35,12 +35,23 @@ GemPlugin
35
35
  Acts As Taggable On is also available as a gem plugin using Rails 2.1's gem dependencies.
36
36
  To install the gem, add this to your config/environment.rb:
37
37
 
38
- config.gem "mbleigh-acts-as-taggable-on", :source => "http://gems.github.com"
38
+ config.gem "mbleigh-acts-as-taggable-on", :source => "http://gems.github.com", :lib => "acts-as-taggable-on"
39
39
 
40
40
  After that, you can run "rake gems:install" to install the gem if you don't already have it.
41
41
  See http://ryandaigle.com/articles/2008/4/1/what-s-new-in-edge-rails-gem-dependencies for
42
42
  additional details about gem dependencies in Rails.
43
43
 
44
+ ** NOTE **
45
+ Some issues have been experienced with "rake gems:install". If that doesn't work to install the gem,
46
+ try just installing it as a normal gem:
47
+
48
+ gem install mbleigh-acts-as-taggable-on --source http://gems.github.com
49
+
50
+ Post Installation (Rails)
51
+ -------------------------
52
+ 1. script/generate acts_as_taggable_on_migration
53
+ 2. rake db/migrate
54
+
44
55
  Testing
45
56
  =======
46
57
 
@@ -135,14 +146,19 @@ Contributors
135
146
  ============
136
147
 
137
148
  * Michael Bleigh - Original Author
138
- * Brendan Lim - Related objects
149
+ * Brendan Lim - Related Objects
139
150
  * Pradeep Elankumaran - Taggers
151
+ * Sinclair Bain - Patch King
140
152
 
141
153
  Patch Contributors
142
154
  ------------------
143
155
 
156
+ * tristanzdunn - Related objects of other classes
157
+ * azabaj - Fixed migrate down
144
158
  * Peter Cooper - named_scope fix
145
159
  * slainer68 - STI fix
160
+ * harrylove - migration instructions and fix-ups
161
+ * lawrencepit - cached tag work
146
162
 
147
163
  Resources
148
164
  =========
@@ -2,7 +2,6 @@ class ActsAsTaggableOnMigrationGenerator < Rails::Generator::Base
2
2
  def manifest
3
3
  record do |m|
4
4
  m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => "acts_as_taggable_on_migration"
5
- m.migration_template 'add_users_migration.rb', 'db/migrate', :migration_file_name => "add_users_to_acts_as_taggable_on_migration"
6
- end
5
+ end
7
6
  end
8
7
  end
@@ -7,6 +7,8 @@ class ActsAsTaggableOnMigration < ActiveRecord::Migration
7
7
  create_table :taggings do |t|
8
8
  t.column :tag_id, :integer
9
9
  t.column :taggable_id, :integer
10
+ t.column :tagger_id, :integer
11
+ t.column :tagger_type, :string
10
12
 
11
13
  # You should make sure that the column created is
12
14
  # long enough to store the required class names.
@@ -6,12 +6,17 @@ module ActiveRecord
6
6
  end
7
7
 
8
8
  module ClassMethods
9
+ def taggable?
10
+ false
11
+ end
12
+
9
13
  def acts_as_taggable
10
14
  acts_as_taggable_on :tags
11
15
  end
12
16
 
13
17
  def acts_as_taggable_on(*args)
14
- puts "Registering #{args.inspect} with #{self.inspect}"
18
+ args.flatten! if args
19
+ args.compact! if args
15
20
  for tag_type in args
16
21
  tag_type = tag_type.to_s
17
22
  self.class_eval do
@@ -21,6 +26,10 @@ module ActiveRecord
21
26
  end
22
27
 
23
28
  self.class_eval <<-RUBY
29
+ def self.taggable?
30
+ true
31
+ end
32
+
24
33
  def self.caching_#{tag_type.singularize}_list?
25
34
  caching_tag_list_on?("#{tag_type}")
26
35
  end
@@ -46,18 +55,21 @@ module ActiveRecord
46
55
  end
47
56
 
48
57
  def find_related_#{tag_type}(options = {})
49
- related_tags_on('#{tag_type}',options)
58
+ related_tags_for('#{tag_type}', self.class, options)
50
59
  end
51
60
  alias_method :find_related_on_#{tag_type}, :find_related_#{tag_type}
61
+
62
+ def find_related_#{tag_type}_for(klass, options = {})
63
+ related_tags_for('#{tag_type}', klass, options)
64
+ end
52
65
  RUBY
53
66
  end
54
67
 
55
68
  if respond_to?(:tag_types)
56
- puts "Appending #{args.inspect} onto #{tag_types.inspect}"
57
- write_inheritable_attribute(:tag_types, tag_types + args)
69
+ write_inheritable_attribute( :tag_types, (tag_types + args).uniq )
58
70
  else
59
71
  self.class_eval do
60
- write_inheritable_attribute(:tag_types, args)
72
+ write_inheritable_attribute(:tag_types, args.uniq)
61
73
  class_inheritable_reader :tag_types
62
74
 
63
75
  has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag
@@ -207,9 +219,10 @@ module ActiveRecord
207
219
 
208
220
  def tag_list_on(context, owner=nil)
209
221
  var_name = context.to_s.singularize + "_list"
222
+ add_custom_context(context)
210
223
  return instance_variable_get("@#{var_name}") unless instance_variable_get("@#{var_name}").nil?
211
224
 
212
- if !owner && self.class.caching_tag_list_on?(context) and !(cached_value = cached_tag_list_on(context, owner)).nil?
225
+ if !owner && self.class.caching_tag_list_on?(context) and !(cached_value = cached_tag_list_on(context)).nil?
213
226
  instance_variable_set("@#{var_name}", TagList.from(self["cached_#{var_name}"]))
214
227
  else
215
228
  instance_variable_set("@#{var_name}", TagList.new(*tags_on(context, owner).map(&:name)))
@@ -238,18 +251,22 @@ module ActiveRecord
238
251
  def tag_counts_on(context,options={})
239
252
  self.class.tag_counts_on(context,{:conditions => ["#{Tag.table_name}.name IN (?)", tag_list_on(context)]}.reverse_merge!(options))
240
253
  end
241
-
242
- def related_tags_on(context, options={})
243
- tags_to_find = self.tags_on(context).collect {|t| t.name}
244
- search_conditions = {
245
- :select => "#{self.class.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
246
- :from => "#{self.class.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
247
- :conditions => ["#{self.class.table_name}.id != #{self.id} AND #{self.class.table_name}.id = #{Tagging.table_name}.taggable_id AND #{Tagging.table_name}.taggable_type = '#{self.class.to_s}' AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND #{Tag.table_name}.name IN (?)",tags_to_find],
248
- :group => "#{self.class.table_name}.id",
254
+
255
+ def related_tags_for(context, klass, options = {})
256
+ search_conditions = related_search_options(context, klass, options)
257
+
258
+ klass.find(:all, search_conditions)
259
+ end
260
+
261
+ def related_search_options(context, klass, options = {})
262
+ tags_to_find = self.tags_on(context).collect { |t| t.name }
263
+
264
+ { :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
265
+ :from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
266
+ :conditions => ["#{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 (?)", tags_to_find],
267
+ :group => "#{klass.table_name}.id",
249
268
  :order => "count DESC"
250
269
  }.update(options)
251
-
252
- self.class.find(:all, search_conditions)
253
270
  end
254
271
 
255
272
  def save_cached_tag_list
@@ -24,10 +24,14 @@ module ActiveRecord
24
24
  end
25
25
 
26
26
  def tag(taggable, opts={})
27
+ opts.reverse_merge!(:force => true)
28
+
27
29
  return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
28
30
  raise "You need to specify a tag context using :on" unless opts.has_key?(:on)
29
31
  raise "You need to specify some tags using :with" unless opts.has_key?(:with)
30
- raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless taggable.tag_types.include?(opts[:on])
32
+ raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless
33
+ ( opts[:force] || taggable.tag_types.include?(opts[:on]) )
34
+
31
35
  taggable.set_tag_list_on(opts[:on].to_s, opts[:with], self)
32
36
  taggable.save
33
37
  end
@@ -1,11 +1,20 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
- describe "acts_as_taggable_on" do
4
- context "Taggable Method Generation" do
3
+ describe "Acts As Taggable On" do
4
+ it "should provide a class method 'taggable?' that is false for untaggable models" do
5
+ UntaggableModel.should_not be_taggable
6
+ end
7
+
8
+ describe "Taggable Method Generation" do
5
9
  before(:each) do
10
+ [TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
6
11
  @taggable = TaggableModel.new(:name => "Bob Jones")
7
12
  end
8
13
 
14
+ it "should respond 'true' to taggable?" do
15
+ @taggable.class.should be_taggable
16
+ end
17
+
9
18
  it "should create a class attribute for tag types" do
10
19
  @taggable.class.should respond_to(:tag_types)
11
20
  end
@@ -33,7 +42,7 @@ describe "acts_as_taggable_on" do
33
42
  end
34
43
  end
35
44
 
36
- context "inheritance" do
45
+ describe "Single Table Inheritance" do
37
46
  before do
38
47
  @taggable = TaggableModel.new(:name => "taggable")
39
48
  @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
@@ -50,7 +59,7 @@ describe "acts_as_taggable_on" do
50
59
  end
51
60
  end
52
61
 
53
- context "reloading" do
62
+ describe "Reloading" do
54
63
  it "should save a model instantiated by Model.find" do
55
64
  taggable = TaggableModel.create!(:name => "Taggable")
56
65
  found_taggable = TaggableModel.find(taggable.id)
@@ -58,7 +67,7 @@ describe "acts_as_taggable_on" do
58
67
  end
59
68
  end
60
69
 
61
- context "related" do
70
+ describe "Related Objects" do
62
71
  it "should find related objects based on tag names on context" do
63
72
  taggable1 = TaggableModel.create!(:name => "Taggable 1")
64
73
  taggable2 = TaggableModel.create!(:name => "Taggable 2")
@@ -76,5 +85,67 @@ describe "acts_as_taggable_on" do
76
85
  taggable1.find_related_tags.should include(taggable3)
77
86
  taggable1.find_related_tags.should_not include(taggable2)
78
87
  end
88
+
89
+ it "should find other related objects based on tag names on context" do
90
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
91
+ taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
92
+ taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
93
+
94
+ taggable1.tag_list = "one, two"
95
+ taggable1.save
96
+
97
+ taggable2.tag_list = "three, four"
98
+ taggable2.save
99
+
100
+ taggable3.tag_list = "one, four"
101
+ taggable3.save
102
+
103
+ taggable1.find_related_tags_for(OtherTaggableModel).should include(taggable3)
104
+ taggable1.find_related_tags_for(OtherTaggableModel).should_not include(taggable2)
105
+ end
79
106
  end
107
+
108
+ describe 'Tagging Contexts' do
109
+ before(:all) do
110
+ class Array
111
+ def freq
112
+ k=Hash.new(0)
113
+ self.each {|e| k[e]+=1}
114
+ k
115
+ end
116
+ end
117
+ end
118
+
119
+ it 'should eliminate duplicate tagging contexts ' do
120
+ TaggableModel.acts_as_taggable_on(:skills, :skills)
121
+ TaggableModel.tag_types.freq[:skills].should_not == 3
122
+ end
123
+
124
+ it "should not contain embedded/nested arrays" do
125
+ TaggableModel.acts_as_taggable_on([:array], [:array])
126
+ TaggableModel.tag_types.freq[[:array]].should == 0
127
+ end
128
+
129
+ it "should _flatten_ the content of arrays" do
130
+ TaggableModel.acts_as_taggable_on([:array], [:array])
131
+ TaggableModel.tag_types.freq[:array].should == 1
132
+ end
133
+
134
+ it "should not raise an error when passed nil" do
135
+ lambda {
136
+ TaggableModel.acts_as_taggable_on()
137
+ }.should_not raise_error
138
+ end
139
+
140
+ it "should not raise an error when passed [nil]" do
141
+ lambda {
142
+ TaggableModel.acts_as_taggable_on([nil])
143
+ }.should_not raise_error
144
+ end
145
+
146
+ after(:all) do
147
+ class Array; remove_method :freq; end
148
+ end
149
+ end
150
+
80
151
  end
@@ -2,6 +2,7 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe "Taggable" do
4
4
  before(:each) do
5
+ [TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
5
6
  @taggable = TaggableModel.new(:name => "Bob Jones")
6
7
  end
7
8
 
@@ -13,6 +14,13 @@ describe "Taggable" do
13
14
  Tag.find(:all).size.should == 3
14
15
  end
15
16
 
17
+ it "should be able to create tags through the tag list directly" do
18
+ @taggable.tag_list_on(:test).add("hello")
19
+ @taggable.save
20
+ @taggable.reload
21
+ @taggable.tag_list_on(:test).should == ["hello"]
22
+ end
23
+
16
24
  it "should differentiate between contexts" do
17
25
  @taggable.skill_list = "ruby, rails, css"
18
26
  @taggable.tag_list = "ruby, bob, charlie"
@@ -89,8 +97,9 @@ describe "Taggable" do
89
97
  TaggableModel.find_tagged_with("spinning", :on => :rotors).should_not be_empty
90
98
  end
91
99
 
92
- context "inheritance" do
100
+ describe "Single Table Inheritance" do
93
101
  before do
102
+ [TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
94
103
  @taggable = TaggableModel.new(:name => "taggable")
95
104
  @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
96
105
  @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
@@ -2,6 +2,7 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe "Tagger" do
4
4
  before(:each) do
5
+ [TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
5
6
  @user = TaggableUser.new
6
7
  @taggable = TaggableModel.new(:name => "Bob Jones")
7
8
  end
data/spec/schema.rb CHANGED
@@ -1,16 +1,19 @@
1
1
  ActiveRecord::Schema.define :version => 0 do
2
- create_table :tags, :force => true do |t|
3
- t.column :name, :string
2
+ create_table "taggings", :force => true do |t|
3
+ t.integer "tag_id", :limit => 11
4
+ t.integer "taggable_id", :limit => 11
5
+ t.string "taggable_type"
6
+ t.string "context"
7
+ t.datetime "created_at"
8
+ t.integer "tagger_id", :limit => 11
9
+ t.string "tagger_type"
4
10
  end
5
-
6
- create_table :taggings, :force => true do |t|
7
- t.column :tag_id, :integer
8
- t.column :taggable_id, :integer
9
- t.column :taggable_type, :string
10
- t.column :context, :string
11
- t.column :created_at, :datetime
12
- t.column :tagger_id, :integer
13
- t.column :tagger_type, :string
11
+
12
+ add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
13
+ add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context"
14
+
15
+ create_table "tags", :force => true do |t|
16
+ t.string "name"
14
17
  end
15
18
 
16
19
  create_table :taggable_models, :force => true do |t|
@@ -21,4 +24,9 @@ ActiveRecord::Schema.define :version => 0 do
21
24
  create_table :taggable_users, :force => true do |t|
22
25
  t.column :name, :string
23
26
  end
27
+ create_table :other_taggable_models, :force => true do |t|
28
+ t.column :name, :string
29
+ t.column :type, :string
30
+ #t.column :cached_tag_list, :string
31
+ end
24
32
  end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  require File.dirname(__FILE__) + '/../../../../spec/spec_helper'
2
2
 
3
+ module Spec::Example::ExampleGroupMethods
4
+ alias :context :describe
5
+ end
6
+
3
7
  plugin_spec_dir = File.dirname(__FILE__)
4
8
  ActiveRecord::Base.logger = Logger.new(plugin_spec_dir + "/debug.log")
5
9
 
@@ -10,6 +14,10 @@ class TaggableModel < ActiveRecord::Base
10
14
  acts_as_taggable_on :skills
11
15
  end
12
16
 
17
+ class OtherTaggableModel < ActiveRecord::Base
18
+ acts_as_taggable_on :tags, :languages
19
+ end
20
+
13
21
  class InheritingTaggableModel < TaggableModel
14
22
  end
15
23
 
@@ -19,4 +27,7 @@ end
19
27
 
20
28
  class TaggableUser < ActiveRecord::Base
21
29
  acts_as_tagger
30
+ end
31
+
32
+ class UntaggableModel < ActiveRecord::Base
22
33
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mbleigh-acts-as-taggable-on
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
@@ -72,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
72
  requirements: []
73
73
 
74
74
  rubyforge_project:
75
- rubygems_version: 1.0.1
75
+ rubygems_version: 1.2.0
76
76
  signing_key:
77
77
  specification_version: 2
78
78
  summary: Tagging for ActiveRecord with custom contexts and advanced features.
@@ -1,11 +0,0 @@
1
- class AddUsersToActsAsTaggableOnMigration < ActiveRecord::Migration
2
- def self.up
3
- add_column :taggings, :tagger_id, :integer
4
- add_column :taggings, :tagger_type, :string
5
- end
6
-
7
- def self.down
8
- remove_column :taggings, :tagger_type
9
- remove_column :taggings, :tagger_id
10
- end
11
- end