acts-as-taggable-on-padrino 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/.rspec +2 -0
  2. data/CHANGELOG +29 -0
  3. data/Gemfile +15 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.rdoc +194 -0
  6. data/Rakefile +33 -0
  7. data/VERSION +1 -0
  8. data/acts-as-taggable-on-padrino.gemspec +94 -0
  9. data/lib/acts-as-taggable-on-padrino.rb +41 -0
  10. data/lib/acts_as_taggable_on_padrino/tag.rb +72 -0
  11. data/lib/acts_as_taggable_on_padrino/taggable.rb +64 -0
  12. data/lib/acts_as_taggable_on_padrino/taggable/cache.rb +54 -0
  13. data/lib/acts_as_taggable_on_padrino/taggable/collection.rb +91 -0
  14. data/lib/acts_as_taggable_on_padrino/taggable/core.rb +244 -0
  15. data/lib/acts_as_taggable_on_padrino/taggable/ownership.rb +104 -0
  16. data/lib/acts_as_taggable_on_padrino/taggable/related.rb +60 -0
  17. data/lib/acts_as_taggable_on_padrino/taggable/tag_list.rb +96 -0
  18. data/lib/acts_as_taggable_on_padrino/tagger.rb +65 -0
  19. data/lib/acts_as_taggable_on_padrino/tagging.rb +29 -0
  20. data/lib/acts_as_taggable_on_padrino/tags_helper.rb +17 -0
  21. data/lib/tasks/generate_migration.rb +23 -0
  22. data/lib/tasks/templates/migration.rb +29 -0
  23. data/spec/acts_as_taggable_on_padrino/acts_as_taggable_on_spec.rb +263 -0
  24. data/spec/acts_as_taggable_on_padrino/acts_as_tagger_spec.rb +110 -0
  25. data/spec/acts_as_taggable_on_padrino/tag_list_spec.rb +70 -0
  26. data/spec/acts_as_taggable_on_padrino/tag_spec.rb +105 -0
  27. data/spec/acts_as_taggable_on_padrino/taggable_spec.rb +333 -0
  28. data/spec/acts_as_taggable_on_padrino/tagger_spec.rb +90 -0
  29. data/spec/acts_as_taggable_on_padrino/tagging_spec.rb +26 -0
  30. data/spec/acts_as_taggable_on_padrino/tags_helper_spec.rb +26 -0
  31. data/spec/bm.rb +52 -0
  32. data/spec/database.yml.sample +17 -0
  33. data/spec/models.rb +47 -0
  34. data/spec/schema.rb +57 -0
  35. data/spec/spec_helper.rb +60 -0
  36. data/uninstall.rb +1 -0
  37. metadata +175 -0
