acts-as-taggable-on 0.0.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 (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