crowdint_acts-as-taggable-on 2.3.2

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 (45) hide show
  1. data/.gitignore +10 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +9 -0
  4. data/CHANGELOG +35 -0
  5. data/Gemfile +3 -0
  6. data/Guardfile +5 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.rdoc +250 -0
  9. data/Rakefile +13 -0
  10. data/acts-as-taggable-on.gemspec +28 -0
  11. data/lib/acts-as-taggable-on.rb +59 -0
  12. data/lib/acts-as-taggable-on/version.rb +4 -0
  13. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +53 -0
  14. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +127 -0
  15. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +349 -0
  16. data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +37 -0
  17. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +99 -0
  18. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +73 -0
  19. data/lib/acts_as_taggable_on/tag.rb +77 -0
  20. data/lib/acts_as_taggable_on/tag_list.rb +97 -0
  21. data/lib/acts_as_taggable_on/taggable.rb +102 -0
  22. data/lib/acts_as_taggable_on/tagger.rb +67 -0
  23. data/lib/acts_as_taggable_on/tagging.rb +34 -0
  24. data/lib/acts_as_taggable_on/tags_helper.rb +17 -0
  25. data/lib/acts_as_taggable_on/utils.rb +34 -0
  26. data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +39 -0
  27. data/lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb +30 -0
  28. data/rails/init.rb +1 -0
  29. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +514 -0
  30. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +114 -0
  31. data/spec/acts_as_taggable_on/tag_list_spec.rb +93 -0
  32. data/spec/acts_as_taggable_on/tag_spec.rb +153 -0
  33. data/spec/acts_as_taggable_on/taggable_spec.rb +543 -0
  34. data/spec/acts_as_taggable_on/tagger_spec.rb +112 -0
  35. data/spec/acts_as_taggable_on/tagging_spec.rb +28 -0
  36. data/spec/acts_as_taggable_on/tags_helper_spec.rb +44 -0
  37. data/spec/acts_as_taggable_on/utils_spec.rb +21 -0
  38. data/spec/bm.rb +52 -0
  39. data/spec/database.yml.sample +19 -0
  40. data/spec/generators/acts_as_taggable_on/migration/migration_generator_spec.rb +22 -0
  41. data/spec/models.rb +49 -0
  42. data/spec/schema.rb +61 -0
  43. data/spec/spec_helper.rb +83 -0
  44. data/uninstall.rb +1 -0
  45. metadata +240 -0
