axtro-acts-as-taggable-on 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/CHANGELOG +25 -0
  2. data/Gemfile +7 -0
  3. data/Gemfile.lock +95 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.rdoc +221 -0
  6. data/Rakefile +59 -0
  7. data/VERSION +1 -0
  8. data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +7 -0
  9. data/generators/acts_as_taggable_on_migration/templates/migration.rb +29 -0
  10. data/lib/acts-as-taggable-on.rb +30 -0
  11. data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +53 -0
  12. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +53 -0
  13. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +130 -0
  14. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +237 -0
  15. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +101 -0
  16. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +65 -0
  17. data/lib/acts_as_taggable_on/acts_as_tagger.rb +67 -0
  18. data/lib/acts_as_taggable_on/compatibility/Gemfile +6 -0
  19. data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +17 -0
  20. data/lib/acts_as_taggable_on/tag.rb +65 -0
  21. data/lib/acts_as_taggable_on/tag_list.rb +95 -0
  22. data/lib/acts_as_taggable_on/tagging.rb +23 -0
  23. data/lib/acts_as_taggable_on/tags_helper.rb +17 -0
  24. data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +31 -0
  25. data/lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb +28 -0
  26. data/rails/init.rb +1 -0
  27. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +266 -0
  28. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +114 -0
  29. data/spec/acts_as_taggable_on/tag_list_spec.rb +70 -0
  30. data/spec/acts_as_taggable_on/tag_spec.rb +115 -0
  31. data/spec/acts_as_taggable_on/taggable_spec.rb +311 -0
  32. data/spec/acts_as_taggable_on/tagger_spec.rb +91 -0
  33. data/spec/acts_as_taggable_on/tagging_spec.rb +31 -0
  34. data/spec/acts_as_taggable_on/tags_helper_spec.rb +28 -0
  35. data/spec/bm.rb +52 -0
  36. data/spec/models.rb +31 -0
  37. data/spec/schema.rb +43 -0
  38. data/spec/spec.opts +2 -0
  39. data/spec/spec_helper.rb +53 -0
  40. metadata +169 -0
