crowdint_acts-as-taggable-on 2.3.2
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/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/CHANGELOG +35 -0
- data/Gemfile +3 -0
- data/Guardfile +5 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +250 -0
- data/Rakefile +13 -0
- data/acts-as-taggable-on.gemspec +28 -0
- data/lib/acts-as-taggable-on.rb +59 -0
- data/lib/acts-as-taggable-on/version.rb +4 -0
- 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 +127 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +349 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +37 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +99 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +73 -0
- data/lib/acts_as_taggable_on/tag.rb +77 -0
- data/lib/acts_as_taggable_on/tag_list.rb +97 -0
- data/lib/acts_as_taggable_on/taggable.rb +102 -0
- data/lib/acts_as_taggable_on/tagger.rb +67 -0
- data/lib/acts_as_taggable_on/tagging.rb +34 -0
- data/lib/acts_as_taggable_on/tags_helper.rb +17 -0
- data/lib/acts_as_taggable_on/utils.rb +34 -0
- data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +39 -0
- data/lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb +30 -0
- data/rails/init.rb +1 -0
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +514 -0
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +114 -0
- data/spec/acts_as_taggable_on/tag_list_spec.rb +93 -0
- data/spec/acts_as_taggable_on/tag_spec.rb +153 -0
- data/spec/acts_as_taggable_on/taggable_spec.rb +543 -0
- data/spec/acts_as_taggable_on/tagger_spec.rb +112 -0
- data/spec/acts_as_taggable_on/tagging_spec.rb +28 -0
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +44 -0
- data/spec/acts_as_taggable_on/utils_spec.rb +21 -0
- data/spec/bm.rb +52 -0
- data/spec/database.yml.sample +19 -0
- data/spec/generators/acts_as_taggable_on/migration/migration_generator_spec.rb +22 -0
- data/spec/models.rb +49 -0
- data/spec/schema.rb +61 -0
- data/spec/spec_helper.rb +83 -0
- data/uninstall.rb +1 -0
- metadata +240 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
module ActsAsTaggableOn
|
2
|
+
module Tagger
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
##
|
9
|
+
# Make a model a tagger. This allows an instance of a model to claim ownership
|
10
|
+
# of tags.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
# class User < ActiveRecord::Base
|
14
|
+
# acts_as_tagger
|
15
|
+
# end
|
16
|
+
def acts_as_tagger(opts={})
|
17
|
+
class_eval do
|
18
|
+
has_many :owned_taggings, opts.merge(:as => :tagger, :dependent => :destroy,
|
19
|
+
:include => :tag, :class_name => "ActsAsTaggableOn::Tagging")
|
20
|
+
has_many :owned_tags, :through => :owned_taggings, :source => :tag, :uniq => true, :class_name => "ActsAsTaggableOn::Tag"
|
21
|
+
end
|
22
|
+
|
23
|
+
include ActsAsTaggableOn::Tagger::InstanceMethods
|
24
|
+
extend ActsAsTaggableOn::Tagger::SingletonMethods
|
25
|
+
end
|
26
|
+
|
27
|
+
def is_tagger?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module InstanceMethods
|
33
|
+
##
|
34
|
+
# Tag a taggable model with tags that are owned by the tagger.
|
35
|
+
#
|
36
|
+
# @param taggable The object that will be tagged
|
37
|
+
# @param [Hash] options An hash with options. Available options are:
|
38
|
+
# * <tt>:with</tt> - The tags that you want to
|
39
|
+
# * <tt>:on</tt> - The context on which you want to tag
|
40
|
+
#
|
41
|
+
# Example:
|
42
|
+
# @user.tag(@photo, :with => "paris, normandy", :on => :locations)
|
43
|
+
def tag(taggable, opts={})
|
44
|
+
opts.reverse_merge!(:force => true)
|
45
|
+
skip_save = opts.delete(:skip_save)
|
46
|
+
return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
|
47
|
+
|
48
|
+
raise "You need to specify a tag context using :on" unless opts.has_key?(:on)
|
49
|
+
raise "You need to specify some tags using :with" unless opts.has_key?(:with)
|
50
|
+
raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless (opts[:force] || taggable.tag_types.include?(opts[:on]))
|
51
|
+
|
52
|
+
taggable.set_owner_tag_list_on(self, opts[:on].to_s, opts[:with])
|
53
|
+
taggable.save unless skip_save
|
54
|
+
end
|
55
|
+
|
56
|
+
def is_tagger?
|
57
|
+
self.class.is_tagger?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module SingletonMethods
|
62
|
+
def is_tagger?
|
63
|
+
true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ActsAsTaggableOn
|
2
|
+
class Tagging < ::ActiveRecord::Base #:nodoc:
|
3
|
+
attr_accessible :tag,
|
4
|
+
:tag_id,
|
5
|
+
:context,
|
6
|
+
:taggable,
|
7
|
+
:taggable_type,
|
8
|
+
:taggable_id,
|
9
|
+
:tagger,
|
10
|
+
:tagger_type,
|
11
|
+
:tagger_id
|
12
|
+
|
13
|
+
belongs_to :tag, :class_name => 'ActsAsTaggableOn::Tag'
|
14
|
+
belongs_to :taggable, :polymorphic => true
|
15
|
+
belongs_to :tagger, :polymorphic => true
|
16
|
+
|
17
|
+
validates_presence_of :context
|
18
|
+
validates_presence_of :tag_id
|
19
|
+
|
20
|
+
validates_uniqueness_of :tag_id, :scope => [ :taggable_type, :taggable_id, :context, :tagger_id, :tagger_type ]
|
21
|
+
|
22
|
+
after_destroy :remove_unused_tags
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def remove_unused_tags
|
27
|
+
if ActsAsTaggableOn.remove_unused_tags
|
28
|
+
if tag.taggings.count.zero?
|
29
|
+
tag.destroy
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
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(&:count).last.count.to_f
|
10
|
+
|
11
|
+
tags.each do |tag|
|
12
|
+
index = ((tag.count / max_count) * (classes.size - 1))
|
13
|
+
yield tag, classes[index.nan? ? 0 : index.round]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ActsAsTaggableOn
|
2
|
+
module Utils
|
3
|
+
def self.included(base)
|
4
|
+
|
5
|
+
base.send :include, ActsAsTaggableOn::Utils::OverallMethods
|
6
|
+
base.extend ActsAsTaggableOn::Utils::OverallMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module OverallMethods
|
10
|
+
def using_postgresql?
|
11
|
+
::ActiveRecord::Base.connection && ::ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
12
|
+
end
|
13
|
+
|
14
|
+
def using_sqlite?
|
15
|
+
::ActiveRecord::Base.connection && ::ActiveRecord::Base.connection.adapter_name == 'SQLite'
|
16
|
+
end
|
17
|
+
|
18
|
+
def sha_prefix(string)
|
19
|
+
Digest::SHA1.hexdigest("#{string}#{rand}")[0..6]
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def like_operator
|
24
|
+
using_postgresql? ? 'ILIKE' : 'LIKE'
|
25
|
+
end
|
26
|
+
|
27
|
+
# escape _ and % characters in strings, since these are wildcards in SQL.
|
28
|
+
def escape_like(str)
|
29
|
+
str.gsub(/[!%_]/){ |x| '!' + x }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
module ActsAsTaggableOn
|
5
|
+
class MigrationGenerator < Rails::Generators::Base
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
|
8
|
+
desc "Generates migration for Tag and Tagging models"
|
9
|
+
|
10
|
+
def self.orm
|
11
|
+
Rails::Generators.options[:rails][:orm]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.source_root
|
15
|
+
File.join(File.dirname(__FILE__), 'templates', (orm.to_s unless orm.class.eql?(String)) )
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.orm_has_migration?
|
19
|
+
[:active_record].include? orm
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.next_migration_number(dirname)
|
23
|
+
if ActiveRecord::Base.timestamped_migrations
|
24
|
+
migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
|
25
|
+
migration_number += 1
|
26
|
+
migration_number.to_s
|
27
|
+
else
|
28
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_migration_file
|
33
|
+
if self.class.orm_has_migration?
|
34
|
+
migration_template 'migration.rb', 'db/migrate/acts_as_taggable_on_migration'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,30 @@
|
|
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
|
+
# limit is created to prevent mysql error o index lenght for myisam table type.
|
16
|
+
# http://bit.ly/vgW2Ql
|
17
|
+
t.string :context, :limit => 128
|
18
|
+
|
19
|
+
t.datetime :created_at
|
20
|
+
end
|
21
|
+
|
22
|
+
add_index :taggings, :tag_id
|
23
|
+
add_index :taggings, [:taggable_id, :taggable_type, :context]
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.down
|
27
|
+
drop_table :taggings
|
28
|
+
drop_table :tags
|
29
|
+
end
|
30
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'acts-as-taggable-on'
|
@@ -0,0 +1,514 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
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 To Preserve Order" do
|
13
|
+
before(:each) do
|
14
|
+
clean_database!
|
15
|
+
TaggableModel.tag_types = []
|
16
|
+
TaggableModel.preserve_tag_order = false
|
17
|
+
TaggableModel.acts_as_ordered_taggable_on(:ordered_tags)
|
18
|
+
@taggable = TaggableModel.new(:name => "Bob Jones")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should respond 'true' to preserve_tag_order?" do
|
22
|
+
@taggable.class.preserve_tag_order?.should be_true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "Taggable Method Generation" do
|
27
|
+
before(:each) do
|
28
|
+
clean_database!
|
29
|
+
TaggableModel.tag_types = []
|
30
|
+
TaggableModel.acts_as_taggable_on(:tags, :languages, :skills, :needs, :offerings)
|
31
|
+
@taggable = TaggableModel.new(:name => "Bob Jones")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should respond 'true' to taggable?" do
|
35
|
+
@taggable.class.should be_taggable
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should create a class attribute for tag types" do
|
39
|
+
@taggable.class.should respond_to(:tag_types)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should create an instance attribute for tag types" do
|
43
|
+
@taggable.should respond_to(:tag_types)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should have all tag types" do
|
47
|
+
@taggable.tag_types.should == [:tags, :languages, :skills, :needs, :offerings]
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should create a class attribute for preserve tag order" do
|
51
|
+
@taggable.class.should respond_to(:preserve_tag_order?)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should create an instance attribute for preserve tag order" do
|
55
|
+
@taggable.should respond_to(:preserve_tag_order?)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should respond 'false' to preserve_tag_order?" do
|
59
|
+
@taggable.class.preserve_tag_order?.should be_false
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should generate an association for each tag type" do
|
63
|
+
@taggable.should respond_to(:tags, :skills, :languages)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should add tagged_with and tag_counts to singleton" do
|
67
|
+
TaggableModel.should respond_to(:tagged_with, :tag_counts)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should generate a tag_list accessor/setter for each tag type" do
|
71
|
+
@taggable.should respond_to(:tag_list, :skill_list, :language_list)
|
72
|
+
@taggable.should respond_to(:tag_list=, :skill_list=, :language_list=)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should generate a tag_list accessor, that includes owned tags, for each tag type" do
|
76
|
+
@taggable.should respond_to(:all_tags_list, :all_skills_list, :all_languages_list)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "Single Table Inheritance" do
|
81
|
+
before do
|
82
|
+
@taggable = TaggableModel.new(:name => "taggable")
|
83
|
+
@inherited_same = InheritingTaggableModel.new(:name => "inherited same")
|
84
|
+
@inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should pass on tag contexts to STI-inherited models" do
|
88
|
+
@inherited_same.should respond_to(:tag_list, :skill_list, :language_list)
|
89
|
+
@inherited_different.should respond_to(:tag_list, :skill_list, :language_list)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should have tag contexts added in altered STI models" do
|
93
|
+
@inherited_different.should respond_to(:part_list)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "Reloading" do
|
98
|
+
it "should save a model instantiated by Model.find" do
|
99
|
+
taggable = TaggableModel.create!(:name => "Taggable")
|
100
|
+
found_taggable = TaggableModel.find(taggable.id)
|
101
|
+
found_taggable.save
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "Related Objects" do
|
106
|
+
it "should find related objects based on tag names on context" do
|
107
|
+
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
108
|
+
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
109
|
+
taggable3 = TaggableModel.create!(:name => "Taggable 3")
|
110
|
+
|
111
|
+
taggable1.tag_list = "one, two"
|
112
|
+
taggable1.save
|
113
|
+
|
114
|
+
taggable2.tag_list = "three, four"
|
115
|
+
taggable2.save
|
116
|
+
|
117
|
+
taggable3.tag_list = "one, four"
|
118
|
+
taggable3.save
|
119
|
+
|
120
|
+
taggable1.find_related_tags.should include(taggable3)
|
121
|
+
taggable1.find_related_tags.should_not include(taggable2)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should find related objects based on tag names on context - non standard id" do
|
125
|
+
taggable1 = NonStandardIdTaggableModel.create!(:name => "Taggable 1")
|
126
|
+
taggable2 = NonStandardIdTaggableModel.create!(:name => "Taggable 2")
|
127
|
+
taggable3 = NonStandardIdTaggableModel.create!(:name => "Taggable 3")
|
128
|
+
|
129
|
+
taggable1.tag_list = "one, two"
|
130
|
+
taggable1.save
|
131
|
+
|
132
|
+
taggable2.tag_list = "three, four"
|
133
|
+
taggable2.save
|
134
|
+
|
135
|
+
taggable3.tag_list = "one, four"
|
136
|
+
taggable3.save
|
137
|
+
|
138
|
+
taggable1.find_related_tags.should include(taggable3)
|
139
|
+
taggable1.find_related_tags.should_not include(taggable2)
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should find other related objects based on tag names on context" do
|
143
|
+
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
144
|
+
taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
|
145
|
+
taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
|
146
|
+
|
147
|
+
taggable1.tag_list = "one, two"
|
148
|
+
taggable1.save
|
149
|
+
|
150
|
+
taggable2.tag_list = "three, four"
|
151
|
+
taggable2.save
|
152
|
+
|
153
|
+
taggable3.tag_list = "one, four"
|
154
|
+
taggable3.save
|
155
|
+
|
156
|
+
taggable1.find_related_tags_for(OtherTaggableModel).should include(taggable3)
|
157
|
+
taggable1.find_related_tags_for(OtherTaggableModel).should_not include(taggable2)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should not include the object itself in the list of related objects" do
|
161
|
+
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
162
|
+
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
163
|
+
|
164
|
+
taggable1.tag_list = "one"
|
165
|
+
taggable1.save
|
166
|
+
|
167
|
+
taggable2.tag_list = "one, two"
|
168
|
+
taggable2.save
|
169
|
+
|
170
|
+
taggable1.find_related_tags.should include(taggable2)
|
171
|
+
taggable1.find_related_tags.should_not include(taggable1)
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should not include the object itself in the list of related objects - non standard id" do
|
175
|
+
taggable1 = NonStandardIdTaggableModel.create!(:name => "Taggable 1")
|
176
|
+
taggable2 = NonStandardIdTaggableModel.create!(:name => "Taggable 2")
|
177
|
+
|
178
|
+
taggable1.tag_list = "one"
|
179
|
+
taggable1.save
|
180
|
+
|
181
|
+
taggable2.tag_list = "one, two"
|
182
|
+
taggable2.save
|
183
|
+
|
184
|
+
taggable1.find_related_tags.should include(taggable2)
|
185
|
+
taggable1.find_related_tags.should_not include(taggable1)
|
186
|
+
end
|
187
|
+
|
188
|
+
context "Inherited Models" do
|
189
|
+
before do
|
190
|
+
@taggable1 = InheritingTaggableModel.create!(:name => "InheritingTaggable 1")
|
191
|
+
@taggable2 = InheritingTaggableModel.create!(:name => "InheritingTaggable 2")
|
192
|
+
@taggable3 = InheritingTaggableModel.create!(:name => "InheritingTaggable 3")
|
193
|
+
@taggable4 = TaggableModel.create!(:name => "Taggable 4")
|
194
|
+
|
195
|
+
@taggable1.tag_list = "one, two"
|
196
|
+
@taggable1.save
|
197
|
+
|
198
|
+
@taggable2.tag_list = "three, four"
|
199
|
+
@taggable2.save
|
200
|
+
|
201
|
+
@taggable3.tag_list = "one, four"
|
202
|
+
@taggable3.save
|
203
|
+
|
204
|
+
@taggable4.tag_list = "one, two, three, four"
|
205
|
+
@taggable4.save
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should find related objects based on tag names on context" do
|
209
|
+
@taggable1.find_related_tags.should include(@taggable3)
|
210
|
+
@taggable1.find_related_tags.should_not include(@taggable2)
|
211
|
+
@taggable1.find_related_tags.should_not include(@taggable4)
|
212
|
+
|
213
|
+
@taggable1.find_related_tags_for(TaggableModel).should include(@taggable3)
|
214
|
+
@taggable1.find_related_tags_for(TaggableModel).should_not include(@taggable2)
|
215
|
+
@taggable1.find_related_tags_for(TaggableModel).should include(@taggable4)
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should not include the object itself in the list of related objects" do
|
219
|
+
@taggable1.find_related_tags.should_not include(@taggable1)
|
220
|
+
@taggable1.find_related_tags_for(InheritingTaggableModel).should_not include(@taggable1)
|
221
|
+
@taggable1.find_related_tags_for(TaggableModel).should_not include(@taggable1)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
describe "Matching Contexts" do
|
228
|
+
it "should find objects with tags of matching contexts" do
|
229
|
+
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
230
|
+
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
231
|
+
taggable3 = TaggableModel.create!(:name => "Taggable 3")
|
232
|
+
|
233
|
+
taggable1.offering_list = "one, two"
|
234
|
+
taggable1.save!
|
235
|
+
|
236
|
+
taggable2.need_list = "one, two"
|
237
|
+
taggable2.save!
|
238
|
+
|
239
|
+
taggable3.offering_list = "one, two"
|
240
|
+
taggable3.save!
|
241
|
+
|
242
|
+
taggable1.find_matching_contexts(:offerings, :needs).should include(taggable2)
|
243
|
+
taggable1.find_matching_contexts(:offerings, :needs).should_not include(taggable3)
|
244
|
+
end
|
245
|
+
|
246
|
+
it "should find other related objects with tags of matching contexts" do
|
247
|
+
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
248
|
+
taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
|
249
|
+
taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
|
250
|
+
|
251
|
+
taggable1.offering_list = "one, two"
|
252
|
+
taggable1.save
|
253
|
+
|
254
|
+
taggable2.need_list = "one, two"
|
255
|
+
taggable2.save
|
256
|
+
|
257
|
+
taggable3.offering_list = "one, two"
|
258
|
+
taggable3.save
|
259
|
+
|
260
|
+
taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should include(taggable2)
|
261
|
+
taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should_not include(taggable3)
|
262
|
+
end
|
263
|
+
|
264
|
+
it "should not include the object itself in the list of related objects with tags of matching contexts" do
|
265
|
+
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
266
|
+
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
267
|
+
|
268
|
+
taggable1.offering_list = "one, two"
|
269
|
+
taggable1.need_list = "one, two"
|
270
|
+
taggable1.save
|
271
|
+
|
272
|
+
taggable2.need_list = "one, two"
|
273
|
+
taggable2.save
|
274
|
+
|
275
|
+
taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs).should include(taggable2)
|
276
|
+
taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs).should_not include(taggable1)
|
277
|
+
end
|
278
|
+
|
279
|
+
context "Inherited Models" do
|
280
|
+
before do
|
281
|
+
@taggable1 = InheritingTaggableModel.create!(:name => "InheritingTaggable 1")
|
282
|
+
@taggable2 = InheritingTaggableModel.create!(:name => "InheritingTaggable 2")
|
283
|
+
@taggable3 = InheritingTaggableModel.create!(:name => "InheritingTaggable 3")
|
284
|
+
@taggable4 = InheritingTaggableModel.create!(:name => "InheritingTaggable 4")
|
285
|
+
@taggable5 = TaggableModel.create!(:name => "Taggable 5")
|
286
|
+
|
287
|
+
@taggable1.offering_list = "one, two"
|
288
|
+
@taggable1.need_list = "one, two"
|
289
|
+
@taggable1.save!
|
290
|
+
|
291
|
+
@taggable2.need_list = "one, two"
|
292
|
+
@taggable2.save!
|
293
|
+
|
294
|
+
@taggable3.offering_list = "one, two"
|
295
|
+
@taggable3.save!
|
296
|
+
|
297
|
+
@taggable4.tag_list = "one, two, three, four"
|
298
|
+
@taggable4.save!
|
299
|
+
|
300
|
+
@taggable5.need_list = "one, two"
|
301
|
+
@taggable5.save!
|
302
|
+
end
|
303
|
+
|
304
|
+
it "should find objects with tags of matching contexts" do
|
305
|
+
@taggable1.find_matching_contexts(:offerings, :needs).should include(@taggable2)
|
306
|
+
@taggable1.find_matching_contexts(:offerings, :needs).should_not include(@taggable3)
|
307
|
+
@taggable1.find_matching_contexts(:offerings, :needs).should_not include(@taggable4)
|
308
|
+
@taggable1.find_matching_contexts(:offerings, :needs).should_not include(@taggable5)
|
309
|
+
|
310
|
+
@taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs).should include(@taggable2)
|
311
|
+
@taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs).should_not include(@taggable3)
|
312
|
+
@taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs).should_not include(@taggable4)
|
313
|
+
@taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs).should include(@taggable5)
|
314
|
+
end
|
315
|
+
|
316
|
+
it "should not include the object itself in the list of related objects with tags of matching contexts" do
|
317
|
+
@taggable1.find_matching_contexts(:offerings, :needs).should_not include(@taggable1)
|
318
|
+
@taggable1.find_matching_contexts_for(InheritingTaggableModel, :offerings, :needs).should_not include(@taggable1)
|
319
|
+
@taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs).should_not include(@taggable1)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
describe 'Tagging Contexts' do
|
325
|
+
it 'should eliminate duplicate tagging contexts ' do
|
326
|
+
TaggableModel.acts_as_taggable_on(:skills, :skills)
|
327
|
+
TaggableModel.tag_types.freq[:skills].should_not == 3
|
328
|
+
end
|
329
|
+
|
330
|
+
it "should not contain embedded/nested arrays" do
|
331
|
+
TaggableModel.acts_as_taggable_on([:array], [:array])
|
332
|
+
TaggableModel.tag_types.freq[[:array]].should == 0
|
333
|
+
end
|
334
|
+
|
335
|
+
it "should _flatten_ the content of arrays" do
|
336
|
+
TaggableModel.acts_as_taggable_on([:array], [:array])
|
337
|
+
TaggableModel.tag_types.freq[:array].should == 1
|
338
|
+
end
|
339
|
+
|
340
|
+
it "should not raise an error when passed nil" do
|
341
|
+
lambda {
|
342
|
+
TaggableModel.acts_as_taggable_on()
|
343
|
+
}.should_not raise_error
|
344
|
+
end
|
345
|
+
|
346
|
+
it "should not raise an error when passed [nil]" do
|
347
|
+
lambda {
|
348
|
+
TaggableModel.acts_as_taggable_on([nil])
|
349
|
+
}.should_not raise_error
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
describe 'Caching' do
|
354
|
+
before(:each) do
|
355
|
+
@taggable = CachedModel.new(:name => "Bob Jones")
|
356
|
+
@another_taggable = OtherCachedModel.new(:name => "John Smith")
|
357
|
+
end
|
358
|
+
|
359
|
+
it "should add saving of tag lists and cached tag lists to the instance" do
|
360
|
+
@taggable.should respond_to(:save_cached_tag_list)
|
361
|
+
@another_taggable.should respond_to(:save_cached_tag_list)
|
362
|
+
|
363
|
+
@taggable.should respond_to(:save_tags)
|
364
|
+
end
|
365
|
+
|
366
|
+
it "should add cached tag lists to the instance if cached column is not present" do
|
367
|
+
TaggableModel.new(:name => "Art Kram").should_not respond_to(:save_cached_tag_list)
|
368
|
+
end
|
369
|
+
|
370
|
+
it "should generate a cached column checker for each tag type" do
|
371
|
+
CachedModel.should respond_to(:caching_tag_list?)
|
372
|
+
OtherCachedModel.should respond_to(:caching_language_list?)
|
373
|
+
end
|
374
|
+
|
375
|
+
it 'should not have cached tags' do
|
376
|
+
@taggable.cached_tag_list.should be_blank
|
377
|
+
@another_taggable.cached_language_list.should be_blank
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'should cache tags' do
|
381
|
+
@taggable.update_attributes(:tag_list => 'awesome, epic')
|
382
|
+
@taggable.cached_tag_list.should == 'awesome, epic'
|
383
|
+
|
384
|
+
@another_taggable.update_attributes(:language_list => 'ruby, .net')
|
385
|
+
@another_taggable.cached_language_list.should == 'ruby, .net'
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'should keep the cache' do
|
389
|
+
@taggable.update_attributes(:tag_list => 'awesome, epic')
|
390
|
+
@taggable = CachedModel.find(@taggable)
|
391
|
+
@taggable.save!
|
392
|
+
@taggable.cached_tag_list.should == 'awesome, epic'
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'should update the cache' do
|
396
|
+
@taggable.update_attributes(:tag_list => 'awesome, epic')
|
397
|
+
@taggable.update_attributes(:tag_list => 'awesome')
|
398
|
+
@taggable.cached_tag_list.should == 'awesome'
|
399
|
+
end
|
400
|
+
|
401
|
+
it 'should remove the cache' do
|
402
|
+
@taggable.update_attributes(:tag_list => 'awesome, epic')
|
403
|
+
@taggable.update_attributes(:tag_list => '')
|
404
|
+
@taggable.cached_tag_list.should be_blank
|
405
|
+
end
|
406
|
+
|
407
|
+
it 'should have a tag list' do
|
408
|
+
@taggable.update_attributes(:tag_list => 'awesome, epic')
|
409
|
+
@taggable = CachedModel.find(@taggable.id)
|
410
|
+
@taggable.tag_list.sort.should == %w(awesome epic).sort
|
411
|
+
end
|
412
|
+
|
413
|
+
it 'should keep the tag list' do
|
414
|
+
@taggable.update_attributes(:tag_list => 'awesome, epic')
|
415
|
+
@taggable = CachedModel.find(@taggable.id)
|
416
|
+
@taggable.save!
|
417
|
+
@taggable.tag_list.sort.should == %w(awesome epic).sort
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
context 'when tagging context ends in an "s" when singular (ex. "status", "glass", etc.)' do
|
422
|
+
describe 'caching' do
|
423
|
+
before { @taggable = OtherCachedModel.new(:name => "John Smith") }
|
424
|
+
subject { @taggable }
|
425
|
+
|
426
|
+
it { should respond_to(:save_cached_tag_list) }
|
427
|
+
its(:cached_language_list) { should be_blank }
|
428
|
+
its(:cached_status_list) { should be_blank }
|
429
|
+
its(:cached_glass_list) { should be_blank }
|
430
|
+
|
431
|
+
context 'language taggings cache after update' do
|
432
|
+
before { @taggable.update_attributes(:language_list => 'ruby, .net') }
|
433
|
+
subject { @taggable }
|
434
|
+
|
435
|
+
its(:language_list) { should == ['ruby', '.net']}
|
436
|
+
its(:cached_language_list) { should == 'ruby, .net' } # passes
|
437
|
+
its(:instance_variables) { should include((RUBY_VERSION < '1.9' ? '@language_list' : :@language_list)) }
|
438
|
+
end
|
439
|
+
|
440
|
+
context 'status taggings cache after update' do
|
441
|
+
before { @taggable.update_attributes(:status_list => 'happy, married') }
|
442
|
+
subject { @taggable }
|
443
|
+
|
444
|
+
its(:status_list) { should == ['happy', 'married'] }
|
445
|
+
its(:cached_status_list) { should == 'happy, married' } # fails
|
446
|
+
its(:cached_status_list) { should_not == '' } # fails, is blank
|
447
|
+
its(:instance_variables) { should include((RUBY_VERSION < '1.9' ? '@status_list' : :@status_list)) }
|
448
|
+
its(:instance_variables) { should_not include((RUBY_VERSION < '1.9' ? '@statu_list' : :@statu_list)) } # fails, note: one "s"
|
449
|
+
|
450
|
+
end
|
451
|
+
|
452
|
+
context 'glass taggings cache after update' do
|
453
|
+
before do
|
454
|
+
@taggable.update_attributes(:glass_list => 'rectangle, aviator')
|
455
|
+
end
|
456
|
+
|
457
|
+
subject { @taggable }
|
458
|
+
its(:glass_list) { should == ['rectangle', 'aviator'] }
|
459
|
+
its(:cached_glass_list) { should == 'rectangle, aviator' } # fails
|
460
|
+
its(:cached_glass_list) { should_not == '' } # fails, is blank
|
461
|
+
if RUBY_VERSION < '1.9'
|
462
|
+
its(:instance_variables) { should include('@glass_list') }
|
463
|
+
its(:instance_variables) { should_not include('@glas_list') } # fails, note: one "s"
|
464
|
+
else
|
465
|
+
its(:instance_variables) { should include(:@glass_list) }
|
466
|
+
its(:instance_variables) { should_not include(:@glas_list) } # fails, note: one "s"
|
467
|
+
end
|
468
|
+
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
describe "taggings" do
|
474
|
+
before(:each) do
|
475
|
+
@taggable = TaggableModel.new(:name => "Art Kram")
|
476
|
+
end
|
477
|
+
|
478
|
+
it 'should return [] taggings' do
|
479
|
+
@taggable.taggings.should == []
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
describe "@@remove_unused_tags" do
|
484
|
+
before do
|
485
|
+
@taggable = TaggableModel.create(:name => "Bob Jones")
|
486
|
+
@tag = ActsAsTaggableOn::Tag.create(:name => "awesome")
|
487
|
+
|
488
|
+
@tagging = ActsAsTaggableOn::Tagging.create(:taggable => @taggable, :tag => @tag, :context => 'tags')
|
489
|
+
end
|
490
|
+
|
491
|
+
context "if set to true" do
|
492
|
+
before do
|
493
|
+
ActsAsTaggableOn.remove_unused_tags = true
|
494
|
+
end
|
495
|
+
|
496
|
+
it "should remove unused tags after removing taggings" do
|
497
|
+
@tagging.destroy
|
498
|
+
ActsAsTaggableOn::Tag.find_by_name("awesome").should be_nil
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
context "if set to false" do
|
503
|
+
before do
|
504
|
+
ActsAsTaggableOn.remove_unused_tags = false
|
505
|
+
end
|
506
|
+
|
507
|
+
it "should not remove unused tags after removing taggings" do
|
508
|
+
@tagging.destroy
|
509
|
+
ActsAsTaggableOn::Tag.find_by_name("awesome").should == @tag
|
510
|
+
end
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
end
|