@@ -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
@@ -0,0 +1,93 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe ActsAsTaggableOn::TagList do
4
+ let(:tag_list) { ActsAsTaggableOn::TagList.new("awesome","radical") }
5
+
6
+ it { should be_kind_of Array }
7
+
8
+ it "#from should return empty array if empty array is passed" do
9
+ ActsAsTaggableOn::TagList.from([]).should be_empty
10
+ end
11
+
12
+ describe "#add" do
13
+ it "should be able to be add a new tag word" do
14
+ tag_list.add("cool")
15
+ tag_list.include?("cool").should be_true
16
+ end
17
+
18
+ it "should be able to add delimited lists of words" do
19
+ tag_list.add("cool, wicked", :parse => true)
20
+ tag_list.should include("cool", "wicked")
21
+ end
22
+
23
+ it "should be able to add delimited list of words with quoted delimiters" do
24
+ tag_list.add("'cool, wicked', \"really cool, really wicked\"", :parse => true)
25
+ tag_list.should include("cool, wicked", "really cool, really wicked")
26
+ end
27
+
28
+ it "should be able to handle other uses of quotation marks correctly" do
29
+ tag_list.add("john's cool car, mary's wicked toy", :parse => true)
30
+ tag_list.should include("john's cool car", "mary's wicked toy")
31
+ end
32
+
33
+ it "should be able to add an array of words" do
34
+ tag_list.add(["cool", "wicked"], :parse => true)
35
+ tag_list.should include("cool", "wicked")
36
+ end
37
+
38
+ it "should quote escape tags with commas in them" do
39
+ tag_list.add("cool","rad,bodacious")
40
+ tag_list.to_s.should == "awesome, radical, cool, \"rad,bodacious\""
41
+ end
42
+
43
+ end
44
+
45
+ describe "#remove" do
46
+ it "should be able to remove words" do
47
+ tag_list.remove("awesome")
48
+ tag_list.should_not include("awesome")
49
+ end
50
+
51
+ it "should be able to remove delimited lists of words" do
52
+ tag_list.remove("awesome, radical", :parse => true)
53
+ tag_list.should be_empty
54
+ end
55
+
56
+ it "should be able to remove an array of words" do
57
+ tag_list.remove(["awesome", "radical"], :parse => true)
58
+ tag_list.should be_empty
59
+ end
60
+ end
61
+
62
+ describe "#to_s" do
63
+ it "should give a delimited list of words when converted to string" do
64
+ tag_list.to_s.should == "awesome, radical"
65
+ end
66
+
67
+ it "should be able to call to_s on a frozen tag list" do
68
+ tag_list.freeze
69
+ lambda { tag_list.add("cool","rad,bodacious") }.should raise_error
70
+ lambda { tag_list.to_s }.should_not raise_error
71
+ end
72
+ end
73
+
74
+ describe "cleaning" do
75
+ it "should parameterize if force_parameterize is set to true" do
76
+ ActsAsTaggableOn.force_parameterize = true
77
+ tag_list = ActsAsTaggableOn::TagList.new("awesome()","radical)(cc")
78
+
79
+ tag_list.to_s.should == "awesome, radical-cc"
80
+ ActsAsTaggableOn.force_parameterize = false
81
+ end
82
+
83
+ it "should lowercase if force_lowercase is set to true" do
84
+ ActsAsTaggableOn.force_lowercase = true
85
+
86
+ tag_list = ActsAsTaggableOn::TagList.new("aweSomE","RaDicaL")
87
+ tag_list.to_s.should == "awesome, radical"
88
+
89
+ ActsAsTaggableOn.force_lowercase = false
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,153 @@
1
+ #encoding: utf-8
2
+
3
+ require File.expand_path('../../spec_helper', __FILE__)
4
+
5
+ describe ActsAsTaggableOn::Tag do
6
+ before(:each) do
7
+ clean_database!
8
+ @tag = ActsAsTaggableOn::Tag.new
9
+ @user = TaggableModel.create(:name => "Pablo")
10
+ end
11
+
12
+ describe "named like any" do
13
+ before(:each) do
14
+ ActsAsTaggableOn::Tag.create(:name => "Awesome")
15
+ ActsAsTaggableOn::Tag.create(:name => "awesome")
16
+ ActsAsTaggableOn::Tag.create(:name => "epic")
17
+ end
18
+
19
+ it "should find both tags" do
20
+ ActsAsTaggableOn::Tag.named_like_any(["awesome", "epic"]).should have(3).items
21
+ end
22
+ end
23
+
24
+ describe "find or create by name" do
25
+ before(:each) do
26
+ @tag.name = "awesome"
27
+ @tag.save
28
+ end
29
+
30
+ it "should find by name" do
31
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("awesome").should == @tag
32
+ end
33
+
34
+ it "should find by name case insensitive" do
35
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("AWESOME").should == @tag
36
+ end
37
+
38
+ it "should create by name" do
39
+ lambda {
40
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("epic")
41
+ }.should change(ActsAsTaggableOn::Tag, :count).by(1)
42
+ end
43
+ end
44
+
45
+ unless ActsAsTaggableOn::Tag.using_sqlite?
46
+ describe "find or create by unicode name" do
47
+ before(:each) do
48
+ @tag.name = "привет"
49
+ @tag.save
50
+ end
51
+
52
+ it "should find by name" do
53
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("привет").should == @tag
54
+ end
55
+
56
+ it "should find by name case insensitive" do
57
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("ПРИВЕТ").should == @tag
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "find or create all by any name" do
63
+ before(:each) do
64
+ @tag.name = "awesome"
65
+ @tag.save
66
+ end
67
+
68
+ it "should find by name" do
69
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name("awesome").should == [@tag]
70
+ end
71
+
72
+ it "should find by name case insensitive" do
73
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name("AWESOME").should == [@tag]
74
+ end
75
+
76
+ it "should create by name" do
77
+ lambda {
78
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name("epic")
79
+ }.should change(ActsAsTaggableOn::Tag, :count).by(1)
80
+ end
81
+
82
+ it "should find or create by name" do
83
+ lambda {
84
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name("awesome", "epic").map(&:name).should == ["awesome", "epic"]
85
+ }.should change(ActsAsTaggableOn::Tag, :count).by(1)
86
+ end
87
+
88
+ it "should return an empty array if no tags are specified" do
89
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name([]).should == []
90
+ end
91
+ end
92
+
93
+ it "should require a name" do
94
+ @tag.valid?
95
+
96
+ @tag.errors[:name].should == ["can't be blank"]
97
+
98
+ @tag.name = "something"
99
+ @tag.valid?
100
+
101
+ @tag.errors[:name].should == []
102
+ end
103
+
104
+ it "should limit the name length to 255 or less characters" do
105
+ @tag.name = "fgkgnkkgjymkypbuozmwwghblmzpqfsgjasflblywhgkwndnkzeifalfcpeaeqychjuuowlacmuidnnrkprgpcpybarbkrmziqihcrxirlokhnzfvmtzixgvhlxzncyywficpraxfnjptxxhkqmvicbcdcynkjvziefqzyndxkjmsjlvyvbwraklbalykyxoliqdlreeykuphdtmzfdwpphmrqvwvqffojkqhlzvinqajsxbszyvrqqyzusxranr"
106
+ @tag.valid?
107
+ @tag.errors[:name].should == ["is too long (maximum is 255 characters)"]
108
+
109
+ @tag.name = "fgkgnkkgjymkypbuozmwwghblmzpqfsgjasflblywhgkwndnkzeifalfcpeaeqychjuuowlacmuidnnrkprgpcpybarbkrmziqihcrxirlokhnzfvmtzixgvhlxzncyywficpraxfnjptxxhkqmvicbcdcynkjvziefqzyndxkjmsjlvyvbwraklbalykyxoliqdlreeykuphdtmzfdwpphmrqvwvqffojkqhlzvinqajsxbszyvrqqyzusxran"
110
+ @tag.valid?
111
+ @tag.errors[:name].should == []
112
+ end
113
+
114
+ it "should equal a tag with the same name" do
115
+ @tag.name = "awesome"
116
+ new_tag = ActsAsTaggableOn::Tag.new(:name => "awesome")
117
+ new_tag.should == @tag
118
+ end
119
+
120
+ it "should return its name when to_s is called" do
121
+ @tag.name = "cool"
122
+ @tag.to_s.should == "cool"
123
+ end
124
+
125
+ it "have named_scope named(something)" do
126
+ @tag.name = "cool"
127
+ @tag.save!
128
+ ActsAsTaggableOn::Tag.named('cool').should include(@tag)
129
+ end
130
+
131
+ it "have named_scope named_like(something)" do
132
+ @tag.name = "cool"
133
+ @tag.save!
134
+ @another_tag = ActsAsTaggableOn::Tag.create!(:name => "coolip")
135
+ ActsAsTaggableOn::Tag.named_like('cool').should include(@tag, @another_tag)
136
+ end
137
+
138
+ describe "escape wildcard symbols in like requests" do
139
+ before(:each) do
140
+ @tag.name = "cool"
141
+ @tag.save
142
+ @another_tag = ActsAsTaggableOn::Tag.create!(:name => "coo%")
143
+ @another_tag2 = ActsAsTaggableOn::Tag.create!(:name => "coolish")
144
+ end
145
+
146
+ it "return escaped result when '%' char present in tag" do
147
+ ActsAsTaggableOn::Tag.named_like('coo%').should_not include(@tag)
148
+ ActsAsTaggableOn::Tag.named_like('coo%').should include(@another_tag)
149
+ end
150
+
151
+ end
152
+
153
+ end
@@ -0,0 +1,543 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe "Taggable To Preserve Order" do
4
+ before(:each) do
5
+ clean_database!
6
+ @taggable = OrderedTaggableModel.new(:name => "Bob Jones")
7
+ end
8
+
9
+ it "should have tag types" do
10
+ [:tags, :colours].each do |type|
11
+ OrderedTaggableModel.tag_types.should include type
12
+ end
13
+
14
+ @taggable.tag_types.should == OrderedTaggableModel.tag_types
15
+ end
16
+
17
+ it "should have tag associations" do
18
+ [:tags, :colours].each do |type|
19
+ @taggable.respond_to?(type).should be_true
20
+ @taggable.respond_to?("#{type.to_s.singularize}_taggings").should be_true
21
+ end
22
+ end
23
+
24
+ it "should have tag associations ordered by id" do
25
+ [:tags, :colours].each do |type|
26
+ OrderedTaggableModel.reflect_on_association(type).options[:order].should include('id')
27
+ OrderedTaggableModel.reflect_on_association("#{type.to_s.singularize}_taggings".to_sym).options[:order].should include('id')
28
+ end
29
+ end
30
+
31
+ it "should have tag methods" do
32
+ [:tags, :colours].each do |type|
33
+ @taggable.respond_to?("#{type.to_s.singularize}_list").should be_true
34
+ @taggable.respond_to?("#{type.to_s.singularize}_list=").should be_true
35
+ @taggable.respond_to?("all_#{type.to_s}_list").should be_true
36
+ end
37
+ end
38
+
39
+ it "should return tag list in the order the tags were created" do
40
+ # create
41
+ @taggable.tag_list = "rails, ruby, css"
42
+ @taggable.instance_variable_get("@tag_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
43
+
44
+ lambda {
45
+ @taggable.save
46
+ }.should change(ActsAsTaggableOn::Tag, :count).by(3)
47
+
48
+ @taggable.reload
49
+ @taggable.tag_list.should == %w(rails ruby css)
50
+
51
+ # update
52
+ @taggable.tag_list = "pow, ruby, rails"
53
+ @taggable.save
54
+
55
+ @taggable.reload
56
+ @taggable.tag_list.should == %w(pow ruby rails)
57
+
58
+ # update with no change
59
+ @taggable.tag_list = "pow, ruby, rails"
60
+ @taggable.save
61
+
62
+ @taggable.reload
63
+ @taggable.tag_list.should == %w(pow ruby rails)
64
+
65
+ # update to clear tags
66
+ @taggable.tag_list = ""
67
+ @taggable.save
68
+
69
+ @taggable.reload
70
+ @taggable.tag_list.should == []
71
+ end
72
+
73
+ it "should return tag objects in the order the tags were created" do
74
+ # create
75
+ @taggable.tag_list = "pow, ruby, rails"
76
+ @taggable.instance_variable_get("@tag_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
77
+
78
+ lambda {
79
+ @taggable.save
80
+ }.should change(ActsAsTaggableOn::Tag, :count).by(3)
81
+
82
+ @taggable.reload
83
+ @taggable.tags.map{|t| t.name}.should == %w(pow ruby rails)
84
+
85
+ # update
86
+ @taggable.tag_list = "rails, ruby, css, pow"
87
+ @taggable.save
88
+
89
+ @taggable.reload
90
+ @taggable.tags.map{|t| t.name}.should == %w(rails ruby css pow)
91
+ end
92
+
93
+ it "should return tag objects in tagging id order" do
94
+ # create
95
+ @taggable.tag_list = "pow, ruby, rails"
96
+ @taggable.save
97
+
98
+ @taggable.reload
99
+ ids = @taggable.tags.map{|t| t.taggings.first.id}
100
+ ids.should == ids.sort
101
+
102
+ # update
103
+ @taggable.tag_list = "rails, ruby, css, pow"
104
+ @taggable.save
105
+
106
+ @taggable.reload
107
+ ids = @taggable.tags.map{|t| t.taggings.first.id}
108
+ ids.should == ids.sort
109
+ end
110
+ end
111
+
112
+ describe "Taggable" do
113
+ before(:each) do
114
+ clean_database!
115
+ @taggable = TaggableModel.new(:name => "Bob Jones")
116
+ @taggables = [@taggable, TaggableModel.new(:name => "John Doe")]
117
+ end
118
+
119
+ it "should have tag types" do
120
+ [:tags, :languages, :skills, :needs, :offerings].each do |type|
121
+ TaggableModel.tag_types.should include type
122
+ end
123
+
124
+ @taggable.tag_types.should == TaggableModel.tag_types
125
+ end
126
+
127
+ it "should have tag_counts_on" do
128
+ TaggableModel.tag_counts_on(:tags).all.should be_empty
129
+
130
+ @taggable.tag_list = ["awesome", "epic"]
131
+ @taggable.save
132
+
133
+ TaggableModel.tag_counts_on(:tags).length.should == 2
134
+ @taggable.tag_counts_on(:tags).length.should == 2
135
+ end
136
+
137
+ it "should return [] right after create" do
138
+ blank_taggable = TaggableModel.new(:name => "Bob Jones")
139
+ blank_taggable.tag_list.should == []
140
+ end
141
+
142
+ it "should be able to create tags" do
143
+ @taggable.skill_list = "ruby, rails, css"
144
+ @taggable.instance_variable_get("@skill_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
145
+
146
+ lambda {
147
+ @taggable.save
148
+ }.should change(ActsAsTaggableOn::Tag, :count).by(3)
149
+
150
+ @taggable.reload
151
+ @taggable.skill_list.sort.should == %w(ruby rails css).sort
152
+ end
153
+
154
+ it "should be able to create tags through the tag list directly" do
155
+ @taggable.tag_list_on(:test).add("hello")
156
+ @taggable.tag_list_cache_on(:test).should_not be_empty
157
+ @taggable.tag_list_on(:test).should == ["hello"]
158
+
159
+ @taggable.save
160
+ @taggable.save_tags
161
+
162
+ @taggable.reload
163
+ @taggable.tag_list_on(:test).should == ["hello"]
164
+ end
165
+
166
+ it "should differentiate between contexts" do
167
+ @taggable.skill_list = "ruby, rails, css"
168
+ @taggable.tag_list = "ruby, bob, charlie"
169
+ @taggable.save
170
+ @taggable.reload
171
+ @taggable.skill_list.should include("ruby")
172
+ @taggable.skill_list.should_not include("bob")
173
+ end
174
+
175
+ it "should be able to remove tags through list alone" do
176
+ @taggable.skill_list = "ruby, rails, css"
177
+ @taggable.save
178
+ @taggable.reload
179
+ @taggable.should have(3).skills
180
+ @taggable.skill_list = "ruby, rails"
181
+ @taggable.save
182
+ @taggable.reload
183
+ @taggable.should have(2).skills
184
+ end
185
+
186
+ it "should be able to select taggables by subset of tags using ActiveRelation methods" do
187
+ @taggables[0].tag_list = "bob"
188
+ @taggables[1].tag_list = "charlie"
189
+ @taggables[0].skill_list = "ruby"
190
+ @taggables[1].skill_list = "css"
191
+ @taggables.each{|taggable| taggable.save}
192
+
193
+ @found_taggables_by_tag = TaggableModel.joins(:tags).where(:tags => {:name => ["bob"]})
194
+ @found_taggables_by_skill = TaggableModel.joins(:skills).where(:tags => {:name => ["ruby"]})
195
+
196
+ @found_taggables_by_tag.should include @taggables[0]
197
+ @found_taggables_by_tag.should_not include @taggables[1]
198
+ @found_taggables_by_skill.should include @taggables[0]
199
+ @found_taggables_by_skill.should_not include @taggables[1]
200
+ end
201
+
202
+ it "should be able to find by tag" do
203
+ @taggable.skill_list = "ruby, rails, css"
204
+ @taggable.save
205
+
206
+ TaggableModel.tagged_with("ruby").first.should == @taggable
207
+ end
208
+
209
+ it "should be able to find by tag with context" do
210
+ @taggable.skill_list = "ruby, rails, css"
211
+ @taggable.tag_list = "bob, charlie"
212
+ @taggable.save
213
+
214
+ TaggableModel.tagged_with("ruby").first.should == @taggable
215
+ TaggableModel.tagged_with("ruby, css").first.should == @taggable
216
+ TaggableModel.tagged_with("bob", :on => :skills).first.should_not == @taggable
217
+ TaggableModel.tagged_with("bob", :on => :tags).first.should == @taggable
218
+ end
219
+
220
+ it "should not care about case" do
221
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby")
222
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "Ruby")
223
+
224
+ ActsAsTaggableOn::Tag.find(:all).size.should == 1
225
+ TaggableModel.tagged_with("ruby").to_a.should == TaggableModel.tagged_with("Ruby").to_a
226
+ end
227
+
228
+ it "should be able to get tag counts on model as a whole" do
229
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
230
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
231
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
232
+ TaggableModel.tag_counts.all.should_not be_empty
233
+ TaggableModel.skill_counts.all.should_not be_empty
234
+ end
235
+
236
+ it "should be able to get all tag counts on model as whole" do
237
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
238
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
239
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
240
+
241
+ TaggableModel.all_tag_counts.all.should_not be_empty
242
+ TaggableModel.all_tag_counts(:order => 'tags.id').first.count.should == 3 # ruby
243
+ end
244
+
245
+ it "should be able to use named scopes to chain tag finds by any tags by context" do
246
+ bob = TaggableModel.create(:name => "Bob", :need_list => "rails", :offering_list => "c++")
247
+ frank = TaggableModel.create(:name => "Frank", :need_list => "css", :offering_list => "css")
248
+ steve = TaggableModel.create(:name => 'Steve', :need_list => "c++", :offering_list => "java")
249
+
250
+ # Let's only find those who need rails or css and are offering c++ or java
251
+ TaggableModel.tagged_with(['rails, css'], :on => :needs, :any => true).tagged_with(['c++', 'java'], :on => :offerings, :any => true).to_a.should == [bob]
252
+ end
253
+
254
+ it "should not return read-only records" do
255
+ TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
256
+ TaggableModel.tagged_with("ruby").first.should_not be_readonly
257
+ end
258
+
259
+ it "should be able to get scoped tag counts" do
260
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
261
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
262
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
263
+
264
+ TaggableModel.tagged_with("ruby").tag_counts(:order => 'tags.id').first.count.should == 2 # ruby
265
+ TaggableModel.tagged_with("ruby").skill_counts.first.count.should == 1 # ruby
266
+ end
267
+
268
+ it "should be able to get all scoped tag counts" do
269
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
270
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
271
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
272
+
273
+ TaggableModel.tagged_with("ruby").all_tag_counts(:order => 'tags.id').first.count.should == 3 # ruby
274
+ end
275
+
276
+ it 'should only return tag counts for the available scope' do
277
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
278
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
279
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby, java")
280
+
281
+ TaggableModel.tagged_with('rails').all_tag_counts.should have(3).items
282
+ TaggableModel.tagged_with('rails').all_tag_counts.any? { |tag| tag.name == 'java' }.should be_false
283
+
284
+ # Test specific join syntaxes:
285
+ frank.untaggable_models.create!
286
+ TaggableModel.tagged_with('rails').scoped(:joins => :untaggable_models).all_tag_counts.should have(2).items
287
+ TaggableModel.tagged_with('rails').scoped(:joins => { :untaggable_models => :taggable_model }).all_tag_counts.should have(2).items
288
+ TaggableModel.tagged_with('rails').scoped(:joins => [:untaggable_models]).all_tag_counts.should have(2).items
289
+ end
290
+
291
+ it "should be able to set a custom tag context list" do
292
+ bob = TaggableModel.create(:name => "Bob")
293
+ bob.set_tag_list_on(:rotors, "spinning, jumping")
294
+ bob.tag_list_on(:rotors).should == ["spinning","jumping"]
295
+ bob.save
296
+ bob.reload
297
+ bob.tags_on(:rotors).should_not be_empty
298
+ end
299
+
300
+ it "should be able to find tagged" do
301
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
302
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
303
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
304
+
305
+ TaggableModel.tagged_with("ruby", :order => 'taggable_models.name').to_a.should == [bob, frank, steve]
306
+ TaggableModel.tagged_with("ruby, rails", :order => 'taggable_models.name').to_a.should == [bob, frank]
307
+ TaggableModel.tagged_with(["ruby", "rails"], :order => 'taggable_models.name').to_a.should == [bob, frank]
308
+ end
309
+
310
+ it "should be able to find tagged with quotation marks" do
311
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive, 'I love the ,comma,'")
312
+ TaggableModel.tagged_with("'I love the ,comma,'").should include(bob)
313
+ end
314
+
315
+ it "should be able to find tagged with invalid tags" do
316
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive")
317
+ TaggableModel.tagged_with("sad, happier").should_not include(bob)
318
+ end
319
+
320
+ it "should be able to find tagged with any tag" do
321
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
322
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
323
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
324
+
325
+ TaggableModel.tagged_with(["ruby", "java"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank, steve]
326
+ TaggableModel.tagged_with(["c++", "fitter"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, steve]
327
+ TaggableModel.tagged_with(["depressed", "css"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank]
328
+ end
329
+
330
+ context "wild: true" do
331
+ it "should use params as wildcards" do
332
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "bob, tricia")
333
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "bobby, jim")
334
+ steve = TaggableModel.create(:name => "Steve", :tag_list => "john, patricia")
335
+ jim = TaggableModel.create(:name => "Jim", :tag_list => "jim, steve")
336
+
337
+
338
+ TaggableModel.tagged_with(["bob", "tricia"], :wild => true, :any => true).to_a.sort_by{|o| o.id}.should == [bob, frank, steve]
339
+ TaggableModel.tagged_with(["bob", "tricia"], :wild => true, :exclude => true).to_a.should == [jim]
340
+ end
341
+ end
342
+
343
+ it "should be able to find tagged on a custom tag context" do
344
+ bob = TaggableModel.create(:name => "Bob")
345
+ bob.set_tag_list_on(:rotors, "spinning, jumping")
346
+ bob.tag_list_on(:rotors).should == ["spinning","jumping"]
347
+ bob.save
348
+
349
+ TaggableModel.tagged_with("spinning", :on => :rotors).to_a.should == [bob]
350
+ end
351
+
352
+ it "should be able to use named scopes to chain tag finds" do
353
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
354
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
355
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, python')
356
+
357
+ # Let's only find those productive Rails developers
358
+ TaggableModel.tagged_with('rails', :on => :skills, :order => 'taggable_models.name').to_a.should == [bob, frank]
359
+ TaggableModel.tagged_with('happier', :on => :tags, :order => 'taggable_models.name').to_a.should == [bob, steve]
360
+ TaggableModel.tagged_with('rails', :on => :skills).tagged_with('happier', :on => :tags).to_a.should == [bob]
361
+ TaggableModel.tagged_with('rails').tagged_with('happier', :on => :tags).to_a.should == [bob]
362
+ end
363
+
364
+ it "should be able to find tagged with only the matching tags" do
365
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "lazy, happier")
366
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "fitter, happier, inefficient")
367
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => "fitter, happier")
368
+
369
+ TaggableModel.tagged_with("fitter, happier", :match_all => true).to_a.should == [steve]
370
+ end
371
+
372
+ it "should be able to find tagged with some excluded tags" do
373
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "happier, lazy")
374
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "happier")
375
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => "happier")
376
+
377
+ TaggableModel.tagged_with("lazy", :exclude => true).to_a.should == [frank, steve]
378
+ end
379
+
380
+ it "should return an empty scope for empty tags" do
381
+ TaggableModel.tagged_with('').should == []
382
+ TaggableModel.tagged_with(' ').should == []
383
+ TaggableModel.tagged_with(nil).should == []
384
+ TaggableModel.tagged_with([]).should == []
385
+ end
386
+
387
+ it "should not create duplicate taggings" do
388
+ bob = TaggableModel.create(:name => "Bob")
389
+ lambda {
390
+ bob.tag_list << "happier"
391
+ bob.tag_list << "happier"
392
+ bob.save
393
+ }.should change(ActsAsTaggableOn::Tagging, :count).by(1)
394
+ end
395
+
396
+ describe "Associations" do
397
+ before(:each) do
398
+ @taggable = TaggableModel.create(:tag_list => "awesome, epic")
399
+ end
400
+
401
+ it "should not remove tags when creating associated objects" do
402
+ @taggable.untaggable_models.create!
403
+ @taggable.reload
404
+ @taggable.tag_list.should have(2).items
405
+ end
406
+ end
407
+
408
+ describe "grouped_column_names_for method" do
409
+ it "should return all column names joined for Tag GROUP clause" do
410
+ @taggable.grouped_column_names_for(ActsAsTaggableOn::Tag).should == "tags.id, tags.name"
411
+ end
412
+
413
+ it "should return all column names joined for TaggableModel GROUP clause" do
414
+ @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.id, taggable_models.name, taggable_models.type"
415
+ end
416
+
417
+ it "should return all column names joined for NonStandardIdTaggableModel GROUP clause" do
418
+ @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.#{TaggableModel.primary_key}, taggable_models.name, taggable_models.type"
419
+ end
420
+ end
421
+
422
+ describe "Single Table Inheritance" do
423
+ before do
424
+ @taggable = TaggableModel.new(:name => "taggable")
425
+ @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
426
+ @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
427
+ end
428
+
429
+ it "should be able to save tags for inherited models" do
430
+ @inherited_same.tag_list = "bob, kelso"
431
+ @inherited_same.save
432
+ InheritingTaggableModel.tagged_with("bob").first.should == @inherited_same
433
+ end
434
+
435
+ it "should find STI tagged models on the superclass" do
436
+ @inherited_same.tag_list = "bob, kelso"
437
+ @inherited_same.save
438
+ TaggableModel.tagged_with("bob").first.should == @inherited_same
439
+ end
440
+
441
+ it "should be able to add on contexts only to some subclasses" do
442
+ @inherited_different.part_list = "fork, spoon"
443
+ @inherited_different.save
444
+ InheritingTaggableModel.tagged_with("fork", :on => :parts).should be_empty
445
+ AlteredInheritingTaggableModel.tagged_with("fork", :on => :parts).first.should == @inherited_different
446
+ end
447
+
448
+ it "should have different tag_counts_on for inherited models" do
449
+ @inherited_same.tag_list = "bob, kelso"
450
+ @inherited_same.save!
451
+ @inherited_different.tag_list = "fork, spoon"
452
+ @inherited_different.save!
453
+
454
+ InheritingTaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:name).should == %w(bob kelso)
455
+ AlteredInheritingTaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:name).should == %w(fork spoon)
456
+ TaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:name).should == %w(bob kelso fork spoon)
457
+ end
458
+
459
+ it 'should store same tag without validation conflict' do
460
+ @taggable.tag_list = 'one'
461
+ @taggable.save!
462
+
463
+ @inherited_same.tag_list = 'one'
464
+ @inherited_same.save!
465
+
466
+ @inherited_same.update_attributes! :name => 'foo'
467
+ end
468
+ end
469
+
470
+ describe "NonStandardIdTaggable" do
471
+ before(:each) do
472
+ clean_database!
473
+ @taggable = NonStandardIdTaggableModel.new(:name => "Bob Jones")
474
+ @taggables = [@taggable, NonStandardIdTaggableModel.new(:name => "John Doe")]
475
+ end
476
+
477
+ it "should have tag types" do
478
+ [:tags, :languages, :skills, :needs, :offerings].each do |type|
479
+ NonStandardIdTaggableModel.tag_types.should include type
480
+ end
481
+
482
+ @taggable.tag_types.should == NonStandardIdTaggableModel.tag_types
483
+ end
484
+
485
+ it "should have tag_counts_on" do
486
+ NonStandardIdTaggableModel.tag_counts_on(:tags).all.should be_empty
487
+
488
+ @taggable.tag_list = ["awesome", "epic"]
489
+ @taggable.save
490
+
491
+ NonStandardIdTaggableModel.tag_counts_on(:tags).length.should == 2
492
+ @taggable.tag_counts_on(:tags).length.should == 2
493
+ end
494
+
495
+ it "should be able to create tags" do
496
+ @taggable.skill_list = "ruby, rails, css"
497
+ @taggable.instance_variable_get("@skill_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
498
+
499
+ lambda {
500
+ @taggable.save
501
+ }.should change(ActsAsTaggableOn::Tag, :count).by(3)
502
+
503
+ @taggable.reload
504
+ @taggable.skill_list.sort.should == %w(ruby rails css).sort
505
+ end
506
+
507
+ it "should be able to create tags through the tag list directly" do
508
+ @taggable.tag_list_on(:test).add("hello")
509
+ @taggable.tag_list_cache_on(:test).should_not be_empty
510
+ @taggable.tag_list_on(:test).should == ["hello"]
511
+
512
+ @taggable.save
513
+ @taggable.save_tags
514
+
515
+ @taggable.reload
516
+ @taggable.tag_list_on(:test).should == ["hello"]
517
+ end
518
+ end
519
+
520
+ describe "Dirty Objects" do
521
+ before(:each) do
522
+ @taggable = TaggableModel.create(:tag_list => "awesome, epic")
523
+ end
524
+
525
+ it 'should show changes of dirty object' do
526
+ @taggable.changes.should == {}
527
+ @taggable.tag_list = 'one'
528
+ @taggable.changes.should == {"tag_list"=>["awesome, epic", ["one"]]}
529
+
530
+ @taggable.tag_list_changed?.should be_true
531
+ @taggable.tag_list_was.should == "awesome, epic"
532
+ @taggable.tag_list_change.should == ["awesome, epic", ["one"]]
533
+ end
534
+
535
+ it 'should show no changes if the same tag_list' do
536
+ @taggable.tag_list = "awesome, epic"
537
+ @taggable.tag_list_changed?.should be_false
538
+ @taggable.changes.should == {}
539
+ end
540
+ end
541
+ end
542
+
543
+