acts-as-taggable-on 2.2.0 → 2.3.0

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 (30) hide show
  1. data/.gitignore +3 -1
  2. data/README.rdoc +59 -7
  3. data/acts-as-taggable-on.gemspec +3 -3
  4. data/lib/acts-as-taggable-on/version.rb +1 -1
  5. data/lib/acts-as-taggable-on.rb +30 -3
  6. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +7 -19
  7. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +86 -21
  8. data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +37 -0
  9. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +33 -12
  10. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +1 -1
  11. data/lib/acts_as_taggable_on/tag.rb +5 -8
  12. data/lib/acts_as_taggable_on/tag_list.rb +13 -12
  13. data/lib/acts_as_taggable_on/taggable.rb +102 -0
  14. data/lib/acts_as_taggable_on/{acts_as_tagger.rb → tagger.rb} +3 -3
  15. data/lib/acts_as_taggable_on/tagging.rb +12 -0
  16. data/lib/acts_as_taggable_on/tags_helper.rb +2 -2
  17. data/lib/acts_as_taggable_on/utils.rb +4 -0
  18. data/lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb +3 -1
  19. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +58 -0
  20. data/spec/acts_as_taggable_on/tag_list_spec.rb +84 -65
  21. data/spec/acts_as_taggable_on/tag_spec.rb +32 -10
  22. data/spec/acts_as_taggable_on/taggable_spec.rb +147 -14
  23. data/spec/acts_as_taggable_on/tagger_spec.rb +23 -16
  24. data/spec/acts_as_taggable_on/tagging_spec.rb +2 -5
  25. data/spec/acts_as_taggable_on/tags_helper_spec.rb +16 -0
  26. data/spec/models.rb +5 -0
  27. data/spec/schema.rb +5 -0
  28. data/spec/spec_helper.rb +3 -1
  29. metadata +26 -25
  30. data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +0 -54
@@ -0,0 +1,102 @@
1
+ module ActsAsTaggableOn
2
+ module Taggable
3
+ def taggable?
4
+ false
5
+ end
6
+
7
+ ##
8
+ # This is an alias for calling <tt>acts_as_taggable_on :tags</tt>.
9
+ #
10
+ # Example:
11
+ # class Book < ActiveRecord::Base
12
+ # acts_as_taggable
13
+ # end
14
+ def acts_as_taggable
15
+ acts_as_taggable_on :tags
16
+ end
17
+
18
+ ##
19
+ # This is an alias for calling <tt>acts_as_ordered_taggable_on :tags</tt>.
20
+ #
21
+ # Example:
22
+ # class Book < ActiveRecord::Base
23
+ # acts_as_ordered_taggable
24
+ # end
25
+ def acts_as_ordered_taggable
26
+ acts_as_ordered_taggable_on :tags
27
+ end
28
+
29
+ ##
30
+ # Make a model taggable on specified contexts.
31
+ #
32
+ # @param [Array] tag_types An array of taggable contexts
33
+ #
34
+ # Example:
35
+ # class User < ActiveRecord::Base
36
+ # acts_as_taggable_on :languages, :skills
37
+ # end
38
+ def acts_as_taggable_on(*tag_types)
39
+ taggable_on(false, tag_types)
40
+ end
41
+
42
+
43
+ ##
44
+ # Make a model taggable on specified contexts
45
+ # and preserves the order in which tags are created
46
+ #
47
+ # @param [Array] tag_types An array of taggable contexts
48
+ #
49
+ # Example:
50
+ # class User < ActiveRecord::Base
51
+ # acts_as_ordered_taggable_on :languages, :skills
52
+ # end
53
+ def acts_as_ordered_taggable_on(*tag_types)
54
+ taggable_on(true, tag_types)
55
+ end
56
+
57
+ private
58
+
59
+ # Make a model taggable on specified contexts
60
+ # and optionally preserves the order in which tags are created
61
+ #
62
+ # Seperate methods used above for backwards compatibility
63
+ # so that the original acts_as_taggable_on method is unaffected
64
+ # as it's not possible to add another arguement to the method
65
+ # without the tag_types being enclosed in square brackets
66
+ #
67
+ # NB: method overridden in core module in order to create tag type
68
+ # associations and methods after this logic has executed
69
+ #
70
+ def taggable_on(preserve_tag_order, *tag_types)
71
+ tag_types = tag_types.to_a.flatten.compact.map(&:to_sym)
72
+
73
+ if taggable?
74
+ self.tag_types = (self.tag_types + tag_types).uniq
75
+ self.preserve_tag_order = preserve_tag_order
76
+ else
77
+ class_attribute :tag_types
78
+ self.tag_types = tag_types
79
+ class_attribute :preserve_tag_order
80
+ self.preserve_tag_order = preserve_tag_order
81
+
82
+ class_eval do
83
+ has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "ActsAsTaggableOn::Tagging"
84
+ has_many :base_tags, :through => :taggings, :source => :tag, :class_name => "ActsAsTaggableOn::Tag"
85
+
86
+ def self.taggable?
87
+ true
88
+ end
89
+
90
+ include ActsAsTaggableOn::Utils
91
+ include ActsAsTaggableOn::Taggable::Core
92
+ include ActsAsTaggableOn::Taggable::Collection
93
+ include ActsAsTaggableOn::Taggable::Cache
94
+ include ActsAsTaggableOn::Taggable::Ownership
95
+ include ActsAsTaggableOn::Taggable::Related
96
+ include ActsAsTaggableOn::Taggable::Dirty
97
+ end
98
+ end
99
+ end
100
+
101
+ end
102
+ end
@@ -31,7 +31,7 @@ module ActsAsTaggableOn
31
31
 
