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

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.
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