@@ -0,0 +1,17 @@
1
+ module TagsHelper
2
+
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
+
17
+ end
@@ -0,0 +1,31 @@
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)
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
@@ -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
@@ -0,0 +1 @@
1
+ require 'acts-as-taggable-on'
@@ -0,0 +1,266 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
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
+ [TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
15
+ @taggable = TaggableModel.new(:name => "Bob Jones")
16
+ end
17
+
18
+ it "should respond 'true' to taggable?" do
19
+ @taggable.class.should be_taggable
20
+ end
21
+
22
+ it "should create a class attribute for tag types" do
23
+ @taggable.class.should respond_to(:tag_types)
24
+ end
25
+
26
+ it "should create an instance attribute for tag types" do
27
+ @taggable.should respond_to(:tag_types)
28
+ end
29
+
30
+ it "should have all tag types" do
31
+ @taggable.tag_types.should == [:tags, :languages, :skills, :needs, :offerings]
32
+ end
33
+
34
+ it "should generate an association for each tag type" do
35
+ @taggable.should respond_to(:tags, :skills, :languages)
36
+ end
37
+
38
+ it "should add tagged_with and tag_counts to singleton" do
39
+ TaggableModel.should respond_to(:tagged_with, :tag_counts)
40
+ end
41
+
42
+ it "should generate a tag_list accessor/setter for each tag type" do
43
+ @taggable.should respond_to(:tag_list, :skill_list, :language_list)
44
+ @taggable.should respond_to(:tag_list=, :skill_list=, :language_list=)
45
+ end
46
+
47
+ it "should generate a tag_list accessor, that includes owned tags, for each tag type" do
48
+ @taggable.should respond_to(:all_tags_list, :all_skills_list, :all_languages_list)
49
+ end
50
+ end
51
+
52
+ describe "Single Table Inheritance" do
53
+ before do
54
+ @taggable = TaggableModel.new(:name => "taggable")
55
+ @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
56
+ @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
57
+ end
58
+
59
+ it "should pass on tag contexts to STI-inherited models" do
60
+ @inherited_same.should respond_to(:tag_list, :skill_list, :language_list)
61
+ @inherited_different.should respond_to(:tag_list, :skill_list, :language_list)
62
+ end
63
+
64
+ it "should have tag contexts added in altered STI models" do
65
+ @inherited_different.should respond_to(:part_list)
66
+ end
67
+ end
68
+
69
+ describe "Reloading" do
70
+ it "should save a model instantiated by Model.find" do
71
+ taggable = TaggableModel.create!(:name => "Taggable")
72
+ found_taggable = TaggableModel.find(taggable.id)
73
+ found_taggable.save
74
+ end
75
+ end
76
+
77
+ describe "Related Objects" do
78
+ it "should find related objects based on tag names on context" do
79
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
80
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
81
+ taggable3 = TaggableModel.create!(:name => "Taggable 3")
82
+
83
+ taggable1.tag_list = "one, two"
84
+ taggable1.save
85
+
86
+ taggable2.tag_list = "three, four"
87
+ taggable2.save
88
+
89
+ taggable3.tag_list = "one, four"
90
+ taggable3.save
91
+
92
+ taggable1.find_related_tags.should include(taggable3)
93
+ taggable1.find_related_tags.should_not include(taggable2)
94
+ end
95
+
96
+ it "should find other related objects based on tag names on context" do
97
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
98
+ taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
99
+ taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
100
+
101
+ taggable1.tag_list = "one, two"
102
+ taggable1.save
103
+
104
+ taggable2.tag_list = "three, four"
105
+ taggable2.save
106
+
107
+ taggable3.tag_list = "one, four"
108
+ taggable3.save
109
+
110
+ taggable1.find_related_tags_for(OtherTaggableModel).should include(taggable3)
111
+ taggable1.find_related_tags_for(OtherTaggableModel).should_not include(taggable2)
112
+ end
113
+
114
+ it "should not include the object itself in the list of related objects" do
115
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
116
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
117
+
118
+ taggable1.tag_list = "one"
119
+ taggable1.save
120
+
121
+ taggable2.tag_list = "one, two"
122
+ taggable2.save
123
+
124
+ taggable1.find_related_tags.should include(taggable2)
125
+ taggable1.find_related_tags.should_not include(taggable1)
126
+ end
127
+ end
128
+
129
+ describe "Matching Contexts" do
130
+ it "should find objects with tags of matching contexts" do
131
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
132
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
133
+ taggable3 = TaggableModel.create!(:name => "Taggable 3")
134
+
135
+ taggable1.offering_list = "one, two"
136
+ taggable1.save!
137
+
138
+ taggable2.need_list = "one, two"
139
+ taggable2.save!
140
+
141
+ taggable3.offering_list = "one, two"
142
+ taggable3.save!
143
+
144
+ taggable1.find_matching_contexts(:offerings, :needs).should include(taggable2)
145
+ taggable1.find_matching_contexts(:offerings, :needs).should_not include(taggable3)
146
+ end
147
+
148
+ it "should find other related objects with tags of matching contexts" do
149
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
150
+ taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
151
+ taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
152
+
153
+ taggable1.offering_list = "one, two"
154
+ taggable1.save
155
+
156
+ taggable2.need_list = "one, two"
157
+ taggable2.save
158
+
159
+ taggable3.offering_list = "one, two"
160
+ taggable3.save
161
+
162
+ taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should include(taggable2)
163
+ taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should_not include(taggable3)
164
+ end
165
+
166
+ it "should not include the object itself in the list of related objects" do
167
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
168
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
169
+
170
+ taggable1.tag_list = "one"
171
+ taggable1.save
172
+
173
+ taggable2.tag_list = "one, two"
174
+ taggable2.save
175
+
176
+ taggable1.find_related_tags.should include(taggable2)
177
+ taggable1.find_related_tags.should_not include(taggable1)
178
+ end
179
+ end
180
+
181
+ describe 'Tagging Contexts' do
182
+ it 'should eliminate duplicate tagging contexts ' do
183
+ TaggableModel.acts_as_taggable_on(:skills, :skills)
184
+ TaggableModel.tag_types.freq[:skills].should_not == 3
185
+ end
186
+
187
+ it "should not contain embedded/nested arrays" do
188
+ TaggableModel.acts_as_taggable_on([:array], [:array])
189
+ TaggableModel.tag_types.freq[[:array]].should == 0
190
+ end
191
+
192
+ it "should _flatten_ the content of arrays" do
193
+ TaggableModel.acts_as_taggable_on([:array], [:array])
194
+ TaggableModel.tag_types.freq[:array].should == 1
195
+ end
196
+
197
+ it "should not raise an error when passed nil" do
198
+ lambda {
199
+ TaggableModel.acts_as_taggable_on()
200
+ }.should_not raise_error
201
+ end
202
+
203
+ it "should not raise an error when passed [nil]" do
204
+ lambda {
205
+ TaggableModel.acts_as_taggable_on([nil])
206
+ }.should_not raise_error
207
+ end
208
+ end
209
+
210
+ describe 'Caching' do
211
+ before(:each) do
212
+ @taggable = CachedModel.new(:name => "Bob Jones")
213
+ end
214
+
215
+ it "should add saving of tag lists and cached tag lists to the instance" do
216
+ @taggable.should respond_to(:save_cached_tag_list)
217
+ @taggable.should respond_to(:save_tags)
218
+ end
219
+
220
+ it "should generate a cached column checker for each tag type" do
221
+ CachedModel.should respond_to(:caching_tag_list?)
222
+ end
223
+
224
+ it 'should not have cached tags' do
225
+ @taggable.cached_tag_list.should be_blank
226
+ end
227
+
228
+ it 'should cache tags' do
229
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
230
+ @taggable.cached_tag_list.should == 'awesome, epic'
231
+ end
232
+
233
+ it 'should keep the cache' do
234
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
235
+ @taggable = CachedModel.find(@taggable)
236
+ @taggable.save!
237
+ @taggable.cached_tag_list.should == 'awesome, epic'
238
+ end
239
+
240
+ it 'should update the cache' do
241
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
242
+ @taggable.update_attributes(:tag_list => 'awesome')
243
+ @taggable.cached_tag_list.should == 'awesome'
244
+ end
245
+
246
+ it 'should remove the cache' do
247
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
248
+ @taggable.update_attributes(:tag_list => '')
249
+ @taggable.cached_tag_list.should be_blank
250
+ end
251
+
252
+ it 'should have a tag list' do
253
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
254
+ @taggable = CachedModel.find(@taggable.id)
255
+ @taggable.tag_list.sort.should == %w(awesome epic).sort
256
+ end
257
+
258
+ it 'should keep the tag list' do
259
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
260
+ @taggable = CachedModel.find(@taggable.id)
261
+ @taggable.save!
262
+ @taggable.tag_list.sort.should == %w(awesome epic).sort
263
+ end
264
+ end
265
+
266
+ end
@@ -0,0 +1,114 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe "acts_as_tagger" do
4
+ before(:each) do
5
+ clean_database!
6
+ end
7
+
8
+ context "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
+ context "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