acts-as-taggable-on 1.0.13 → 2.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.
- data/CHANGELOG +5 -2
- data/Gemfile +6 -0
- data/README.rdoc +61 -31
- data/Rakefile +46 -16
- data/VERSION +1 -1
- data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +7 -0
- data/generators/acts_as_taggable_on_migration/templates/migration.rb +29 -0
- data/lib/acts-as-taggable-on.rb +30 -7
- data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +53 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +98 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +237 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +101 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +64 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +43 -373
- data/lib/acts_as_taggable_on/acts_as_tagger.rb +58 -43
- data/lib/acts_as_taggable_on/compatibility/Gemfile +6 -0
- data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +17 -0
- data/lib/acts_as_taggable_on/tag.rb +47 -8
- data/lib/acts_as_taggable_on/tag_list.rb +45 -45
- data/lib/acts_as_taggable_on/tagging.rb +17 -2
- data/lib/acts_as_taggable_on/tags_helper.rb +8 -2
- data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +31 -0
- data/lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb +28 -0
- data/rails/init.rb +1 -7
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +98 -53
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +46 -4
- data/spec/acts_as_taggable_on/tag_list_spec.rb +18 -0
- data/spec/acts_as_taggable_on/tag_spec.rb +66 -13
- data/spec/acts_as_taggable_on/taggable_spec.rb +142 -70
- data/spec/acts_as_taggable_on/tagger_spec.rb +73 -5
- data/spec/acts_as_taggable_on/tagging_spec.rb +18 -3
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +1 -3
- data/spec/bm.rb +52 -0
- data/spec/models.rb +30 -0
- data/spec/schema.rb +13 -2
- data/spec/spec.opts +1 -2
- data/spec/spec_helper.rb +39 -34
- metadata +28 -8
- data/lib/acts_as_taggable_on/group_helper.rb +0 -12
- data/spec/acts_as_taggable_on/group_helper_spec.rb +0 -18
|
@@ -1,19 +1,40 @@
|
|
|
1
1
|
class TagList < Array
|
|
2
|
+
|
|
2
3
|
cattr_accessor :delimiter
|
|
3
4
|
self.delimiter = ','
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
attr_accessor :owner
|
|
7
|
+
|
|
5
8
|
def initialize(*args)
|
|
6
9
|
add(*args)
|
|
7
10
|
end
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# Add tags to the tag_list. Duplicate or blank tags will be ignored.
|
|
12
|
+
##
|
|
13
|
+
# Returns a new TagList using the given tag string.
|
|
12
14
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
+
# Example:
|
|
16
|
+
# tag_list = TagList.from("One , Two, Three")
|
|
17
|
+
# tag_list # ["One", "Two", "Three"]
|
|
18
|
+
def self.from(string)
|
|
19
|
+
string = string.join(", ") if string.respond_to?(:join)
|
|
20
|
+
|
|
21
|
+
new.tap do |tag_list|
|
|
22
|
+
string = string.to_s.dup
|
|
23
|
+
|
|
24
|
+
# Parse the quoted tags
|
|
25
|
+
string.gsub!(/(\A|#{delimiter})\s*"(.*?)"\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
|
|
26
|
+
string.gsub!(/(\A|#{delimiter})\s*'(.*?)'\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
|
|
27
|
+
|
|
28
|
+
tag_list.add(string.split(delimiter))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Add tags to the tag_list. Duplicate or blank tags will be ignored.
|
|
15
34
|
# Use the <tt>:parse</tt> option to add an unparsed tag string.
|
|
16
35
|
#
|
|
36
|
+
# Example:
|
|
37
|
+
# tag_list.add("Fun", "Happy")
|
|
17
38
|
# tag_list.add("Fun, Happy", :parse => true)
|
|
18
39
|
def add(*names)
|
|
19
40
|
extract_and_apply_options!(names)
|
|
@@ -21,75 +42,54 @@ class TagList < Array
|
|
|
21
42
|
clean!
|
|
22
43
|
self
|
|
23
44
|
end
|
|
24
|
-
|
|
45
|
+
|
|
46
|
+
##
|
|
25
47
|
# Remove specific tags from the tag_list.
|
|
26
|
-
#
|
|
27
|
-
# tag_list.remove("Sad", "Lonely")
|
|
48
|
+
# Use the <tt>:parse</tt> option to add an unparsed tag string.
|
|
28
49
|
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
50
|
+
# Example:
|
|
51
|
+
# tag_list.remove("Sad", "Lonely")
|
|
31
52
|
# tag_list.remove("Sad, Lonely", :parse => true)
|
|
32
53
|
def remove(*names)
|
|
33
54
|
extract_and_apply_options!(names)
|
|
34
55
|
delete_if { |name| names.include?(name) }
|
|
35
56
|
self
|
|
36
57
|
end
|
|
37
|
-
|
|
58
|
+
|
|
59
|
+
##
|
|
38
60
|
# Transform the tag_list into a tag string suitable for edting in a form.
|
|
39
61
|
# The tags are joined with <tt>TagList.delimiter</tt> and quoted if necessary.
|
|
40
62
|
#
|
|
63
|
+
# Example:
|
|
41
64
|
# tag_list = TagList.new("Round", "Square,Cube")
|
|
42
65
|
# tag_list.to_s # 'Round, "Square,Cube"'
|
|
43
66
|
def to_s
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
67
|
+
tags = frozen? ? self.dup : self
|
|
68
|
+
tags.send(:clean!)
|
|
69
|
+
|
|
70
|
+
tags.map do |name|
|
|
47
71
|
name.include?(delimiter) ? "\"#{name}\"" : name
|
|
48
72
|
end.join(delimiter.ends_with?(" ") ? delimiter : "#{delimiter} ")
|
|
49
73
|
end
|
|
74
|
+
|
|
75
|
+
private
|
|
50
76
|
|
|
51
|
-
private
|
|
52
77
|
# Remove whitespace, duplicates, and blanks.
|
|
53
78
|
def clean!
|
|
54
79
|
reject!(&:blank?)
|
|
55
80
|
map!(&:strip)
|
|
56
81
|
uniq!
|
|
57
82
|
end
|
|
58
|
-
|
|
83
|
+
|
|
59
84
|
def extract_and_apply_options!(args)
|
|
60
85
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
61
86
|
options.assert_valid_keys :parse
|
|
62
|
-
|
|
87
|
+
|
|
63
88
|
if options[:parse]
|
|
64
89
|
args.map! { |a| self.class.from(a) }
|
|
65
90
|
end
|
|
66
|
-
|
|
91
|
+
|
|
67
92
|
args.flatten!
|
|
68
93
|
end
|
|
69
|
-
|
|
70
|
-
class << self
|
|
71
|
-
# Returns a new TagList using the given tag string.
|
|
72
|
-
#
|
|
73
|
-
# tag_list = TagList.from("One , Two, Three")
|
|
74
|
-
# tag_list # ["One", "Two", "Three"]
|
|
75
|
-
def from(string)
|
|
76
|
-
string = string.join(", ") if string.respond_to?(:join)
|
|
77
94
|
|
|
78
|
-
|
|
79
|
-
string = string.to_s.dup
|
|
80
|
-
|
|
81
|
-
# Parse the quoted tags
|
|
82
|
-
string.gsub!(/"(.*?)"\s*#{delimiter}?\s*/) { tag_list << $1; "" }
|
|
83
|
-
string.gsub!(/'(.*?)'\s*#{delimiter}?\s*/) { tag_list << $1; "" }
|
|
84
|
-
|
|
85
|
-
tag_list.add(string.split(delimiter))
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def from_owner(owner, *tags)
|
|
90
|
-
returning from(*tags) do |taglist|
|
|
91
|
-
taglist.owner = owner
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
95
|
+
end
|
|
@@ -1,8 +1,23 @@
|
|
|
1
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
|
+
|
|
2
14
|
belongs_to :tag
|
|
3
15
|
belongs_to :taggable, :polymorphic => true
|
|
4
|
-
belongs_to :tagger,
|
|
5
|
-
|
|
16
|
+
belongs_to :tagger, :polymorphic => true
|
|
17
|
+
|
|
6
18
|
validates_presence_of :context
|
|
7
19
|
validates_presence_of :tag_id
|
|
20
|
+
|
|
21
|
+
validates_uniqueness_of :tag_id, :scope => [ :taggable_type, :taggable_id, :context, :tagger_id, :tagger_type ]
|
|
22
|
+
|
|
8
23
|
end
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
module TagsHelper
|
|
2
|
+
|
|
2
3
|
# See the README for an example using tag_cloud.
|
|
3
4
|
def tag_cloud(tags, classes)
|
|
5
|
+
tags = tags.all if tags.respond_to?(:all)
|
|
6
|
+
|
|
7
|
+
return [] if tags.empty?
|
|
8
|
+
|
|
4
9
|
max_count = tags.sort_by(&:count).last.count.to_f
|
|
5
|
-
|
|
10
|
+
|
|
6
11
|
tags.each do |tag|
|
|
7
12
|
index = ((tag.count / max_count) * (classes.size - 1)).round
|
|
8
13
|
yield tag, classes[index]
|
|
9
14
|
end
|
|
10
15
|
end
|
|
11
|
-
|
|
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
|
data/rails/init.rb
CHANGED
|
@@ -1,7 +1 @@
|
|
|
1
|
-
require 'acts-as-taggable-on'
|
|
2
|
-
|
|
3
|
-
ActiveRecord::Base.send :include, ActiveRecord::Acts::TaggableOn
|
|
4
|
-
ActiveRecord::Base.send :include, ActiveRecord::Acts::Tagger
|
|
5
|
-
ActionView::Base.send :include, TagsHelper if defined?(ActionView::Base)
|
|
6
|
-
|
|
7
|
-
RAILS_DEFAULT_LOGGER.info "** acts_as_taggable_on: initialized properly."
|
|
1
|
+
require 'acts-as-taggable-on'
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
require File.dirname(__FILE__) + '/../spec_helper'
|
|
2
2
|
|
|
3
3
|
describe "Acts As Taggable On" do
|
|
4
|
+
before(:each) do
|
|
5
|
+
clean_database!
|
|
6
|
+
end
|
|
7
|
+
|
|
4
8
|
it "should provide a class method 'taggable?' that is false for untaggable models" do
|
|
5
9
|
UntaggableModel.should_not be_taggable
|
|
6
10
|
end
|
|
@@ -18,32 +22,31 @@ describe "Acts As Taggable On" do
|
|
|
18
22
|
it "should create a class attribute for tag types" do
|
|
19
23
|
@taggable.class.should respond_to(:tag_types)
|
|
20
24
|
end
|
|
21
|
-
|
|
25
|
+
|
|
22
26
|
it "should create an instance attribute for tag types" do
|
|
23
27
|
@taggable.should respond_to(:tag_types)
|
|
24
28
|
end
|
|
29
|
+
|
|
30
|
+
it "should have all tag types" do
|
|
31
|
+
@taggable.tag_types.should == [:tags, :languages, :skills, :needs, :offerings]
|
|
32
|
+
end
|
|
25
33
|
|
|
26
34
|
it "should generate an association for each tag type" do
|
|
27
35
|
@taggable.should respond_to(:tags, :skills, :languages)
|
|
28
36
|
end
|
|
29
37
|
|
|
30
|
-
it "should generate a cached column checker for each tag type" do
|
|
31
|
-
TaggableModel.should respond_to(:caching_tag_list?, :caching_skill_list?, :caching_language_list?)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
38
|
it "should add tagged_with and tag_counts to singleton" do
|
|
35
|
-
TaggableModel.should respond_to(:
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
it "should add saving of tag lists and cached tag lists to the instance" do
|
|
39
|
-
@taggable.should respond_to(:save_cached_tag_list)
|
|
40
|
-
@taggable.should respond_to(:save_tags)
|
|
39
|
+
TaggableModel.should respond_to(:tagged_with, :tag_counts)
|
|
41
40
|
end
|
|
42
41
|
|
|
43
42
|
it "should generate a tag_list accessor/setter for each tag type" do
|
|
44
43
|
@taggable.should respond_to(:tag_list, :skill_list, :language_list)
|
|
45
44
|
@taggable.should respond_to(:tag_list=, :skill_list=, :language_list=)
|
|
46
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
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
describe "Single Table Inheritance" do
|
|
@@ -52,17 +55,17 @@ describe "Acts As Taggable On" do
|
|
|
52
55
|
@inherited_same = InheritingTaggableModel.new(:name => "inherited same")
|
|
53
56
|
@inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
|
|
54
57
|
end
|
|
55
|
-
|
|
58
|
+
|
|
56
59
|
it "should pass on tag contexts to STI-inherited models" do
|
|
57
60
|
@inherited_same.should respond_to(:tag_list, :skill_list, :language_list)
|
|
58
61
|
@inherited_different.should respond_to(:tag_list, :skill_list, :language_list)
|
|
59
62
|
end
|
|
60
|
-
|
|
63
|
+
|
|
61
64
|
it "should have tag contexts added in altered STI models" do
|
|
62
65
|
@inherited_different.should respond_to(:part_list)
|
|
63
66
|
end
|
|
64
67
|
end
|
|
65
|
-
|
|
68
|
+
|
|
66
69
|
describe "Reloading" do
|
|
67
70
|
it "should save a model instantiated by Model.find" do
|
|
68
71
|
taggable = TaggableModel.create!(:name => "Taggable")
|
|
@@ -76,48 +79,48 @@ describe "Acts As Taggable On" do
|
|
|
76
79
|
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
|
77
80
|
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
|
78
81
|
taggable3 = TaggableModel.create!(:name => "Taggable 3")
|
|
79
|
-
|
|
82
|
+
|
|
80
83
|
taggable1.tag_list = "one, two"
|
|
81
84
|
taggable1.save
|
|
82
|
-
|
|
85
|
+
|
|
83
86
|
taggable2.tag_list = "three, four"
|
|
84
87
|
taggable2.save
|
|
85
|
-
|
|
88
|
+
|
|
86
89
|
taggable3.tag_list = "one, four"
|
|
87
90
|
taggable3.save
|
|
88
|
-
|
|
91
|
+
|
|
89
92
|
taggable1.find_related_tags.should include(taggable3)
|
|
90
93
|
taggable1.find_related_tags.should_not include(taggable2)
|
|
91
94
|
end
|
|
92
|
-
|
|
95
|
+
|
|
93
96
|
it "should find other related objects based on tag names on context" do
|
|
94
97
|
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
|
95
98
|
taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
|
|
96
99
|
taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
|
|
97
|
-
|
|
100
|
+
|
|
98
101
|
taggable1.tag_list = "one, two"
|
|
99
102
|
taggable1.save
|
|
100
|
-
|
|
103
|
+
|
|
101
104
|
taggable2.tag_list = "three, four"
|
|
102
105
|
taggable2.save
|
|
103
|
-
|
|
106
|
+
|
|
104
107
|
taggable3.tag_list = "one, four"
|
|
105
108
|
taggable3.save
|
|
106
|
-
|
|
109
|
+
|
|
107
110
|
taggable1.find_related_tags_for(OtherTaggableModel).should include(taggable3)
|
|
108
111
|
taggable1.find_related_tags_for(OtherTaggableModel).should_not include(taggable2)
|
|
109
112
|
end
|
|
110
|
-
|
|
113
|
+
|
|
111
114
|
it "should not include the object itself in the list of related objects" do
|
|
112
115
|
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
|
113
116
|
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
|
114
|
-
|
|
117
|
+
|
|
115
118
|
taggable1.tag_list = "one"
|
|
116
119
|
taggable1.save
|
|
117
|
-
|
|
120
|
+
|
|
118
121
|
taggable2.tag_list = "one, two"
|
|
119
122
|
taggable2.save
|
|
120
|
-
|
|
123
|
+
|
|
121
124
|
taggable1.find_related_tags.should include(taggable2)
|
|
122
125
|
taggable1.find_related_tags.should_not include(taggable1)
|
|
123
126
|
end
|
|
@@ -128,64 +131,54 @@ describe "Acts As Taggable On" do
|
|
|
128
131
|
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
|
129
132
|
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
|
130
133
|
taggable3 = TaggableModel.create!(:name => "Taggable 3")
|
|
131
|
-
|
|
134
|
+
|
|
132
135
|
taggable1.offering_list = "one, two"
|
|
133
136
|
taggable1.save!
|
|
134
|
-
|
|
137
|
+
|
|
135
138
|
taggable2.need_list = "one, two"
|
|
136
139
|
taggable2.save!
|
|
137
|
-
|
|
140
|
+
|
|
138
141
|
taggable3.offering_list = "one, two"
|
|
139
142
|
taggable3.save!
|
|
140
|
-
|
|
143
|
+
|
|
141
144
|
taggable1.find_matching_contexts(:offerings, :needs).should include(taggable2)
|
|
142
145
|
taggable1.find_matching_contexts(:offerings, :needs).should_not include(taggable3)
|
|
143
146
|
end
|
|
144
|
-
|
|
147
|
+
|
|
145
148
|
it "should find other related objects with tags of matching contexts" do
|
|
146
149
|
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
|
147
150
|
taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
|
|
148
151
|
taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
|
|
149
|
-
|
|
152
|
+
|
|
150
153
|
taggable1.offering_list = "one, two"
|
|
151
154
|
taggable1.save
|
|
152
|
-
|
|
155
|
+
|
|
153
156
|
taggable2.need_list = "one, two"
|
|
154
157
|
taggable2.save
|
|
155
|
-
|
|
158
|
+
|
|
156
159
|
taggable3.offering_list = "one, two"
|
|
157
160
|
taggable3.save
|
|
158
|
-
|
|
161
|
+
|
|
159
162
|
taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should include(taggable2)
|
|
160
163
|
taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should_not include(taggable3)
|
|
161
164
|
end
|
|
162
|
-
|
|
165
|
+
|
|
163
166
|
it "should not include the object itself in the list of related objects" do
|
|
164
167
|
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
|
165
168
|
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
|
166
|
-
|
|
169
|
+
|
|
167
170
|
taggable1.tag_list = "one"
|
|
168
171
|
taggable1.save
|
|
169
|
-
|
|
172
|
+
|
|
170
173
|
taggable2.tag_list = "one, two"
|
|
171
174
|
taggable2.save
|
|
172
|
-
|
|
175
|
+
|
|
173
176
|
taggable1.find_related_tags.should include(taggable2)
|
|
174
177
|
taggable1.find_related_tags.should_not include(taggable1)
|
|
175
178
|
end
|
|
176
179
|
end
|
|
177
180
|
|
|
178
181
|
describe 'Tagging Contexts' do
|
|
179
|
-
before(:all) do
|
|
180
|
-
class Array
|
|
181
|
-
def freq
|
|
182
|
-
k=Hash.new(0)
|
|
183
|
-
self.each {|e| k[e]+=1}
|
|
184
|
-
k
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
|
|
189
182
|
it 'should eliminate duplicate tagging contexts ' do
|
|
190
183
|
TaggableModel.acts_as_taggable_on(:skills, :skills)
|
|
191
184
|
TaggableModel.tag_types.freq[:skills].should_not == 3
|
|
@@ -212,10 +205,62 @@ describe "Acts As Taggable On" do
|
|
|
212
205
|
TaggableModel.acts_as_taggable_on([nil])
|
|
213
206
|
}.should_not raise_error
|
|
214
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
|
|
215
219
|
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
218
263
|
end
|
|
219
264
|
end
|
|
220
265
|
|
|
221
|
-
end
|
|
266
|
+
end
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
require File.dirname(__FILE__) + '/../spec_helper'
|
|
2
2
|
|
|
3
3
|
describe "acts_as_tagger" do
|
|
4
|
+
before(:each) do
|
|
5
|
+
clean_database!
|
|
6
|
+
end
|
|
7
|
+
|
|
4
8
|
context "Tagger Method Generation" do
|
|
5
|
-
|
|
6
9
|
before(:each) do
|
|
7
10
|
@tagger = TaggableUser.new()
|
|
8
11
|
end
|
|
@@ -48,8 +51,23 @@ describe "acts_as_tagger" do
|
|
|
48
51
|
|
|
49
52
|
it 'should by default create the tag context on-the-fly' do
|
|
50
53
|
@taggable.tag_list_on(:here_ond_now).should be_empty
|
|
51
|
-
@tagger.tag(@taggable, :with=>'that', :on
|
|
52
|
-
@taggable.tag_list_on(:here_ond_now).
|
|
54
|
+
@tagger.tag(@taggable, :with=>'that', :on => :here_ond_now)
|
|
55
|
+
@taggable.tag_list_on(:here_ond_now).should_not include('that')
|
|
56
|
+
@taggable.all_tags_list_on(:here_ond_now).should include('that')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "should show all the tag list when both public and owned tags exist" do
|
|
60
|
+
@taggable.tag_list = 'ruby, python'
|
|
61
|
+
@tagger.tag(@taggable, :with => 'java, lisp', :on => :tags)
|
|
62
|
+
@taggable.all_tags_on(:tags).map(&:name).sort.should == %w(ruby python java lisp).sort
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "should not add owned tags to the common list" do
|
|
66
|
+
@taggable.tag_list = 'ruby, python'
|
|
67
|
+
@tagger.tag(@taggable, :with => 'java, lisp', :on => :tags)
|
|
68
|
+
@taggable.tag_list.should == %w(ruby python)
|
|
69
|
+
@tagger.tag(@taggable, :with => '', :on => :tags)
|
|
70
|
+
@taggable.tag_list.should == %w(ruby python)
|
|
53
71
|
end
|
|
54
72
|
|
|
55
73
|
it "should throw an exception when the default is over-ridden" do
|
|
@@ -64,9 +82,33 @@ describe "acts_as_tagger" do
|
|
|
64
82
|
@tagger.tag(@taggable, :with=>'this, and, that', :on=>:foo_boo, :force=>false) rescue
|
|
65
83
|
@taggable.tag_list_on(:foo_boo).should be_empty
|
|
66
84
|
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
context "when called by multiple tagger's" do
|
|
88
|
+
before(:each) do
|
|
89
|
+
@user_x = TaggableUser.create(:name => "User X")
|
|
90
|
+
@user_y = TaggableUser.create(:name => "User Y")
|
|
91
|
+
@taggable = TaggableModel.create(:name => 'acts_as_taggable_on', :tag_list => 'plugin')
|
|
92
|
+
|
|
93
|
+
@user_x.tag(@taggable, :with => 'ruby, rails', :on => :tags)
|
|
94
|
+
@user_y.tag(@taggable, :with => 'ruby, plugin', :on => :tags)
|
|
67
95
|
|
|
96
|
+
@user_y.tag(@taggable, :with => '', :on => :tags)
|
|
97
|
+
@user_y.tag(@taggable, :with => '', :on => :tags)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "should delete owned tags" do
|
|
101
|
+
@user_y.owned_tags.should == []
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "should not delete other taggers tags" do
|
|
105
|
+
@user_x.owned_tags.should have(2).items
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "should not delete original tags" do
|
|
109
|
+
@taggable.all_tags_list_on(:tags).should include('plugin')
|
|
110
|
+
end
|
|
68
111
|
end
|
|
69
|
-
|
|
70
112
|
end
|
|
71
113
|
|
|
72
114
|
end
|
|
@@ -20,6 +20,18 @@ describe TagList do
|
|
|
20
20
|
@tag_list.include?("wicked").should be_true
|
|
21
21
|
end
|
|
22
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
|
+
|
|
23
35
|
it "should be able to add an array of words" do
|
|
24
36
|
@tag_list.add(["cool", "wicked"], :parse => true)
|
|
25
37
|
@tag_list.include?("cool").should be_true
|
|
@@ -49,4 +61,10 @@ describe TagList do
|
|
|
49
61
|
@tag_list.add("cool","rad,bodacious")
|
|
50
62
|
@tag_list.to_s.should == "awesome, radical, cool, \"rad,bodacious\""
|
|
51
63
|
end
|
|
64
|
+
|
|
65
|
+
it "should be able to call to_s on a frozen tag list" do
|
|
66
|
+
@tag_list.freeze
|
|
67
|
+
lambda { @tag_list.add("cool","rad,bodacious") }.should raise_error
|
|
68
|
+
lambda { @tag_list.to_s }.should_not raise_error
|
|
69
|
+
end
|
|
52
70
|
end
|