acts-as-taggable-on 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/CHANGELOG +25 -0
  2. data/Gemfile +6 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +212 -0
  5. data/Rakefile +59 -0
  6. data/VERSION +1 -0
  7. data/lib/acts-as-taggable-on.rb +30 -0
  8. data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +41 -0
  9. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +56 -0
  10. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +97 -0
  11. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +220 -0
  12. data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +29 -0
  13. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +101 -0
  14. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +64 -0
  15. data/lib/acts_as_taggable_on/acts_as_tagger.rb +47 -0
  16. data/lib/acts_as_taggable_on/compatibility/Gemfile +6 -0
  17. data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +17 -0
  18. data/lib/acts_as_taggable_on/tag.rb +65 -0
  19. data/lib/acts_as_taggable_on/tag_list.rb +95 -0
  20. data/lib/acts_as_taggable_on/tagging.rb +23 -0
  21. data/lib/acts_as_taggable_on/tags_helper.rb +17 -0
  22. data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +31 -0
  23. data/lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb +28 -0
  24. data/rails/init.rb +1 -0
  25. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +266 -0
  26. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +114 -0
  27. data/spec/acts_as_taggable_on/tag_list_spec.rb +70 -0
  28. data/spec/acts_as_taggable_on/tag_spec.rb +115 -0
  29. data/spec/acts_as_taggable_on/taggable_spec.rb +277 -0
  30. data/spec/acts_as_taggable_on/tagger_spec.rb +75 -0
  31. data/spec/acts_as_taggable_on/tagging_spec.rb +31 -0
  32. data/spec/acts_as_taggable_on/tags_helper_spec.rb +28 -0
  33. data/spec/bm.rb +52 -0
  34. data/spec/models.rb +36 -0
  35. data/spec/schema.rb +42 -0
  36. data/spec/spec.opts +2 -0
  37. data/spec/spec_helper.rb +47 -0
  38. metadata +109 -0