32
32
  module InstanceMethods
33
33
  ##
34
- # Tag a taggable model with tags that are owned by the tagger.
34
+ # Tag a taggable model with tags that are owned by the tagger.
35
35
  #
36
36
  # @param taggable The object that will be tagged
37
37
  # @param [Hash] options An hash with options. Available options are:
@@ -42,7 +42,7 @@ module ActsAsTaggableOn
42
42
  # @user.tag(@photo, :with => "paris, normandy", :on => :locations)
43
43
  def tag(taggable, opts={})
44
44
  opts.reverse_merge!(:force => true)
45
-
45
+ skip_save = opts.delete(:skip_save)
46
46
  return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
47
47
 
48
48
  raise "You need to specify a tag context using :on" unless opts.has_key?(:on)
@@ -50,7 +50,7 @@ module ActsAsTaggableOn
50
50
  raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless (opts[:force] || taggable.tag_types.include?(opts[:on]))
51
51
 
52
52
  taggable.set_owner_tag_list_on(self, opts[:on].to_s, opts[:with])
53
- taggable.save
53
+ taggable.save unless skip_save
54
54
  end
55
55
 
56
56
  def is_tagger?
@@ -18,5 +18,17 @@ module ActsAsTaggableOn
18
18
  validates_presence_of :tag_id
19
19
 
20
20
  validates_uniqueness_of :tag_id, :scope => [ :taggable_type, :taggable_id, :context, :tagger_id, :tagger_type ]
21
+
22
+ after_destroy :remove_unused_tags
23
+
24
+ private
25
+
26
+ def remove_unused_tags
27
+ if ActsAsTaggableOn.remove_unused_tags
28
+ if tag.taggings.count.zero?
29
+ tag.destroy
30
+ end
31
+ end
32
+ end
21
33
  end
22
34
  end
@@ -9,8 +9,8 @@ module ActsAsTaggableOn
9
9
  max_count = tags.sort_by(&:count).last.count.to_f
10
10
 
11
11
  tags.each do |tag|
12
- index = ((tag.count / max_count) * (classes.size - 1)).round
13
- yield tag, classes[index]
12
+ index = ((tag.count / max_count) * (classes.size - 1))
13
+ yield tag, classes[index.nan? ? 0 : index.round]
14
14
  end
15
15
  end
16
16
  end
@@ -15,6 +15,10 @@ module ActsAsTaggableOn
15
15
  ::ActiveRecord::Base.connection && ::ActiveRecord::Base.connection.adapter_name == 'SQLite'
16
16
  end
17
17
 
18
+ def sha_prefix(string)
19
+ Digest::SHA1.hexdigest("#{string}#{rand}")[0..6]
20
+ end
21
+
18
22
  private
19
23
  def like_operator
20
24
  using_postgresql? ? 'ILIKE' : 'LIKE'
@@ -12,7 +12,9 @@ class ActsAsTaggableOnMigration < ActiveRecord::Migration
12
12
  t.references :taggable, :polymorphic => true
13
13
  t.references :tagger, :polymorphic => true
14
14
 
15
- t.string :context
15
+ # limit is created to prevent mysql error o index lenght for myisam table type.
16
+ # http://bit.ly/vgW2Ql
17
+ t.string :context, :limit => 128
16
18
 