@@ -0,0 +1,104 @@
1
+ module ActsAsTaggableOn::Taggable
2
+ module Ownership
3
+ def self.included(base)
4
+ base.send :include, ActsAsTaggableOn::Taggable::Ownership::InstanceMethods
5
+ base.extend ActsAsTaggableOn::Taggable::Ownership::ClassMethods
6
+
7
+ base.class_eval do
8
+ after_save :save_owned_tags
9
+ end
10
+
11
+ base.initialize_acts_as_taggable_on_ownership
12
+ end
13
+
14
+ module ClassMethods
15
+ def acts_as_taggable_on(*args)
16
+ initialize_acts_as_taggable_on_ownership
17
+ super
18
+ end
19
+
20
+ def initialize_acts_as_taggable_on_ownership
21
+ tag_types.each do |tag_type|
22
+ class_eval %(
23
+ def #{tag_type}_from(owner)
24
+ owner_tag_list_on(owner, '#{tag_type}')
25
+ end
26
+ )
27
+ end
28
+ end
29
+ end
30
+
31
+ module InstanceMethods
32
+ def owner_tags_on(owner, context)
33
+ if owner
34
+ base_tags.where(:taggings => {:context => context.to_s, :tagger_id => owner.id, :tagger_type => owner.class.to_s})
35
+ else
36
+ base_tags.where(:taggings => {:context => context.to_s})
37
+ end
38
+ end
39
+
40
+ def cached_owned_tag_list_on(context)
41
+ variable_name = "@owned_#{context}_list"
42
+ cache = instance_variable_get(variable_name) || instance_variable_set(variable_name, {})
43
+ end
44
+
45
+ def owner_tag_list_on(owner, context)
46
+ add_custom_context(context)
47
+
48
+ cache = cached_owned_tag_list_on(context)
49
+ cache.delete_if { |key, value| key.id == owner.id && key.class == owner.class }
50
+
51
+ cache[owner] ||= ActsAsTaggableOn::Taggable::TagList.new(*owner_tags_on(owner, context).names)
52
+ end
53
+
54
+ def set_owner_tag_list_on(owner, context, new_list)
55
+ add_custom_context(context)
56
+
57
+ cache = cached_owned_tag_list_on(context)
58
+ cache.delete_if { |key, value| key.id == owner.id && key.class == owner.class }
59
+
60
+ cache[owner] = ActsAsTaggableOn::Taggable::TagList.from(new_list)
61
+ end
62
+
63
+ def reload(*args)
64
+ self.class.tag_types.each do |context|
65
+ instance_variable_set("@owned_#{context}_list", nil)
66
+ end
67
+
68
+ super
69
+ end
70
+
71
+ def save_owned_tags
72
+ tagging_contexts.each do |context|
73
+ cached_owned_tag_list_on(context).each do |owner, tag_list|
74
+ # Find existing tags or create non-existing tags:
75
+ tag_list = acts_as_taggable_on_tag_model.find_or_create_all_with_like_by_name(tag_list.uniq)
76
+
77
+ owned_tags = owner_tags_on(owner, context)
78
+ old_tags = owned_tags - tag_list
79
+ new_tags = tag_list - owned_tags
80
+
81
+ # Find all taggings that belong to the taggable (self), are owned by the owner,
82
+ # have the correct context, and are removed from the list.
83
+ old_taggings = acts_as_taggable_on_tagging_model.where(
84
+ :taggable_id => id, :taggable_type => self.class.base_class.to_s,
85
+ :tagger_type => owner.class.to_s, :tagger_id => owner.id,
86
+ :tag_id => old_tags, :context => context)
87
+
88
+ if old_taggings.present?
89
+ # Destroy old taggings:
90
+ acts_as_taggable_on_tagging_model.destroy_all(:id => old_taggings.map {|tagging| tagging.id })
91
+ end
92
+
93
+ # Create new taggings:
94
+ new_tags.each do |tag|
95
+ taggings.create!(:tag_id => tag.id, :context => context.to_s, :tagger => owner, :taggable => self)
96
+ end
97
+ end
98
+ end
99
+
100
+ true
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,60 @@
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
+ base.initialize_acts_as_taggable_on_related
7
+ end
8
+
9
+ module ClassMethods
10
+ def initialize_acts_as_taggable_on_related
11
+ tag_types.each do |tag_type|
12
+ class_eval %(
13
+ def find_related_#{tag_type}
14
+ related_tags_for('#{tag_type}', self.class)
15
+ end
16
+ alias_method :find_related_on_#{tag_type}, :find_related_#{tag_type}
17
+
18
+ def find_related_#{tag_type}_for(klass)
19
+ related_tags_for('#{tag_type}', klass)
20
+ end
21
+
22
+ def find_matching_contexts(search_context, result_context)
23
+ matching_contexts_for(search_context.to_s, result_context.to_s, self.class)
24
+ end
25
+
26
+ def find_matching_contexts_for(klass, search_context, result_context)
27
+ matching_contexts_for(search_context.to_s, result_context.to_s, klass)
28
+ end
29
+ )
30
+ end
31
+ end
32
+
33
+ def acts_as_taggable_on(*args)
34
+ super
35
+ initialize_acts_as_taggable_on_related
36
+ end
37
+ end
38
+
39
+ module InstanceMethods
40
+ def matching_contexts_for(search_context, result_context, klass)
41
+ related_tags_for(search_context, klass).where("#{acts_as_taggable_on_tagging_model.table_name}.context = ?", result_context)
42
+ end
43
+
44
+ def related_tags_for(context, klass)
45
+ tags_to_find = tags_on(context).collect { |t| t.name }
46
+
47
+ scope = klass.scoped
48
+ scope = scope.where("#{klass.table_name}.id != ?", id) if self.class == klass # exclude self
49
+ scope.select("#{klass.table_name}.*, COUNT(#{acts_as_taggable_on_tag_model.table_name}.id) AS count").
50
+ from("#{klass.table_name}, #{acts_as_taggable_on_tag_model.table_name}, #{acts_as_taggable_on_tagging_model.table_name}").
51
+ where("#{klass.table_name}.id = #{acts_as_taggable_on_tagging_model.table_name}.taggable_id").
52
+ where("#{acts_as_taggable_on_tagging_model.table_name}.taggable_type = ?", klass.to_s).
53
+ where("#{acts_as_taggable_on_tagging_model.table_name}.tag_id = #{acts_as_taggable_on_tag_model.table_name}.id").
54
+ where("#{acts_as_taggable_on_tag_model.table_name}.name IN (?)", tags_to_find).
55
+ group(grouped_column_names_for(klass)).
56
+ order("count DESC")
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,96 @@
1
+ module ActsAsTaggableOn::Taggable
2
+ class TagList < Array
3
+ cattr_accessor :delimiter
4
+ self.delimiter = ','
5
+
6
+ attr_accessor :owner
7
+
8
+ def initialize(*args)
9
+ add(*args)
10
+ end
11
+
12
+ ##
13
+ # Returns a new TagList using the given tag string.
14
+ #
15
+ # Example:
16
+ # tag_list = TagList.from("One , Two, Three")
17
+ # tag_list # ["One", "Two", "Three"]
18
+ def self.from(string)
19
+ glue = delimiter.ends_with?(" ") ? delimiter : "#{delimiter} "
20
+ string = string.join(glue) if string.respond_to?(:join)
21
+
22
+ new.tap do |tag_list|
23
+ string = string.to_s.dup
24
+
25
+ # Parse the quoted tags
26
+ string.gsub!(/(\A|#{delimiter})\s*"(.*?)"\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
27
+ string.gsub!(/(\A|#{delimiter})\s*'(.*?)'\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
28
+
29
+ tag_list.add(string.split(delimiter))
30
+ end
31
+ end
32
+
33
+ ##
34
+ # Add tags to the tag_list. Duplicate or blank tags will be ignored.
35
+ # Use the <tt>:parse</tt> option to add an unparsed tag string.
36
+ #
37
+ # Example:
38
+ # tag_list.add("Fun", "Happy")
39
+ # tag_list.add("Fun, Happy", :parse => true)
40
+ def add(*names)
41
+ extract_and_apply_options!(names)
42
+ concat(names)
43
+ clean!
44
+ self
45
+ end
46
+
47
+ ##
48
+ # Remove specific tags from the tag_list.
49
+ # Use the <tt>:parse</tt> option to add an unparsed tag string.
50
+ #
51
+ # Example:
52
+ # tag_list.remove("Sad", "Lonely")
53
+ # tag_list.remove("Sad, Lonely", :parse => true)
54
+ def remove(*names)
55
+ extract_and_apply_options!(names)
56
+ delete_if { |name| names.include?(name) }
57
+ self
58
+ end
59
+
60
+ ##
61
+ # Transform the tag_list into a tag string suitable for edting in a form.
62
+ # The tags are joined with <tt>TagList.delimiter</tt> and quoted if necessary.
63
+ #
64
+ # Example:
65
+ # tag_list = TagList.new("Round", "Square,Cube")
66
+ # tag_list.to_s # 'Round, "Square,Cube"'
67
+ def to_s
68
+ tags = frozen? ? self.dup : self
69
+ tags.send(:clean!)
70
+
71
+ tags.map do |name|
72
+ name.include?(delimiter) ? "\"#{name}\"" : name
73
+ end.join(delimiter.ends_with?(" ") ? delimiter : "#{delimiter} ")
74
+ end
75
+
76
+ private
77
+
78
+ # Remove whitespace, duplicates, and blanks.
79
+ def clean!
80
+ reject! {|name| name.blank? }
81
+ map! {|name| name.strip }
82
+ uniq!
83
+ end
84
+
85
+ def extract_and_apply_options!(args)
86
+ options = args.last.is_a?(Hash) ? args.pop : {}
87
+ options.assert_valid_keys :parse
88
+
89
+ if options[:parse]
90
+ args.map! { |a| self.class.from(a) }
91
+ end
92
+
93
+ args.flatten!
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,65 @@
1
+ module ActsAsTaggableOn
2
+ module Tagger
3
+ ##
4
+ # Make a model a tagger. This allows an instance of a model to claim ownership
5
+ # of tags.
6
+ #
7
+ # Example:
8
+ # class User < ActiveRecord::Base
9
+ # acts_as_tagger
10
+ # end
11
+ def acts_as_tagger(opts={})
12
+ opts.assert_valid_keys :tagging, :tag
13
+ tagging_class_name = opts[:tagging] || 'Tagging'
14
+ tag_class_name = opts[:tag] || 'Tag'
15
+
16
+ class_eval do
17
+ has_many :owned_taggings, opts.merge(:as => :tagger, :dependent => :destroy,
18
+ :include => :tag, :class_name => tagging_class_name)
19
+ has_many :owned_tags, :through => :owned_taggings, :source => :tag, :uniq => true, :class_name => tag_class_name
20
+ end
21
+
22
+ include ActsAsTaggableOn::Tagger::InstanceMethods
23
+ extend ActsAsTaggableOn::Tagger::ClassMethods
24
+ end
25
+
26
+ def is_tagger?
27
+ false
28
+ end
29
+
30
+ module InstanceMethods
31
+ ##
32
+ # Tag a taggable model with tags that are owned by the tagger.
33
+ #
34
+ # @param taggable The object that will be tagged
35
+ # @param [Hash] options An hash with options. Available options are:
36
+ # * <tt>:with</tt> - The tags that you want to
37
+ # * <tt>:on</tt> - The context on which you want to tag
38
+ #
39
+ # Example:
40
+ # @user.tag(@photo, :with => "paris, normandy", :on => :locations)
41
+ def tag(taggable, opts={})
42
+ opts.reverse_merge!(:force => true)
43
+
44
+ return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
45
+
46
+ raise "You need to specify a tag context using :on" unless opts.has_key?(:on)
47
+ raise "You need to specify some tags using :with" unless opts.has_key?(:with)
48
+ raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless (opts[:force] || taggable.tag_types.include?(opts[:on]))
49
+
50
+ taggable.set_owner_tag_list_on(self, opts[:on].to_s, opts[:with])
51
+ taggable.save
52
+ end
53
+
54
+ def is_tagger?
55
+ self.class.is_tagger?
56
+ end
57
+ end
58
+
59
+ module ClassMethods
60
+ def is_tagger?
61
+ true
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,29 @@
1
+ module ActsAsTaggableOn
2
+ module Tagging
3
+ def acts_as_tagging(opts = {})
4
+ opts.assert_valid_keys :tag
5
+ tag_class_name = opts[:tag] || 'Tag'
6
+
7
+ class_eval do
8
+ attr_accessible :tag,
9
+ :tag_id,
10
+ :context,
11
+ :taggable,
12
+ :taggable_type,
13
+ :taggable_id,
14
+ :tagger,
15
+ :tagger_type,
16
+ :tagger_id
17
+
18
+ belongs_to :tag, :class_name => tag_class_name
19
+ belongs_to :taggable, :polymorphic => true
20
+ belongs_to :tagger, :polymorphic => true
21
+
22
+ validates_presence_of :context
23
+ validates_presence_of :tag_id
24
+
25
+ validates_uniqueness_of :tag_id, :scope => [ :taggable_type, :taggable_id, :context, :tagger_id, :tagger_type ]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ module ActsAsTaggableOn
2
+ module TagsHelper
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 {|tag| tag.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
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ namespace :taggable do
2
+ desc 'Creates the database migration needed for acts_as_as_taggable_on_padrino'
3
+ task :create_migration => :environment do
4
+ filename = "create_taggable_tables"
5
+
6
+ def return_last_migration_number
7
+ Dir[Dir.pwd + '/db/migrate/*.rb'].map { |f|
8
+ File.basename(f).match(/^(\d+)/)[0].to_i
9
+ }.max.to_i || 0
10
+ end
11
+
12
+ if (Dir[Dir.pwd + "/db/migrate/*_#{filename.underscore}.rb"].size > 0)
13
+ puts "\e[31mMigration already exists\e[0m"
14
+ elsif
15
+ migration_filename = "#{format("%03d", return_last_migration_number() +1)}_#{filename.underscore}.rb"
16
+ puts "Creating: \e[32m#{migration_filename}\e[0m"
17
+ FileUtils.cp(File.dirname(__FILE__) + "/templates/migration.rb", Dir.pwd + "/db/migrate/" + migration_filename)
18
+ puts "Now run: \e[32mpadrino rake ar:migrate\e[0m"
19
+ end
20
+
21
+
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ class CreateTaggableTables < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tags do |t|
4
+ t.column :name, :string
5
+ end
6
+
7
+ create_table :taggings do |t|
8
+ t.column :tag_id, :integer
9
+ t.column :taggable_id, :integer
10
+ t.column :tagger_id, :integer
11
+ t.column :tagger_type, :string
12
+
13
+ # You should make sure that the column created is
14
+ # long enough to store the required class names.
15
+ t.column :taggable_type, :string
16
+ t.column :context, :string
17
+
18
+ t.column :created_at, :datetime
19
+ end
20
+
21
+ add_index :taggings, :tag_id
22
+ add_index :taggings, [:taggable_id, :taggable_type, :context]
23
+ end
24
+
25
+ def self.down
26
+ drop_table :taggings
27
+ drop_table :tags
28
+ end
29
+ end
@@ -0,0 +1,263 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe "Acts As Taggable On" 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.write_inheritable_attribute(:tag_types, [])
11
+ TaggableModel.acts_as_taggable_on(:tags, :languages, :skills, :needs, :offerings)
12
+ @taggable = TaggableModel.new(:name => "Bob Jones")
13
+ end
14
+
15
+ it "should respond 'true' to taggable?" do
16
+ @taggable.class.should be_taggable
17
+ end
18
+
19
+ it "should create a class attribute for tag types" do
20
+ @taggable.class.should respond_to(:tag_types)
21
+ end
22
+
23
+ it "should create an instance attribute for tag types" do
24
+ @taggable.should respond_to(:tag_types)
25
+ end
26
+
27
+ it "should have all tag types" do
28
+ @taggable.tag_types.should == [:tags, :languages, :skills, :needs, :offerings]
29
+ end
30
+
31
+ it "should generate an association for each tag type" do
32
+ @taggable.should respond_to(:tags, :skills, :languages)
33
+ end
34
+
35
+ it "should add tagged_with and tag_counts to singleton" do
36
+ TaggableModel.should respond_to(:tagged_with, :tag_counts)
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 tag_list accessor, that includes owned tags, for each tag type" do
45
+ @taggable.should respond_to(:all_tags_list, :all_skills_list, :all_languages_list)
46
+ end
47
+ end
48
+
49
+ describe "Single Table Inheritance" do
50
+ before do
51
+ @taggable = TaggableModel.new(:name => "taggable")
52
+ @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
53
+ @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
54
+ end
55
+
56
+ it "should pass on tag contexts to STI-inherited models" do
57
+ @inherited_same.should respond_to(:tag_list, :skill_list, :language_list)
58
+ @inherited_different.should respond_to(:tag_list, :skill_list, :language_list)
59
+ end
60
+
61
+ it "should have tag contexts added in altered STI models" do
62
+ @inherited_different.should respond_to(:part_list)
63
+ end
64
+ end
65
+
66
+ describe "Reloading" do
67
+ it "should save a model instantiated by Model.find" do
68
+ taggable = TaggableModel.create!(:name => "Taggable")
69
+ found_taggable = TaggableModel.find(taggable.id)
70
+ found_taggable.save
71
+ end
72
+ end
73
+
74
+ describe "Related Objects" do
75
+ it "should find related objects based on tag names on context" do
76
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
77
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
78
+ taggable3 = TaggableModel.create!(:name => "Taggable 3")
79
+
80
+ taggable1.tag_list = "one, two"
81
+ taggable1.save
82
+
83
+ taggable2.tag_list = "three, four"
84
+ taggable2.save
85
+
86
+ taggable3.tag_list = "one, four"
87
+ taggable3.save
88
+
89
+ taggable1.find_related_tags.should include(taggable3)
90
+ taggable1.find_related_tags.should_not include(taggable2)
91
+ end
92
+
93
+ it "should find other related objects based on tag names on context" do
94
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
95
+ taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
96
+ taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
97
+
98
+ taggable1.tag_list = "one, two"
99
+ taggable1.save
100
+
101
+ taggable2.tag_list = "three, four"
102
+ taggable2.save
103
+
104
+ taggable3.tag_list = "one, four"
105
+ taggable3.save
106
+
107
+ taggable1.find_related_tags_for(OtherTaggableModel).should include(taggable3)
108
+ taggable1.find_related_tags_for(OtherTaggableModel).should_not include(taggable2)
109
+ end
110
+
111
+ it "should not include the object itself in the list of related objects" do
112
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
113
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
114
+
115
+ taggable1.tag_list = "one"
116
+ taggable1.save
117
+
118
+ taggable2.tag_list = "one, two"
119
+ taggable2.save
120
+
121
+ taggable1.find_related_tags.should include(taggable2)
122
+ taggable1.find_related_tags.should_not include(taggable1)
123
+ end
124
+ end
125
+
126
+ describe "Matching Contexts" do
127
+ it "should find objects with tags of matching contexts" do
128
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
129
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
130
+ taggable3 = TaggableModel.create!(:name => "Taggable 3")
131
+
132
+ taggable1.offering_list = "one, two"
133
+ taggable1.save!
134
+
135
+ taggable2.need_list = "one, two"
136
+ taggable2.save!
137
+
138
+ taggable3.offering_list = "one, two"
139
+ taggable3.save!
140
+
141
+ taggable1.find_matching_contexts(:offerings, :needs).should include(taggable2)
142
+ taggable1.find_matching_contexts(:offerings, :needs).should_not include(taggable3)
143
+ end
144
+
145
+ it "should find other related objects with tags of matching contexts" do
146
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
147
+ taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
148
+ taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
149
+
150
+ taggable1.offering_list = "one, two"
151
+ taggable1.save
152
+
153
+ taggable2.need_list = "one, two"
154
+ taggable2.save
155
+
156
+ taggable3.offering_list = "one, two"
157
+ taggable3.save
158
+
159
+ taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should include(taggable2)
160
+ taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should_not include(taggable3)
161
+ end
162
+
163
+ it "should not include the object itself in the list of related objects" do
164
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
165
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
166
+
167
+ taggable1.tag_list = "one"
168
+ taggable1.save
169
+
170
+ taggable2.tag_list = "one, two"
171
+ taggable2.save
172
+
173
+ taggable1.find_related_tags.should include(taggable2)
174
+ taggable1.find_related_tags.should_not include(taggable1)
175
+ end
176
+ end
177
+
178
+ describe 'Tagging Contexts' do
179
+ it 'should eliminate duplicate tagging contexts ' do
180
+ TaggableModel.acts_as_taggable_on(:skills, :skills)
181
+ TaggableModel.tag_types.freq[:skills].should_not == 3
182
+ end
183
+
184
+ it "should not contain embedded/nested arrays" do
185
+ TaggableModel.acts_as_taggable_on([:array], [:array])
186
+ TaggableModel.tag_types.freq[[:array]].should == 0
187
+ end
188
+
189
+ it "should _flatten_ the content of arrays" do
190
+ TaggableModel.acts_as_taggable_on([:array], [:array])
191
+ TaggableModel.tag_types.freq[:array].should == 1
192
+ end
193
+
194
+ it "should not raise an error when passed nil" do
195
+ lambda {
196
+ TaggableModel.acts_as_taggable_on()
197
+ }.should_not raise_error
198
+ end
199
+
200
+ it "should not raise an error when passed [nil]" do
201
+ lambda {
202
+ TaggableModel.acts_as_taggable_on([nil])
203
+ }.should_not raise_error
204
+ end
205
+ end
206
+
207
+ describe 'Caching' do
208
+ before(:each) do
209
+ @taggable = CachedModel.new(:name => "Bob Jones")
210
+ end
211
+
212
+ it "should add saving of tag lists and cached tag lists to the instance" do
213
+ @taggable.should respond_to(:save_cached_tag_list)
214
+ @taggable.should respond_to(:save_tags)
215
+ end
216
+
217
+ it "should generate a cached column checker for each tag type" do
218
+ CachedModel.should respond_to(:caching_tag_list?)
219
+ end
220
+
221
+ it 'should not have cached tags' do
222
+ @taggable.cached_tag_list.should be_blank
223
+ end
224
+
225
+ it 'should cache tags' do
226
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
227
+ @taggable.cached_tag_list.should == 'awesome, epic'
228
+ end
229
+
230
+ it 'should keep the cache' do
231
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
232
+ @taggable = CachedModel.find(@taggable)
233
+ @taggable.save!
234
+ @taggable.cached_tag_list.should == 'awesome, epic'
235
+ end
236
+
237
+ it 'should update the cache' do
238
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
239
+ @taggable.update_attributes(:tag_list => 'awesome')
240
+ @taggable.cached_tag_list.should == 'awesome'
241
+ end
242
+
243
+ it 'should remove the cache' do
244
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
245
+ @taggable.update_attributes(:tag_list => '')
246
+ @taggable.cached_tag_list.should be_blank
247
+ end
248
+
249
+ it 'should have a tag list' do
250
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
251
+ @taggable = CachedModel.find(@taggable.id)
252
+ @taggable.tag_list.sort.should == %w(awesome epic).sort
253
+ end
254
+
255
+ it 'should keep the tag list' do
256
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
257
+ @taggable = CachedModel.find(@taggable.id)
258
+ @taggable.save!
259
+ @taggable.tag_list.sort.should == %w(awesome epic).sort
260
+ end
261
+ end
262
+
263
+ end