citrusbyte-is_taggable 0.85

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.
@@ -0,0 +1,49 @@
1
+ module ActiveRecord
2
+ module Is
3
+ module Tagger
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def is_tagger(opts={})
10
+ has_many :owned_taggings, opts.merge(:as => :tagger, :dependent => :destroy, :class_name => "Tagging")
11
+ include ActiveRecord::Is::Tagger::InstanceMethods
12
+ extend ActiveRecord::Is::Tagger::SingletonMethods
13
+ end
14
+
15
+ def is_tagger?
16
+ false
17
+ end
18
+ end
19
+
20
+ module InstanceMethods
21
+ def self.included(base)
22
+ end
23
+
24
+ def tag(taggable, opts={})
25
+ opts.reverse_merge!(:force => true)
26
+
27
+ return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
28
+ raise "You need to specify a tag context using :on" unless opts.has_key?(:on)
29
+ 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 (opts[:force] || taggable.tag_types.include?(opts[:on]))
31
+
32
+ taggable.set_tag_list_on(opts[:on].to_s, opts[:with], self)
33
+ taggable.save
34
+ end
35
+
36
+ def is_tagger?
37
+ self.class.is_tagger?
38
+ end
39
+ end
40
+
41
+ module SingletonMethods
42
+ def is_tagger?
43
+ true
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,107 @@
1
+ class TagList < Array
2
+ cattr_accessor :delimiter
3
+ self.delimiter = ','
4
+
5
+ def initialize(*args)
6
+ add(*args)
7
+ end
8
+
9
+ attr_accessor :owner
10
+
11
+ # Add tags to the tag_list. Duplicate or blank tags will be ignored.
12
+ #
13
+ # tag_list.add("Fun", "Happy")
14
+ #
15
+ # Use the <tt>:parse</tt> option to add an unparsed tag string.
16
+ #
17
+ # tag_list.add("Fun, Happy", :parse => true)
18
+ def add(*names)
19
+ extract_and_apply_options!(names)
20
+ concat(names)
21
+ clean!
22
+ self
23
+ end
24
+
25
+ # Remove specific tags from the tag_list.
26
+ #
27
+ # tag_list.remove("Sad", "Lonely")
28
+ #
29
+ # Like #add, the <tt>:parse</tt> option can be used to remove multiple tags in a string.
30
+ #
31
+ # tag_list.remove("Sad, Lonely", :parse => true)
32
+ def remove(*names)
33
+ extract_and_apply_options!(names)
34
+ delete_if { |name| names.include?(name) }
35
+ self
36
+ end
37
+
38
+ # Transform the tag_list into a tag string suitable for edting in a form.
39
+ # The tags are joined with <tt>TagList.delimiter</tt> and quoted if necessary.
40
+ #
41
+ # tag_list = TagList.new("Round", "Square,Cube")
42
+ # tag_list.to_s # 'Round, "Square,Cube"'
43
+ def to_s
44
+ clean!
45
+
46
+ map do |name|
47
+ name.include?(delimiter) ? "\"#{name}\"" : name
48
+ end.join(delimiter.ends_with?(" ") ? delimiter : "#{delimiter} ")
49
+ end
50
+
51
+ def normalized
52
+ TagList.new(collect{ |tag| self.class.normalize(tag) })
53
+ end
54
+
55
+ private
56
+ # Remove whitespace, duplicates, and blanks.
57
+ def clean!
58
+ reject!(&:blank?)
59
+ map!(&:strip)
60
+ uniq!
61
+ end
62
+
63
+ def extract_and_apply_options!(args)
64
+ options = args.last.is_a?(Hash) ? args.pop : {}
65
+ options.assert_valid_keys :parse
66
+
67
+ if options[:parse]
68
+ args.map! { |a| self.class.from(a) }
69
+ end
70
+
71
+ args.flatten!
72
+ end
73
+
74
+ class << self
75
+ # Returns a new TagList using the given tag string.
76
+ #
77
+ # tag_list = TagList.from("One , Two, Three")
78
+ # tag_list # ["One", "Two", "Three"]
79
+ def from(string)
80
+ returning new do |tag_list|
81
+ string = string.to_s.dup
82
+
83
+ # Parse the quoted tags
84
+ string.gsub!(/"(.*?)"\s*#{delimiter}?\s*/) { tag_list << $1; "" }
85
+ string.gsub!(/'(.*?)'\s*#{delimiter}?\s*/) { tag_list << $1; "" }
86
+
87
+ tag_list.add(string.split(delimiter))
88
+ end
89
+ end
90
+
91
+ def from_owner(owner, *tags)
92
+ returning from(*tags) do |taglist|
93
+ taglist.owner = owner
94
+ end
95
+ end
96
+
97
+ def new_from_owner(owner, *tags)
98
+ returning new(*tags) do |taglist|
99
+ taglist.owner = owner
100
+ end
101
+ end
102
+
103
+ def normalize(tag)
104
+ tag.gsub(/\s+/, ' ').to_ascii.downcase.gsub(/[^a-z0-9\-\s]/, '')
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,31 @@
1
+ class Tagging < ActiveRecord::Base #:nodoc:
2
+ belongs_to :taggable, :polymorphic => true
3
+ belongs_to :tagger, :polymorphic => true
4
+
5
+ # these validations are useless since we don't save tags this way...
6
+ validates_presence_of :context
7
+ validates_presence_of :tag
8
+ validates_presence_of :normalized
9
+ validates_uniqueness_of :normalized, :scope => [ :context, :taggable_type, :taggable_id ]
10
+
11
+ def tag=(tag)
12
+ self[:tag] = tag
13
+ self[:normalized] = TagList.normalize(tag)
14
+ end
15
+
16
+ def context
17
+ self[:context] ? self[:context].to_sym : nil
18
+ end
19
+
20
+ def to_s
21
+ tag
22
+ end
23
+
24
+ def count
25
+ read_attribute(:count).to_i
26
+ end
27
+
28
+ def save
29
+ raise "Taggings are protected from being created individually, please use the tagging methods on Taggable objects"
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ module TagsHelper
2
+ # See the README for an example using tag_cloud.
3
+ def tag_cloud(tags, classes)
4
+ max_count = tags.sort_by(&:count).last.count.to_f
5
+
6
+ tags.each do |tag|
7
+ index = ((tag.count / max_count) * (classes.size - 1)).round
8
+ yield tag, classes[index]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ require 'is_taggable/is_taggable'
2
+ require 'is_taggable/is_tagger'
3
+ require 'is_taggable/tag_list'
4
+ require 'is_taggable/tags_helper'
5
+ require 'is_taggable/tagging'
6
+ require 'is_taggable/string'
data/rails/init.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'is_taggable'
2
+
3
+ ActiveRecord::Base.send :include, ActiveRecord::Is::Taggable
4
+ ActiveRecord::Base.send :include, ActiveRecord::Is::Tagger
5
+
6
+ RAILS_DEFAULT_LOGGER.info "** is_taggable: initialized properly."
@@ -0,0 +1,170 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe "Is Taggable" 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
9
+ before(:each) do
10
+ [TaggableModel, Tagging, TaggableUser].each(&:delete_all)
11
+ @taggable = TaggableModel.new(:name => "Bob Jones")
12
+ end
13
+
14
+ it "should respond 'true' to taggable?" do
15
+ @taggable.class.should be_taggable
16
+ end
17
+
18
+ it "should create a class attribute for tag types" do
19
+ @taggable.class.should respond_to(:tag_types)
20
+ end
21
+
22
+ it "should generate an association for each tag type" do
23
+ @taggable.should respond_to(:tags, :skills, :languages)
24
+ end
25
+
26
+ it "should generate a cached column checker for each tag type" do
27
+ TaggableModel.should respond_to(:caching_tag_list?, :caching_skill_list?, :caching_language_list?)
28
+ end
29
+
30
+ it "should add tagged_with and tag_counts to singleton" do
31
+ TaggableModel.should respond_to(:find_tagged_with, :tag_counts)
32
+ end
33
+
34
+ it "should add saving of tag lists and cached tag lists to the instance" do
35
+ @taggable.should respond_to(:save_cached_tag_list)
36
+ @taggable.should respond_to(:save_tags)
37
+ end
38
+
39
+ it "should generate a tag_list accessor/setter for each tag type" do
40
+ @taggable.should respond_to(:tag_list, :skill_list, :language_list)
41
+ @taggable.should respond_to(:tag_list=, :skill_list=, :language_list=)
42
+ end
43
+
44
+ it "should generate a tags accessor/setter for each tag type" do
45
+ @taggable.should respond_to(:tags, :skills, :languages)
46
+ @taggable.should respond_to(:tags=, :skills=, :languages=)
47
+ end
48
+
49
+ end
50
+
51
+ describe "Single Table Inheritance" do
52
+ before do
53
+ @taggable = TaggableModel.new(:name => "taggable")
54
+ @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
55
+ @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
56
+ end
57
+
58
+ it "should pass on tag contexts to STI-inherited models" do
59
+ @inherited_same.should respond_to(:tag_list, :skill_list, :language_list)
60
+ @inherited_different.should respond_to(:tag_list, :skill_list, :language_list)
61
+ end
62
+
63
+ it "should have tag contexts added in altered STI models" do
64
+ @inherited_different.should respond_to(:part_list)
65
+ end
66
+ end
67
+
68
+ describe "Reloading" do
69
+ it "should save a model instantiated by Model.find" do
70
+ taggable = TaggableModel.create!(:name => "Taggable")
71
+ found_taggable = TaggableModel.find(taggable.id)
72
+ found_taggable.save
73
+ end
74
+ end
75
+
76
+ describe "Related Objects" do
77
+ it "should find related objects based on tag names on context" do
78
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
79
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
80
+ taggable3 = TaggableModel.create!(:name => "Taggable 3")
81
+
82
+ taggable1.tag_list = "one, two"
83
+ taggable1.save
84
+
85
+ taggable2.tag_list = "three, four"
86
+ taggable2.save
87
+
88
+ taggable3.tag_list = "one, four"
89
+ taggable3.save
90
+
91
+ taggable1.find_related_tags.should include(taggable3)
92
+ taggable1.find_related_tags.should_not include(taggable2)
93
+ end
94
+
95
+ it "should find other related objects based on tag names on context" do
96
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
97
+ taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
98
+ taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
99
+
100
+ taggable1.tag_list = "one, two"
101
+ taggable1.save
102
+
103
+ taggable2.tag_list = "three, four"
104
+ taggable2.save
105
+
106
+ taggable3.tag_list = "one, four"
107
+ taggable3.save
108
+
109
+ taggable1.find_related_tags_for(OtherTaggableModel).should include(taggable3)
110
+ taggable1.find_related_tags_for(OtherTaggableModel).should_not include(taggable2)
111
+ end
112
+ end
113
+
114
+ describe "when setting tags" do
115
+ before :each do
116
+ @taggable = TaggableModel.create!(:name => 'foo')
117
+ end
118
+
119
+ it "should be settable through through tag_list=" do
120
+ @taggable.tag_list = 'foo, bar, baz'
121
+ @taggable.save
122
+ @taggable.reload.tags.should include('foo', 'bar', 'baz')
123
+ end
124
+
125
+ it "should be settable through through tags=" do
126
+ @taggable.tags = %w(foo bar baz)
127
+ @taggable.save
128
+ @taggable.reload.tags.should include('foo', 'bar', 'baz')
129
+ end
130
+ end
131
+
132
+ describe 'Tagging Contexts' do
133
+ before(:all) do
134
+ class Array
135
+ def freq
136
+ k=Hash.new(0)
137
+ self.each {|e| k[e]+=1}
138
+ k
139
+ end
140
+ end
141
+ end
142
+
143
+ it 'should eliminate duplicate tagging contexts ' do
144
+ TaggableModel.is_taggable(:skills, :skills)
145
+ TaggableModel.tag_types.freq[:skills].should_not == 3
146
+ end
147
+
148
+ it "should not contain embedded/nested arrays" do
149
+ TaggableModel.is_taggable([:array], [:array])
150
+ TaggableModel.tag_types.freq[[:array]].should == 0
151
+ end
152
+
153
+ it "should _flatten_ the content of arrays" do
154
+ TaggableModel.is_taggable([:array], [:array])
155
+ TaggableModel.tag_types.freq[:array].should == 1
156
+ end
157
+
158
+
159
+ it "should not raise an error when passed [nil]" do
160
+ lambda {
161
+ TaggableModel.is_taggable([nil])
162
+ }.should_not raise_error
163
+ end
164
+
165
+ after(:all) do
166
+ class Array; remove_method :freq; end
167
+ end
168
+ end
169
+
170
+ end
@@ -0,0 +1,67 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe TagList do
4
+ before(:each) do
5
+ @tag_list = TagList.new("awesome","radical")
6
+ end
7
+
8
+ it "should be an array" do
9
+ @tag_list.is_a?(Array).should be_true
10
+ end
11
+
12
+ it "should be able to be add a new tag word" do
13
+ @tag_list.add("cool")
14
+ @tag_list.include?("cool").should be_true
15
+ end
16
+
17
+ it "should be able to add delimited lists of words" do
18
+ @tag_list.add("cool, wicked", :parse => true)
19
+ @tag_list.include?("cool").should be_true
20
+ @tag_list.include?("wicked").should be_true
21
+ end
22
+
23
+ it "should be able to remove words" do
24
+ @tag_list.remove("awesome")
25
+ @tag_list.include?("awesome").should be_false
26
+ end
27
+
28
+ it "should be able to remove delimited lists of words" do
29
+ @tag_list.remove("awesome, radical", :parse => true)
30
+ @tag_list.should be_empty
31
+ end
32
+
33
+ it "should give a delimited list of words when converted to string" do
34
+ @tag_list.to_s.should == "awesome, radical"
35
+ end
36
+
37
+ it "should quote escape tags with commas in them" do
38
+ @tag_list.add("cool","rad,bodacious")
39
+ @tag_list.to_s.should == "awesome, radical, cool, \"rad,bodacious\""
40
+ end
41
+
42
+ describe "normalization" do
43
+ def normalized_tags(tag_list)
44
+ TagList.from(tag_list).normalized.to_s
45
+ end
46
+
47
+ it "should lower case all tags" do
48
+ normalized_tags("COol, BeANs").should eql('cool, beans')
49
+ end
50
+
51
+ it "should replace accented characters" do
52
+ normalized_tags("CÕÖl, BÈÄñs").should eql('cool, beans')
53
+ end
54
+
55
+ it "should compress whitespace" do
56
+ normalized_tags(" c o o l, b e an s ").should eql('c o o l, b e an s')
57
+ end
58
+
59
+ it "should strip 'special' characters" do
60
+ normalized_tags('c%!#{*!@\&oo!@l}, #*!#**)#(!@#)bea<>><>}{}:":":ns').should eql('cool, beans')
61
+ end
62
+
63
+ it "should replace ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöøùúûüýÆæ" do
64
+ normalized_tags('ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöøùúûüýÆæ').should eql('aaaaaaceeeeiiiidnoooooxouuuuyaaaaaaceeeeiiiinoooooouuuuyaeae')
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,136 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe "Taggable" do
4
+ before(:each) do
5
+ [TaggableModel, Tagging, TaggableUser].each(&:delete_all)
6
+ @taggable = TaggableModel.new(:name => "Bob Jones")
7
+ end
8
+
9
+ it "should be able to create tags" do
10
+ @taggable.skill_list = "ruby, rails, css"
11
+ @taggable.instance_variable_get("@skill_list").instance_of?(TagList).should be_true
12
+ @taggable.save
13
+
14
+ Tagging.find(:all).size.should == 3
15
+ end
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
+
24
+ it "should differentiate between contexts" do
25
+ @taggable.skill_list = "ruby, rails, css"
26
+ @taggable.tag_list = "ruby, bob, charlie"
27
+ @taggable.save
28
+ @taggable.reload
29
+ @taggable.skill_list.include?("ruby").should be_true
30
+ @taggable.skill_list.include?("bob").should be_false
31
+ end
32
+
33
+ it "should be able to remove tags through list alone" do
34
+ @taggable.skill_list = "ruby, rails, css"
35
+ @taggable.save
36
+ @taggable.reload
37
+ @taggable.should have(3).skills
38
+ @taggable.skill_list = "ruby, rails"
39
+ @taggable.save
40
+ @taggable.reload
41
+ @taggable.should have(2).skills
42
+ end
43
+
44
+ it "should be able to find by tag" do
45
+ @taggable.skill_list = "ruby, rails, css"
46
+ @taggable.save
47
+ TaggableModel.find_tagged_with("ruby").first.should == @taggable
48
+ end
49
+
50
+ it "should be able to find by tag with context" do
51
+ @taggable.skill_list = "ruby, rails, css"
52
+ @taggable.tag_list = "bob, charlie"
53
+ @taggable.save
54
+ TaggableModel.find_tagged_with("ruby").first.should == @taggable
55
+ TaggableModel.find_tagged_with("bob", :on => :skills).first.should_not == @taggable
56
+ TaggableModel.find_tagged_with("bob", :on => :tags).first.should == @taggable
57
+ end
58
+
59
+ it "should be able to use the tagged_with named scope" do
60
+ @taggable.skill_list = "ruby, rails, css"
61
+ @taggable.tag_list = "bob, charlie"
62
+ @taggable.save
63
+ TaggableModel.tagged_with("ruby", {}).first.should == @taggable
64
+ TaggableModel.tagged_with("bob", :on => :skills).first.should_not == @taggable
65
+ TaggableModel.tagged_with("bob", :on => :tags).first.should == @taggable
66
+ end
67
+
68
+ it "should not care about case" do
69
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby")
70
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "Ruby")
71
+
72
+ Tagging.find(:all).size.should == 2
73
+ TaggableModel.find_tagged_with("ruby").should == TaggableModel.find_tagged_with("Ruby")
74
+ end
75
+
76
+ it "should be able to get tag counts on model as a whole" do
77
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
78
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
79
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
80
+ TaggableModel.tag_counts.should_not be_empty
81
+ TaggableModel.skill_counts.should_not be_empty
82
+ end
83
+
84
+ it "should be able to get tag counts on an association" do
85
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
86
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
87
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
88
+ bob.tag_counts.collect{ |t| t.count }.sort.should eql([1, 2, 2])
89
+ charlie.skill_counts.collect{ |t| t.count }.sort.should eql([1])
90
+ end
91
+
92
+ it "should be able to set a custom tag context list" do
93
+ bob = TaggableModel.create(:name => "Bob")
94
+ bob.set_tag_list_on(:rotors, "spinning, jumping")
95
+ bob.tag_list_on(:rotors).should == ["spinning","jumping"]
96
+ bob.save
97
+ bob.reload
98
+ bob.taggings_on(:rotors).should_not be_empty
99
+ end
100
+
101
+ it "should be able to find tagged on a custom tag context" do
102
+ bob = TaggableModel.create(:name => "Bob")
103
+ bob.set_tag_list_on(:rotors, "spinning, jumping")
104
+ bob.tag_list_on(:rotors).should == ["spinning","jumping"]
105
+ bob.save
106
+ TaggableModel.find_tagged_with("spinning", :on => :rotors).should_not be_empty
107
+ end
108
+
109
+ describe "Single Table Inheritance" do
110
+ before do
111
+ [TaggableModel, Tagging, TaggableUser].each(&:delete_all)
112
+ @taggable = TaggableModel.new(:name => "taggable")
113
+ @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
114
+ @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
115
+ end
116
+
117
+ it "should be able to save tags for inherited models" do
118
+ @inherited_same.tag_list = "bob, kelso"
119
+ @inherited_same.save
120
+ InheritingTaggableModel.find_tagged_with("bob").first.should == @inherited_same
121
+ end
122
+
123
+ it "should find STI tagged models on the superclass" do
124
+ @inherited_same.tag_list = "bob, kelso"
125
+ @inherited_same.save
126
+ TaggableModel.find_tagged_with("bob").first.should == @inherited_same
127
+ end
128
+
129
+ it "should be able to add on contexts only to some subclasses" do
130
+ @inherited_different.part_list = "fork, spoon"
131
+ @inherited_different.save
132
+ InheritingTaggableModel.find_tagged_with("fork", :on => :parts).should be_empty
133
+ AlteredInheritingTaggableModel.find_tagged_with("fork", :on => :parts).first.should == @inherited_different
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,18 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe "Tagger" do
4
+ before(:each) do
5
+ [TaggableModel, Tagging, TaggableUser].each(&:delete_all)
6
+ @user = TaggableUser.new
7
+ @taggable = TaggableModel.new(:name => "Bob Jones")
8
+ end
9
+
10
+ it "should have taggings" do
11
+ @user.tag(@taggable, :with=>'ruby,scheme', :on=>:tags)
12
+ @user.owned_taggings.size == 2
13
+ end
14
+
15
+ it "is tagger" do
16
+ @user.is_tagger?.should(be_true)
17
+ end
18
+ end
@@ -0,0 +1,42 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Tagging do
4
+ before(:each) do
5
+ @tagging = Tagging.new
6
+ end
7
+
8
+ it "should require a tag" do
9
+ @tagging.valid?
10
+ @tagging.should have(1).errors_on(:tag)
11
+ end
12
+
13
+ it "should require a normalized version of the tag" do
14
+ @tagging.valid?
15
+ @tagging.should have(1).errors_on(:normalized)
16
+ end
17
+
18
+ it "should be valid with a tag" do
19
+ @tagging.tag = "something"
20
+ @tagging.valid?
21
+ @tagging.should have(0).errors_on(:tag)
22
+ end
23
+
24
+ it "should return its name when to_s is called" do
25
+ @tagging.tag = "cool"
26
+ @tagging.to_s.should == "cool"
27
+ end
28
+
29
+ it "should raise if you try to save one even when it's valid" do
30
+ @tagging.tag = 'foo'
31
+ lambda { @tagging.save }.should raise_error
32
+ end
33
+
34
+ it "should raise on create" do
35
+ lambda { Tagging.create :tag => 'foo' }.should raise_error
36
+ end
37
+
38
+ it "should raise on update" do
39
+ @taggable = TaggableModel.create!(:name => "Bob Jones", :tag_list => 'bar')
40
+ lambda { @taggable.reload.taggings.first.update_attributes(:tag => 'foo') }.should raise_error
41
+ end
42
+ end
data/spec/schema.rb ADDED
@@ -0,0 +1,30 @@
1
+ ActiveRecord::Schema.define :version => 0 do
2
+ create_table "taggings", :force => true do |t|
3
+ t.string "tag"
4
+ t.string "normalized"
5
+ t.string "context"
6
+ t.integer "taggable_id", :limit => 11
7
+ t.string "taggable_type"
8
+ t.datetime "created_at"
9
+ t.integer "tagger_id", :limit => 11
10
+ t.string "tagger_type"
11
+ end
12
+
13
+ add_index "taggings", ["tag"], :name => "index_taggings_on_tag"
14
+ add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context"
15
+ add_index "taggings", ["taggable_id", "taggable_type", "context", "normalized"], :name => "index_taggings_on_taggable_and_context_and_normalized", :uniq => true
16
+
17
+ create_table :taggable_models, :force => true do |t|
18
+ t.column :name, :string
19
+ t.column :type, :string
20
+ #t.column :cached_tag_list, :string
21
+ end
22
+ create_table :taggable_users, :force => true do |t|
23
+ t.column :name, :string
24
+ end
25
+ create_table :other_taggable_models, :force => true do |t|
26
+ t.column :name, :string
27
+ t.column :type, :string
28
+ #t.column :cached_tag_list, :string
29
+ end
30
+ end