17
19
  t.datetime :created_at
18
20
  end
@@ -8,6 +8,20 @@ describe "Acts As Taggable On" do
8
8
  it "should provide a class method 'taggable?' that is false for untaggable models" do
9
9
  UntaggableModel.should_not be_taggable
10
10
  end
11
+
12
+ describe "Taggable Method Generation To Preserve Order" do
13
+ before(:each) do
14
+ clean_database!
15
+ TaggableModel.tag_types = []
16
+ TaggableModel.preserve_tag_order = false
17
+ TaggableModel.acts_as_ordered_taggable_on(:ordered_tags)
18
+ @taggable = TaggableModel.new(:name => "Bob Jones")
19
+ end
20
+
21
+ it "should respond 'true' to preserve_tag_order?" do
22
+ @taggable.class.preserve_tag_order?.should be_true
23
+ end
24
+ end
11
25
 
12
26
  describe "Taggable Method Generation" do
13
27
  before(:each) do
@@ -32,6 +46,18 @@ describe "Acts As Taggable On" do
32
46
  it "should have all tag types" do
33
47
  @taggable.tag_types.should == [:tags, :languages, :skills, :needs, :offerings]
34
48
  end
49
+
50
+ it "should create a class attribute for preserve tag order" do
51
+ @taggable.class.should respond_to(:preserve_tag_order?)
52
+ end
53
+
54
+ it "should create an instance attribute for preserve tag order" do
55
+ @taggable.should respond_to(:preserve_tag_order?)
56
+ end
57
+
58
+ it "should respond 'false' to preserve_tag_order?" do
59
+ @taggable.class.preserve_tag_order?.should be_false
60
+ end
35
61
 
36
62
  it "should generate an association for each tag type" do
37
63
  @taggable.should respond_to(:tags, :skills, :languages)
@@ -453,4 +479,36 @@ describe "Acts As Taggable On" do
453
479
  @taggable.taggings.should == []
454
480
  end
455
481
  end
482
+
483
+ describe "@@remove_unused_tags" do
484
+ before do
485
+ @taggable = TaggableModel.create(:name => "Bob Jones")
486
+ @tag = ActsAsTaggableOn::Tag.create(:name => "awesome")
487
+
488
+ @tagging = ActsAsTaggableOn::Tagging.create(:taggable => @taggable, :tag => @tag, :context => 'tags')
489
+ end
490
+
491
+ context "if set to true" do
492
+ before do
493
+ ActsAsTaggableOn.remove_unused_tags = true
494
+ end
495
+
496
+ it "should remove unused tags after removing taggings" do
497
+ @tagging.destroy
498
+ ActsAsTaggableOn::Tag.find_by_name("awesome").should be_nil
499
+ end
500
+ end
501
+
502
+ context "if set to false" do
503
+ before do
504
+ ActsAsTaggableOn.remove_unused_tags = false
505
+ end
506
+
507
+ it "should not remove unused tags after removing taggings" do
508
+ @tagging.destroy
509
+ ActsAsTaggableOn::Tag.find_by_name("awesome").should == @tag
510
+ end
511
+ end
512
+ end
513
+
456
514
  end
@@ -1,74 +1,93 @@
1
1
  require File.expand_path('../../spec_helper', __FILE__)
2
2
 
3
3
  describe ActsAsTaggableOn::TagList do
4
- before(:each) do
5
- @tag_list = ActsAsTaggableOn::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 add delimited list of words with quoted delimiters" do
24
- @tag_list.add("'cool, wicked', \"really cool, really wicked\"", :parse => true)
25
- @tag_list.include?("cool, wicked").should be_true
26
- @tag_list.include?("really cool, really wicked").should be_true
27
- end
28
-
29
- it "should be able to handle other uses of quotation marks correctly" do
30
- @tag_list.add("john's cool car, mary's wicked toy", :parse => true)
31
- @tag_list.include?("john's cool car").should be_true
32
- @tag_list.include?("mary's wicked toy").should be_true
33
- end
34
-
35
- it "should be able to add an array of words" do
36
- @tag_list.add(["cool", "wicked"], :parse => true)
37
- @tag_list.include?("cool").should be_true
38
- @tag_list.include?("wicked").should be_true
39
- end
40
-
41
- it "should be able to remove words" do
42
- @tag_list.remove("awesome")
43
- @tag_list.include?("awesome").should be_false
44
- end
45
-
46
- it "should be able to remove delimited lists of words" do
47
- @tag_list.remove("awesome, radical", :parse => true)
48
- @tag_list.should be_empty
49
- end
50
-
51
- it "should be able to remove an array of words" do
52
- @tag_list.remove(["awesome", "radical"], :parse => true)
53
- @tag_list.should be_empty
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
54
10
  end
