yetanothernguyen-acts-as-taggable-on 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/CHANGELOG +25 -0
  2. data/Gemfile +10 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +221 -0
  5. data/Rakefile +59 -0
  6. data/VERSION +1 -0
  7. data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +7 -0
  8. data/generators/acts_as_taggable_on_migration/templates/migration.rb +29 -0
  9. data/lib/acts-as-taggable-on.rb +30 -0
  10. data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +53 -0
  11. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +53 -0
  12. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +139 -0
  13. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +262 -0
  14. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +105 -0
  15. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +69 -0
  16. data/lib/acts_as_taggable_on/acts_as_tagger.rb +67 -0
  17. data/lib/acts_as_taggable_on/compatibility/Gemfile +8 -0
  18. data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +21 -0
  19. data/lib/acts_as_taggable_on/tag.rb +84 -0
  20. data/lib/acts_as_taggable_on/tag_list.rb +96 -0
  21. data/lib/acts_as_taggable_on/tagging.rb +24 -0
  22. data/lib/acts_as_taggable_on/tags_helper.rb +17 -0
  23. data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +32 -0
  24. data/lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb +28 -0
  25. data/rails/init.rb +1 -0
  26. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +268 -0
  27. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +114 -0
  28. data/spec/acts_as_taggable_on/tag_list_spec.rb +70 -0
  29. data/spec/acts_as_taggable_on/tag_spec.rb +115 -0
  30. data/spec/acts_as_taggable_on/taggable_spec.rb +343 -0
  31. data/spec/acts_as_taggable_on/tagger_spec.rb +91 -0
  32. data/spec/acts_as_taggable_on/tagging_spec.rb +31 -0
  33. data/spec/acts_as_taggable_on/tags_helper_spec.rb +28 -0
  34. data/spec/bm.rb +52 -0
  35. data/spec/database.yml.sample +17 -0
  36. data/spec/models.rb +31 -0
  37. data/spec/schema.rb +43 -0
  38. data/spec/spec_helper.rb +60 -0
  39. metadata +115 -0