@@ -0,0 +1,64 @@
1
+ module ActsAsTaggableOn::Taggable
2
+ module Related
3
+ def self.included(base)
4
+ base.send :include, ActsAsTaggableOn::Taggable::Related::InstanceMethods
5
+ base.extend ActsAsTaggableOn::Taggable::Related::ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def initialize_acts_as_taggable_on_related
10
+ tag_types.map(&:to_s).each do |tag_type|
11
+ class_eval %(
12
+ def find_related_#{tag_type}(options = {})
13
+ related_tags_for('#{tag_type}', self.class, options)
14
+ end
15
+ alias_method :find_related_on_#{tag_type}, :find_related_#{tag_type}
16
+
17
+ def find_related_#{tag_type}_for(klass, options = {})
18
+ related_tags_for('#{tag_type}', klass, options)
19
+ end
20
+
21
+ def find_matching_contexts(search_context, result_context, options = {})
22
+ matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options)
23
+ end
24
+
25
+ def find_matching_contexts_for(klass, search_context, result_context, options = {})
26
+ matching_contexts_for(search_context.to_s, result_context.to_s, klass, options)
27
+ end
28
+ )
29
+ end
30
+ end
31
+
32
+ def acts_as_taggable_on(*args)
33
+ super(*args)
34
+ initialize_acts_as_taggable_on_related
35
+ end
36
+ end
37
+
38
+ module InstanceMethods
39
+ def matching_contexts_for(search_context, result_context, klass, options = {})
40
+ tags_to_find = tags_on(search_context).collect { |t| t.name }
41
+
42
+ exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
43
+
44
+ klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
45
+ :from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
46
+ :conditions => ["#{exclude_self} #{klass.table_name}.id = #{Tagging.table_name}.taggable_id AND #{Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND #{Tag.table_name}.name IN (?) AND #{Tagging.table_name}.context = ?", tags_to_find, result_context],
47
+ :group => grouped_column_names_for(klass),
48
+ :order => "count DESC" }.update(options))
49
+ end
50
+
51
+ def related_tags_for(context, klass, options = {})
52
+ tags_to_find = tags_on(context).collect { |t| t.name }
53
+
54
+ exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
55
+
56
+ klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
57
+ :from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
58
+ :conditions => ["#{exclude_self} #{klass.table_name}.id = #{Tagging.table_name}.taggable_id AND #{Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND #{Tag.table_name}.name IN (?)", tags_to_find],
59
+ :group => grouped_column_names_for(klass),
60
+ :order => "count DESC" }.update(options))
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,47 @@
1
+ module ActsAsTaggableOn
2
+ module Tagger
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def acts_as_tagger(opts={})
9
+ has_many :owned_taggings, opts.merge(:as => :tagger, :dependent => :destroy,
10
+ :include => :tag, :class_name => "Tagging")
11
+ has_many :owned_tags, :through => :owned_taggings, :source => :tag, :uniq => true
12
+
13
+ include ActsAsTaggableOn::Tagger::InstanceMethods
14
+ extend ActsAsTaggableOn::Tagger::SingletonMethods
15
+ end
16
+
17
+ def is_tagger?
18
+ false
19
+ end
20
+ end
21
+
22
+ module InstanceMethods
23
+ def tag(taggable, opts={})
24
+ opts.reverse_merge!(:force => true)
25
+
26
+ return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
27
+
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_owner_tag_list_on(self, opts[:on].to_s, opts[:with])
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
+ end
47
+ end
@@ -0,0 +1,6 @@
1
+ source :gemcutter
2
+
3
+ # Rails 2.3
4
+ gem 'rails', '2.3.5'
5
+ gem 'rspec', '1.3.0', :require => 'spec'
6
+ gem 'sqlite3-ruby', '1.2.5', :require => 'sqlite3'
@@ -0,0 +1,17 @@
1
+ module ActsAsTaggableOn
2
+ module ActiveRecord
3
+ module Backports
4
+ def self.included(base)
5
+ base.class_eval do
6
+ named_scope :where, lambda { |conditions| { :conditions => conditions } }
7
+ named_scope :joins, lambda { |joins| { :joins => joins } }
8
+ named_scope :group, lambda { |group| { :group => group } }
9
+ named_scope :order, lambda { |order| { :order => order } }
10
+ named_scope :select, lambda { |select| { :select => select } }
11
+ named_scope :limit, lambda { |limit| { :limit => limit } }
12
+ named_scope :readonly, lambda { |readonly| { :readonly => readonly } }
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,65 @@
1
+ class Tag < ActiveRecord::Base
2
+ include ActsAsTaggableOn::ActiveRecord::Backports if ActiveRecord::VERSION::MAJOR < 3
3
+
4
+ attr_accessible :name
5
+
6
+ ### ASSOCIATIONS:
7
+
8
+ has_many :taggings, :dependent => :destroy
9
+
10
+ ### VALIDATIONS:
11
+
12
+ validates_presence_of :name
13
+ validates_uniqueness_of :name
14
+
15
+ ### SCOPES:
16
+
17
+ def self.named(name)
18
+ where(["name LIKE ?", name])
19
+ end
20
+
21
+ def self.named_any(list)
22
+ where(list.map { |tag| sanitize_sql(["name LIKE ?", tag.to_s]) }.join(" OR "))
23
+ end
24
+
25
+ def self.named_like(name)
26
+ where(["name LIKE ?", "%#{name}%"])
27
+ end
28
+
29
+ def self.named_like_any(list)
30
+ where(list.map { |tag| sanitize_sql(["name LIKE ?", "%#{tag.to_s}%"]) }.join(" OR "))
31
+ end
32
+
33
+ ### CLASS METHODS:
34
+
35
+ def self.find_or_create_with_like_by_name(name)
36
+ named_like(name).first || create(:name => name)
37
+ end
38
+
39
+ def self.find_or_create_all_with_like_by_name(*list)
40
+ list = [list].flatten
41
+
42
+ return [] if list.empty?
43
+
44
+ existing_tags = Tag.named_any(list).all
45
+ new_tag_names = list.reject { |name| existing_tags.any? { |tag| tag.name.downcase == name.downcase } }
46
+ created_tags = new_tag_names.map { |name| Tag.create(:name => name) }
47
+
48
+ existing_tags + created_tags
49
+ end
50
+
51
+ ### INSTANCE METHODS:
52
+
53
+ def ==(object)
54
+ super || (object.is_a?(Tag) && name == object.name)
55
+ end
56
+
57
+ def to_s
58
+ name
59
+ end
60
+
61
+ def count
62
+ read_attribute(:count).to_i
63
+ end
64
+
65
+ end
@@ -0,0 +1,95 @@
1
+ class TagList < Array
2
+
3
+ cattr_accessor :delimiter
4
+
5
+ self.delimiter = ','
6
+
7
+ def initialize(*args)
8
+ add(*args)
9
+ end
10
+
11
+ attr_accessor :owner
12
+
13
+ # Add tags to the tag_list. Duplicate or blank tags will be ignored.
14
+ #
15
+ # tag_list.add("Fun", "Happy")
16
+ #
17
+ # Use the <tt>:parse</tt> option to add an unparsed tag string.
18
+ #
19
+ # tag_list.add("Fun, Happy", :parse => true)
20
+ def add(*names)
21
+ extract_and_apply_options!(names)
22
+ concat(names)
23
+ clean!
24
+ self
25
+ end
26
+
27
+ # Remove specific tags from the tag_list.
28
+ #
29
+ # tag_list.remove("Sad", "Lonely")
30
+ #
31
+ # Like #add, the <tt>:parse</tt> option can be used to remove multiple tags in a string.
32
+ #
33
+ # tag_list.remove("Sad, Lonely", :parse => true)
34
+ def remove(*names)
35
+ extract_and_apply_options!(names)
36
+ delete_if { |name| names.include?(name) }
37
+ self
38
+ end
39
+
40
+ # Transform the tag_list into a tag string suitable for edting in a form.
41
+ # The tags are joined with <tt>TagList.delimiter</tt> and quoted if necessary.
42
+ #
43
+ # tag_list = TagList.new("Round", "Square,Cube")
44
+ # tag_list.to_s # 'Round, "Square,Cube"'
45
+ def to_s
46
+ tags = frozen? ? self.dup : self
47
+ tags.send(:clean!)
48
+
49
+ tags.map do |name|
50
+ name.include?(delimiter) ? "\"#{name}\"" : name
51
+ end.join(delimiter.ends_with?(" ") ? delimiter : "#{delimiter} ")
52
+ end
53
+
54
+ private
55
+ # Remove whitespace, duplicates, and blanks.
56
+ def clean!
57
+ reject!(&:blank?)
58
+ map!(&:strip)
59
+ uniq!
60
+ end
61
+
62
+ def extract_and_apply_options!(args)
63
+ options = args.last.is_a?(Hash) ? args.pop : {}
64
+ options.assert_valid_keys :parse
65
+
66
+ if options[:parse]
67
+ args.map! { |a| self.class.from(a) }
68
+ end
69
+
70
+ args.flatten!
71
+ end
72
+
73
+ class << self
74
+
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
+ string = string.join(", ") if string.respond_to?(:join)
81
+
82
+ new.tap do |tag_list|
83
+ string = string.to_s.dup
84
+
85
+ # Parse the quoted tags
86
+ string.gsub!(/(\A|#{delimiter})\s*"(.*?)"\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
87
+ string.gsub!(/(\A|#{delimiter})\s*'(.*?)'\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
88
+
89
+ tag_list.add(string.split(delimiter))
90
+ end
91
+ end
92
+
93
+ end
94
+
95
+ end
@@ -0,0 +1,23 @@
1
+ class Tagging < ActiveRecord::Base #:nodoc:
2
+ include ActsAsTaggableOn::ActiveRecord::Backports if ActiveRecord::VERSION::MAJOR < 3
3
+
4
+ attr_accessible :tag,
5
+ :tag_id,
6
+ :context,
7
+ :taggable,
8
+ :taggable_type,
9
+ :taggable_id,
10
+ :tagger,
11
+ :tagger_type,
12
+ :tagger_id
13
+
14
+ belongs_to :tag
15
+ belongs_to :taggable, :polymorphic => true
16
+ belongs_to :tagger, :polymorphic => true
17
+
18
+ validates_presence_of :context
19
+ validates_presence_of :tag_id
20
+
21
+ validates_uniqueness_of :tag_id, :scope => [ :taggable_type, :taggable_id, :context, :tagger_id, :tagger_type ]
22
+
23
+ end
@@ -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