55
-
56
- it "should give a delimited list of words when converted to string" do
57
- @tag_list.to_s.should == "awesome, radical"
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
+
58
43
  end
59
-
60
- it "should quote escape tags with commas in them" do
61
- @tag_list.add("cool","rad,bodacious")
62
- @tag_list.to_s.should == "awesome, radical, cool, \"rad,bodacious\""
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
63
60
  end
64
-
65
- it "#from should return empty array if empty array is passed" do
66
- ActsAsTaggableOn::TagList.from([]).should == []
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
67
72
  end
68
-
69
- it "should be able to call to_s on a frozen tag list" do
70
- @tag_list.freeze
71
- lambda { @tag_list.add("cool","rad,bodacious") }.should raise_error
72
- lambda { @tag_list.to_s }.should_not raise_error
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
+
73
92
  end
74
93
  end
@@ -1,3 +1,5 @@
1
+ #encoding: utf-8
2
+
1
3
  require File.expand_path('../../spec_helper', __FILE__)
2
4
 
3
5
  describe ActsAsTaggableOn::Tag do
@@ -40,6 +42,23 @@ describe ActsAsTaggableOn::Tag do
40
42
  end
41
43
  end
42
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
+
43
62
  describe "find or create all by any name" do
44
63
  before(:each) do
45
64
  @tag.name = "awesome"
@@ -74,20 +93,22 @@ describe ActsAsTaggableOn::Tag do
74
93
  it "should require a name" do
75
94
  @tag.valid?
76
95
 
77
- if ActiveRecord::VERSION::MAJOR >= 3
78
- @tag.errors[:name].should == ["can't be blank"]
79
- else
80
- @tag.errors[:name].should == "can't be blank"
81
- end
96
+ @tag.errors[:name].should == ["can't be blank"]
82
97
 
83
98
  @tag.name = "something"
84
99
  @tag.valid?
85
100
 
86
- if ActiveRecord::VERSION::MAJOR >= 3
87
- @tag.errors[:name].should == []
88
- else
89
- @tag.errors[:name].should be_nil
90
- end
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 == []
91
112
  end
92
113
 
93
114
  it "should equal a tag with the same name" do
@@ -128,4 +149,5 @@ describe ActsAsTaggableOn::Tag do
128
149
  end
129
150
 
130
151
  end
152
+
131
153
  end
@@ -1,5 +1,114 @@
1
1
  require File.expand_path('../../spec_helper', __FILE__)
2
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
+
3
112
  describe "Taggable" do
4
113
  before(:each) do
5
114
  clean_database!
@@ -142,20 +251,9 @@ describe "Taggable" do
142
251
  TaggableModel.tagged_with(['rails, css'], :on => :needs, :any => true).tagged_with(['c++', 'java'], :on => :offerings, :any => true).to_a.should == [bob]
143
252
  end
144
253
 
145
- if ActiveRecord::VERSION::MAJOR >= 3
146
- it "should not return read-only records" do
147
- TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
148
- TaggableModel.tagged_with("ruby").first.should_not be_readonly
149
- end
150
- else
151
- xit "should not return read-only records" do
152
- # apparantly, there is no way to set readonly to false in a scope if joins are made
153
- end
154
-
155
- it "should be possible to return writable records" do
156
- TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
157
- TaggableModel.tagged_with("ruby").first(:readonly => false).should_not be_readonly
158
- end
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
159
257
  end
160
258
 
161
259
  it "should be able to get scoped tag counts" do
@@ -229,6 +327,19 @@ describe "Taggable" do
229
327
  TaggableModel.tagged_with(["depressed", "css"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank]
230
328
  end
231
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
+
232
343
  it "should be able to find tagged on a custom tag context" do
233
344
  bob = TaggableModel.create(:name => "Bob")
234
345
  bob.set_tag_list_on(:rotors, "spinning, jumping")
@@ -405,6 +516,28 @@ describe "Taggable" do
405
516
  @taggable.tag_list_on(:test).should == ["hello"]
406
517
  end
407
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
408
541
  end
409
542
 
410
543