@@ -0,0 +1,24 @@
1
+ module ActsAsTaggableOn
2
+ class Tagging < ::ActiveRecord::Base #:nodoc:
3
+ include ActsAsTaggableOn::ActiveRecord::Backports if ::ActiveRecord::VERSION::MAJOR < 3
4
+
5
+ attr_accessible :tag,
6
+ :tag_id,
7
+ :context,
8
+ :taggable,
9
+ :taggable_type,
10
+ :taggable_id,
11
+ :tagger,
12
+ :tagger_type,
13
+ :tagger_id
14
+
15
+ belongs_to :tag, :class_name => 'ActsAsTaggableOn::Tag'
16
+ belongs_to :taggable, :polymorphic => true
17
+ belongs_to :tagger, :polymorphic => true
18
+
19
+ validates_presence_of :context
20
+ validates_presence_of :tag_id
21
+
22
+ validates_uniqueness_of :tag_id, :scope => [ :taggable_type, :taggable_id, :context, :tagger_id, :tagger_type ]
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module ActsAsTaggableOn
2
+ module TagsHelper
3
+ # See the README for an example using tag_cloud.
4
+ def tag_cloud(tags, classes)
5
+ tags = tags.all if tags.respond_to?(:all)
6
+
7
+ return [] if tags.empty?
8
+
9
+ max_count = tags.sort_by(&:count).last.count.to_f
10
+
11
+ tags.each do |tag|
12
+ index = ((tag.count / max_count) * (classes.size - 1)).round
13
+ yield tag, classes[index]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ require 'rails/generators/migration'
2
+
3
+ module ActsAsTaggableOn
4
+ class MigrationGenerator < Rails::Generators::Base
5
+ include Rails::Generators::Migration
6
+
7
+ desc "Generates migration for Tag and Tagging models"
8
+
9
+ def self.orm
10
+ Rails::Generators.options[:rails][:orm]
11
+ end
12
+
13
+ def self.source_root
14
+ File.join(File.dirname(__FILE__), 'templates', (orm.to_s unless orm.class.eql?(String)) )
15
+ end
16
+
17
+ def self.orm_has_migration?
18
+ [:active_record].include? orm
19
+ end
20
+
21
+ def self.next_migration_number(path)
22
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
23
+ end
24
+
25
+ def create_migration_file
26
+ if self.class.orm_has_migration?
27
+ migration_template 'migration.rb', 'db/migrate/acts_as_taggable_on_migration'
28
+ end
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,28 @@
1
+ class ActsAsTaggableOnMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tags do |t|
4
+ t.string :name
5
+ end
6
+
7
+ create_table :taggings do |t|
8
+ t.references :tag
9
+
10
+ # You should make sure that the column created is
11
+ # long enough to store the required class names.
12
+ t.references :taggable, :polymorphic => true
13
+ t.references :tagger, :polymorphic => true
14
+
15
+ t.string :context
16
+
17
+ t.datetime :created_at
18
+ end
19
+
20
+ add_index :taggings, :tag_id
21
+ add_index :taggings, [:taggable_id, :taggable_type, :context]
22
+ end
23
+
24
+ def self.down
25
+ drop_table :taggings
26
+ drop_table :tags
27
+ end
28
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'acts-as-taggable-on'
@@ -0,0 +1,268 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe "Acts As Taggable On" do
4
+ before(:each) do
5
+ clean_database!
6
+ end
7
+
8
+ it "should provide a class method 'taggable?' that is false for untaggable models" do
9
+ UntaggableModel.should_not be_taggable
10
+ end
11
+
12
+ describe "Taggable Method Generation" do
13
+ before(:each) do
14
+ clean_database!
15
+ TaggableModel.write_inheritable_attribute(:tag_types, [])
16
+ TaggableModel.acts_as_taggable_on(:tags, :languages, :skills, :needs, :offerings)
17
+ @taggable = TaggableModel.new(:name => "Bob Jones")
18
+ end
19
+
20
+ it "should respond 'true' to taggable?" do
21
+ @taggable.class.should be_taggable
22
+ end
23
+
24
+ it "should create a class attribute for tag types" do
25
+ @taggable.class.should respond_to(:tag_types)
26
+ end
27
+
28
+ it "should create an instance attribute for tag types" do
29
+ @taggable.should respond_to(:tag_types)
30
+ end
31
+
32
+ it "should have all tag types" do
33
+ @taggable.tag_types.should == [:tags, :languages, :skills, :needs, :offerings]
34
+ end
35
+
36
+ it "should generate an association for each tag type" do
37
+ @taggable.should respond_to(:tags, :skills, :languages)
38
+ end
39
+
40
+ it "should add tagged_with and tag_counts to singleton" do
41
+ TaggableModel.should respond_to(:tagged_with, :tag_counts)
42
+ end
43
+
44
+ it "should generate a tag_list accessor/setter for each tag type" do
45
+ @taggable.should respond_to(:tag_list, :skill_list, :language_list)
46
+ @taggable.should respond_to(:tag_list=, :skill_list=, :language_list=)
47
+ end
48
+
49
+ it "should generate a tag_list accessor, that includes owned tags, for each tag type" do
50
+ @taggable.should respond_to(:all_tags_list, :all_skills_list, :all_languages_list)
51
+ end
52
+ end
53
+
54
+ describe "Single Table Inheritance" do
55
+ before do
56
+ @taggable = TaggableModel.new(:name => "taggable")
57
+ @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
58
+ @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
59
+ end
60
+
61
+ it "should pass on tag contexts to STI-inherited models" do
62
+ @inherited_same.should respond_to(:tag_list, :skill_list, :language_list)
63
+ @inherited_different.should respond_to(:tag_list, :skill_list, :language_list)
64
+ end
65
+
66
+ it "should have tag contexts added in altered STI models" do
67
+ @inherited_different.should respond_to(:part_list)
68
+ end
69
+ end
70
+
71
+ describe "Reloading" do
72
+ it "should save a model instantiated by Model.find" do
73
+ taggable = TaggableModel.create!(:name => "Taggable")
74
+ found_taggable = TaggableModel.find(taggable.id)
75
+ found_taggable.save
76
+ end
77
+ end
78
+
79
+ describe "Related Objects" do
80
+ it "should find related objects based on tag names on context" do
81
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
82
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
83
+ taggable3 = TaggableModel.create!(:name => "Taggable 3")
84
+
85
+ taggable1.tag_list = "one, two"
86
+ taggable1.save
87
+
88
+ taggable2.tag_list = "three, four"
89
+ taggable2.save
90
+
91
+ taggable3.tag_list = "one, four"
92
+ taggable3.save
93
+
94
+ taggable1.find_related_tags.should include(taggable3)
95
+ taggable1.find_related_tags.should_not include(taggable2)
96
+ end
97
+
98
+ it "should find other related objects based on tag names on context" do
99
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
100
+ taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
101
+ taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
102
+
103
+ taggable1.tag_list = "one, two"
104
+ taggable1.save
105
+
106
+ taggable2.tag_list = "three, four"
107
+ taggable2.save
108
+
109
+ taggable3.tag_list = "one, four"
110
+ taggable3.save
111
+
112
+ taggable1.find_related_tags_for(OtherTaggableModel).should include(taggable3)
113
+ taggable1.find_related_tags_for(OtherTaggableModel).should_not include(taggable2)
114
+ end
115
+
116
+ it "should not include the object itself in the list of related objects" do
117
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
118
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
119
+
120
+ taggable1.tag_list = "one"
121
+ taggable1.save
122
+
123
+ taggable2.tag_list = "one, two"
124
+ taggable2.save
125
+
126
+ taggable1.find_related_tags.should include(taggable2)
127
+ taggable1.find_related_tags.should_not include(taggable1)
128
+ end
129
+ end
130
+
131
+ describe "Matching Contexts" do
132
+ it "should find objects with tags of matching contexts" do
133
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
134
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
135
+ taggable3 = TaggableModel.create!(:name => "Taggable 3")
136
+
137
+ taggable1.offering_list = "one, two"
138
+ taggable1.save!
139
+
140
+ taggable2.need_list = "one, two"
141
+ taggable2.save!
142
+
143
+ taggable3.offering_list = "one, two"
144
+ taggable3.save!
145
+
146
+ taggable1.find_matching_contexts(:offerings, :needs).should include(taggable2)
147
+ taggable1.find_matching_contexts(:offerings, :needs).should_not include(taggable3)
148
+ end
149
+
150
+ it "should find other related objects with tags of matching contexts" do
151
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
152
+ taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
153
+ taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
154
+
155
+ taggable1.offering_list = "one, two"
156
+ taggable1.save
157
+
158
+ taggable2.need_list = "one, two"
159
+ taggable2.save
160
+
161
+ taggable3.offering_list = "one, two"
162
+ taggable3.save
163
+
164
+ taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should include(taggable2)
165
+ taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should_not include(taggable3)
166
+ end
167
+
168
+ it "should not include the object itself in the list of related objects" do
169
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
170
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
171
+
172
+ taggable1.tag_list = "one"
173
+ taggable1.save
174
+
175
+ taggable2.tag_list = "one, two"
176
+ taggable2.save
177
+
178
+ taggable1.find_related_tags.should include(taggable2)
179
+ taggable1.find_related_tags.should_not include(taggable1)
180
+ end
181
+ end
182
+
183
+ describe 'Tagging Contexts' do
184
+ it 'should eliminate duplicate tagging contexts ' do
185
+ TaggableModel.acts_as_taggable_on(:skills, :skills)
186
+ TaggableModel.tag_types.freq[:skills].should_not == 3
187
+ end
188
+
189
+ it "should not contain embedded/nested arrays" do
190
+ TaggableModel.acts_as_taggable_on([:array], [:array])
191
+ TaggableModel.tag_types.freq[[:array]].should == 0
192
+ end
193
+
194
+ it "should _flatten_ the content of arrays" do
195
+ TaggableModel.acts_as_taggable_on([:array], [:array])
196
+ TaggableModel.tag_types.freq[:array].should == 1
197
+ end
198
+
199
+ it "should not raise an error when passed nil" do
200
+ lambda {
201
+ TaggableModel.acts_as_taggable_on()
202
+ }.should_not raise_error
203
+ end
204
+
205
+ it "should not raise an error when passed [nil]" do
206
+ lambda {
207
+ TaggableModel.acts_as_taggable_on([nil])
208
+ }.should_not raise_error
209
+ end
210
+ end
211
+
212
+ describe 'Caching' do
213
+ before(:each) do
214
+ @taggable = CachedModel.new(:name => "Bob Jones")
215
+ end
216
+
217
+ it "should add saving of tag lists and cached tag lists to the instance" do
218
+ @taggable.should respond_to(:save_cached_tag_list)
219
+ @taggable.should respond_to(:save_tags)
220
+ end
221
+
222
+ it "should generate a cached column checker for each tag type" do
223
+ CachedModel.should respond_to(:caching_tag_list?)
224
+ end
225
+
226
+ it 'should not have cached tags' do
227
+ @taggable.cached_tag_list.should be_blank
228
+ end
229
+
230
+ it 'should cache tags' do
231
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
232
+ @taggable.cached_tag_list.should == 'awesome, epic'
233
+ end
234
+
235
+ it 'should keep the cache' do
236
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
237
+ @taggable = CachedModel.find(@taggable)
238
+ @taggable.save!
239
+ @taggable.cached_tag_list.should == 'awesome, epic'
240
+ end
241
+
242
+ it 'should update the cache' do
243
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
244
+ @taggable.update_attributes(:tag_list => 'awesome')
245
+ @taggable.cached_tag_list.should == 'awesome'
246
+ end
247
+
248
+ it 'should remove the cache' do
249
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
250
+ @taggable.update_attributes(:tag_list => '')
251
+ @taggable.cached_tag_list.should be_blank
252
+ end
253
+
254
+ it 'should have a tag list' do
255
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
256
+ @taggable = CachedModel.find(@taggable.id)
257
+ @taggable.tag_list.sort.should == %w(awesome epic).sort
258
+ end
259
+
260
+ it 'should keep the tag list' do
261
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
262
+ @taggable = CachedModel.find(@taggable.id)
263
+ @taggable.save!
264
+ @taggable.tag_list.sort.should == %w(awesome epic).sort
265
+ end
266
+ end
267
+
268
+ end
@@ -0,0 +1,114 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe "acts_as_tagger" do
4
+ before(:each) do
5
+ clean_database!
6
+ end
7
+
8
+ describe "Tagger Method Generation" do
9
+ before(:each) do
10
+ @tagger = TaggableUser.new()
11
+ end
12
+
13
+ it "should add #is_tagger? query method to the class-side" do
14
+ TaggableUser.should respond_to(:is_tagger?)
15
+ end
16
+
17
+ it "should return true from the class-side #is_tagger?" do
18
+ TaggableUser.is_tagger?.should be_true
19
+ end
20
+
21
+ it "should return false from the base #is_tagger?" do
22
+ ActiveRecord::Base.is_tagger?.should be_false
23
+ end
24
+
25
+ it "should add #is_tagger? query method to the singleton" do
26
+ @tagger.should respond_to(:is_tagger?)
27
+ end
28
+
29
+ it "should add #tag method on the instance-side" do
30
+ @tagger.should respond_to(:tag)
31
+ end
32
+
33
+ it "should generate an association for #owned_taggings and #owned_tags" do
34
+ @tagger.should respond_to(:owned_taggings, :owned_tags)
35
+ end
36
+ end
37
+
38
+ describe "#tag" do
39
+ context 'when called with a non-existent tag context' do
40
+ before(:each) do
41
+ @tagger = TaggableUser.new()
42
+ @taggable = TaggableModel.new(:name=>"Richard Prior")
43
+ end
44
+
45
+ it "should by default not throw an exception " do
46
+ @taggable.tag_list_on(:foo).should be_empty
47
+ lambda {
48
+ @tagger.tag(@taggable, :with=>'this, and, that', :on=>:foo)
49
+ }.should_not raise_error
50
+ end
51
+
52
+ it 'should by default create the tag context on-the-fly' do
53
+ @taggable.tag_list_on(:here_ond_now).should be_empty
54
+ @tagger.tag(@taggable, :with=>'that', :on => :here_ond_now)
55
+ @taggable.tag_list_on(:here_ond_now).should_not include('that')
56
+ @taggable.all_tags_list_on(:here_ond_now).should include('that')
57
+ end
58
+
59
+ it "should show all the tag list when both public and owned tags exist" do
60
+ @taggable.tag_list = 'ruby, python'
61
+ @tagger.tag(@taggable, :with => 'java, lisp', :on => :tags)
62
+ @taggable.all_tags_on(:tags).map(&:name).sort.should == %w(ruby python java lisp).sort
63
+ end
64
+
65
+ it "should not add owned tags to the common list" do
66
+ @taggable.tag_list = 'ruby, python'
67
+ @tagger.tag(@taggable, :with => 'java, lisp', :on => :tags)
68
+ @taggable.tag_list.should == %w(ruby python)
69
+ @tagger.tag(@taggable, :with => '', :on => :tags)
70
+ @taggable.tag_list.should == %w(ruby python)
71
+ end
72
+
73
+ it "should throw an exception when the default is over-ridden" do
74
+ @taggable.tag_list_on(:foo_boo).should be_empty
75
+ lambda {
76
+ @tagger.tag(@taggable, :with=>'this, and, that', :on=>:foo_boo, :force=>false)
77
+ }.should raise_error
78
+ end
79
+
80
+ it "should not create the tag context on-the-fly when the default is over-ridden" do
81
+ @taggable.tag_list_on(:foo_boo).should be_empty
82
+ @tagger.tag(@taggable, :with=>'this, and, that', :on=>:foo_boo, :force=>false) rescue
83
+ @taggable.tag_list_on(:foo_boo).should be_empty
84
+ end
85
+ end
86
+
87
+ describe "when called by multiple tagger's" do
88
+ before(:each) do
89
+ @user_x = TaggableUser.create(:name => "User X")
90
+ @user_y = TaggableUser.create(:name => "User Y")
91
+ @taggable = TaggableModel.create(:name => 'acts_as_taggable_on', :tag_list => 'plugin')
92
+
93
+ @user_x.tag(@taggable, :with => 'ruby, rails', :on => :tags)
94
+ @user_y.tag(@taggable, :with => 'ruby, plugin', :on => :tags)
95
+
96
+ @user_y.tag(@taggable, :with => '', :on => :tags)
97
+ @user_y.tag(@taggable, :with => '', :on => :tags)
98
+ end
99
+
100
+ it "should delete owned tags" do
101
+ @user_y.owned_tags.should == []
102
+ end
103
+
104
+ it "should not delete other taggers tags" do
105
+ @user_x.owned_tags.should have(2).items
106
+ end
107
+
108
+ it "should not delete original tags" do
109
+ @taggable.all_tags_list_on(:tags).should include('plugin')
110
+ end
111
+ end
112
+ end
113
+
114
+ end